Hey, Scripting Guy!뜬금없는 명언

Microsoft Scripting Guys

제가 태어나기 훨씬 전인 기원전 399년에 사망한 소크라테스는 "시험하지 않는 삶은 살 가치가 없다"는 말로 유명합니다. 이 격언을 모르는 사람은 거의 없습니다. 그런데 놀랍게도 최근 Scripting Guys는 소크라테스가 "시험하지 않는 삶은 살 가치가 없다"는 말을 한 적이 없다는 사실을 알아냈습니다. 사실 이 말은 수백 년 전 중세시대의 한 서기에 의해 잘못 번역되었습니다. 원래 소크라테스가 한 말은 "시험하지 않는 XML 파일은 가지고 있을 가치가 없다"입니다.

이제야 그럴듯한 철학적 격언이 되었군요! XML의 인기는 나날이 높아지는 추세입니다. Scripting Guys의 테스트용 컴퓨터 한 대에서 잠깐 검색해 보니 다양한 시스템 또는 응용 프로그램에 사용되는 XML 파일의 수가 500개 넘게 발견되었습니다.

익히 알려진 바와 같이, 요즘은 중요한 데이터의 상당수가 XML 형식으로 저장됩니다. 좋은 일이지요. 단, 데이터가 시험을 거친다는 가정하에 말입니다. 그런데 슬프게도 많은 경우 데이터는 시험을 거치지 않습니다. 그 이유 중 하나는 XML 파일을 어떻게 시험해야 하는지 방법조차 모르는 사람이 많다는 데 있습니다. 구체적으로 보면 XML 파일을 쿼리하고 검색하는 방법을 모릅니다.

참고로, 그리스 철학자에 대해 잘 모르는 사람을 위해 말하자면 소크라테스는 사물에 대한 대화를 강력히 지지했지만 행동에 대해서는 그만큼 열성적이지 않았습니다. 사실 그의 아내 크산티페는 소크라테스를 가리켜 "쓸모없는 게으르뱅이"라고 말했습니다. Scripting Guys가 소크라테스에게 동질감을 느끼는 이유를 아시겠지요?

물론 이 글을 읽는 여러분 중 몇몇은 "가만, Scripting Guys 이전 칼럼 "자동차 뒤쫓기… 그리고 XML"(technet.microsoft.com/magazine/cc162506)에서 이미 XML 파일 검색에 대해 설명하지 않았나? 그런데 왜 그 주제를 지금 다시 꺼내지? 똑같은 칼럼을 쓰고 또 쓰고 할 만큼 Scripting Guys가 게으른 것인가?"라는 생각을 하실 수도 있습니다.

믿거나 말거나지만 사실 우리는 그것보다 더 게으릅니다. 다만 우리가 이 주제를 다시 꺼낸 데는 게으름보다 더 중요한 이유가 있습니다. 이전 칼럼에서 우리는 그림 1의 코드와 같은 구조를 가진 XML 파일을 사용한 작업에 대해 설명했습니다. 여기에서 각 속성 값은 하나의 노드를 나타냅니다.

그림 1 노드만으로 이루어진 XML 파일

<?xml version='1.0'?> 
  <INVENTORY> 
    <COMPUTER>
      <os>Windows XP</os>
      <department>Human Resources
      </department>
      <name>atl-ws-001</name>
    </COMPUTER> 
    <COMPUTER>
      <os>Windows XP</os>
      <department>Finance</department>
      <name>atl-ws-002</name>
    </COMPUTER> 
    <COMPUTER>
      <os>Windows Vista</os>
      <department>Finance</department>
      <name>atl-ws-003</name>
    </COMPUTER> 
  </INVENTORY>

이 방식에 문제는 없지만 사람들이 지적했듯이 XML 파일은 이것 외의 다른 구조도 가질 수 있습니다. 위의 예에서 파일의 각 컴퓨터에는 각자의 노드가 있고, 이러한 노드에는 각기 여러 자식 노드(os, department 및 name)가 있습니다. 그런데 각 노드에 자식 노드가 없고, 대신 추가 속성 값이 특성으로 구성되는 형태로 XML 파일을 작성하는 방법도 가능합니다. 그림 2의 파일처럼 말이지요.

그림 2 특성이 사용된 XML 파일

<?xml version='1.0'?> 
  <HARDWARE> 
      <COMPUTER os="Windows XP" department="Human Resources">atl-ws-001</COMPUTER> 
      <COMPUTER os="Windows XP" department="Finance">atl-ws-002</COMPUTER> 
      <COMPUTER os="Windows Server 2003" department="IT">atl-fs-003</COMPUTER> 
      <COMPUTER os="Windows Vista" department="IT">atl-ws-004</COMPUTER> 
      <COMPUTER os="Windows Vista" department="Human Resources">atl-ws-005</COMPUTER> 
      <COMPUTER os="Windows Vista" department="Finance">atl-ws-006</COMPUTER> 
      <COMPUTER os="Windows XP" department="Sales">atl-ws-007</COMPUTER> 
      <COMPUTER os="Windows Server 2008" department="IT">atl-fs-008</COMPUTER> 
      <COMPUTER os="Windows XP" department="Human Resources">atl-ws-009</COMPUTER> 
      <COMPUTER os="Windows Vista" department="Sales">atl-ws-010</COMPUTER> 
  </HARDWARE>

이것이 문제일까요? 사실 문제입니다. 까마득한 옛날에 Scripting Guys가 여러분에게 제시한, 아주 오래된 스크립트는 그림 2와 같은 구조의 XML 파일에서 동작하지 않습니다. 네, 절대 동작하지 않지요. 그리고 이러한 유형의 XML 파일을 읽어야 하는 사람들이 워낙 많으니 확실히 문제라고 해야겠습니다.

여러분은 그저 질문을 하고 Scripting Guys가 문제를 해결할 때까지 1년 반만 기다리시면 됩니다.

깊이 들어가기에 앞서 문제의 XML 파일을 더 자세히 살펴보겠습니다. 이 파일에는 HARDWARE라는 주 노드가 있고 각 개별 컴퓨터는 HARDWARE의 자식 노드로 존재합니다. 또한 각 노드에는 한 쌍의 특성이 있습니다. 바로 os(컴퓨터에 설치된 운영 체제의 이름을 저장하는 데 사용)와 department(컴퓨터를 소유한 부서의 이름을 저장하는 데 사용)입니다.

예를 들어 Windows XP를 실행하는 모든 컴퓨터 목록을 구하려는 경우를 가정해 보겠습니다. 스크립트를 사용해서 원하는 결과를 얻을 수 있을까요? 당연히 가능합니다.

Set xmlDoc = _
  CreateObject("Microsoft.XMLDOM")

xmlDoc.Async = "False"
xmlDoc.Load("HARDWARE.xml")

Set colNodes=xmlDoc.selectNodes _
  ("//HARDWARE/COMPUTER[@os='Windows XP']")

For Each objNode in colNodes
  Wscript.Echo objNode.Text 
Next

자세히 살펴봅시다. 먼저 Microsoft.XMLDOM 개체의 인스턴스를 만듭니다. 이름에서 알 수 있듯이 이 개체는 XML 파일을 사용할 수 있게 해 줍니다. 이 개체를 만들고 나면 다음으로 Async 속성을 False로 설정하여 문서를 비동기적이 아니라 동기적으로 로드해야 함을 스크립트가 알 수 있도록 합니다. 스크립트가 왜 이 부분에 신경을 쓰냐구요? 솔직히 말해 신경 안 쓰겠죠. 어쨌거나 스크립트는 무생물인데요(Scripting Guys처럼).

그렇지만 여러분은 신경을 써야 합니다. 문서를 비동기적으로 로드할 경우 문서가 완전히 로드되지 않은 상태에서 스크립트가 계속 실행될 수 있기 때문입니다. 이는 좋지 않은 현상입니다. 아직 생성되지도 않은 문서에 작업을 수행하려는 경우 발생할 문제를 생각해 보십시오. XML 파일을 동기적으로 로드하면 파일이 완전히 로드된 후에 스크립트가 진행되도록 보장됩니다.

어떻게 하다 보니 이제 XML 파일을 로드할 시점이군요. 이를 위해 Load 메서드를 호출하고 C:\Scripts\HARDWARE.xml 파일을 엽니다. 그러면 다음과 같은 코드를 볼 수 있습니다.

Set colNodes=xmlDoc.selectNodes _
  ("//HARDWARE/COMPUTER[@os='Windows XP']")

지금 우리가 하는 일은 SelectNodes 메서드를 사용하여 XML 파일을 쿼리하고 가져오고자 하는 XML 노드를 스크립트에 알리는 것입니다. 이 코드의 구문은 확실히 다소 특이합니다.

먼저 XML 파일 내에 경로부터 지정해야 합니다. 문제의 XML 파일에는 HARDWARE라는 최상위 노드가 있고 그 다음 두 번째 수준에 일련의 COMPUTER 노드가 있습니다. 이 두 번째 수준의 각 노드는 XML 데이터 파일에서 하나의 레코드를 나타냅니다. 이러한 이유로 selectNodes 쿼리는 다음과 같이 시작됩니다.

//HARDWARE/COMPUTER

이 부분에서 다음과 같은 구조를 볼 수 있습니다.

[@os='Windows XP']

이 쿼리 부분은 표준 SQL 쿼리의 WHERE 절과 비슷합니다. SQL에서는 다음과 같이 이것과 비슷한 데이터베이스 쿼리를 사용하여 Windows XP 운영 체제를 실행하는 모든 컴퓨터 목록을 검색할 수 있습니다.

SELECT Name FROM Hardware 
    WHERE OS = 'Windows XP'

SelectNodes 메서드를 사용하여 수행하는 작업도 비슷합니다. 즉, os 특성(@os)이 Windows XP인 모든 컴퓨터의 목록을 가져오도록 요청합니다. 이 부분이 WHERE 절임을 나타내기 위해 전체 절을 대괄호 안에 넣습니다.

참고 이러한 유형의 쿼리를 XPath 쿼리라고 합니다. XPath에 대한 자세한 내용은 msdn.microsoft.com/library/ms256115.aspx를 참조하십시오.

앞서 언급했듯이 약간 이상하지만 어쨌든 동작은 합니다. Finance 부서에 속하는 모든 컴퓨터 목록을 얻으려면 어떻게 해야 할까요? 간단합니다. 다음과 같이 selectNodes를 호출하면 됩니다.

Set colNodes=xmlDoc.selectNodes _
  ("//HARDWARE/COMPUTER" & _
   "[@department='Finance']")

이번에도 WHERE 절은 대괄호로 묶고 @ 기호를 붙여 특성 이름(department)을 앞에 쓴 다음 원하는 값을 지정합니다.

간단하지요?

selectNodes 메서드를 호출한 후에는 다음과 같이 반환된 값 모음을 돌면서 Text 속성을 출력하는 간단한 방법으로 컴퓨터 이름을 출력할 수 있습니다.

For Each objNode in colNodes
  Wscript.Echo objNode.Text 
Next

그렇다면 이를 통해 어떤 종류의 정보를 얻게 될까요? 사실 다음과 같은 정보를 얻게 됩니다.

atl-ws-001
atl-ws-002
atl-ws-007
atl-ws-009

즉, 여전히 Windows XP를 실행하는 모든 컴퓨터의 이름이 반환됩니다.

덧붙여 말하자면 컴퓨터 이름만 반환할 수 있는 것은 아닙니다. 컴퓨터 이름은 각 노드의 "기본값"이기 때문에 Text 속성을 참조할 경우 바로 컴퓨터 이름을 얻게 됩니다.

또는 정확히 어떤 특성이 반환되기를 원하는지 지정할 수 있습니다. 예를 들어 다음과 같이 수정된 For Each 루프를 보십시오.

For Each objNode in colNodes
    Wscript.Echo objNode.Text 
    Wscript.Echo objNode.Attributes. _
      getNamedItem("department").Text
    Wscript.Echo
Next

보시는 바와 같이 이 루프에서도 여전히 Text 속성의 값을 출력하지만 우리는 여기에 다음과 같은 코드도 덧붙였습니다.

Wscript.Echo objNode.Attributes. _
  getNamedItem("department").Text

여기에서는 getNamedItem 메서드를 사용하여 department 특성의 값을 가져옵니다. 그런 다음 이 특성에 대한 Text 속성을 출력합니다. 이와 같은 방법으로 화면에 출력할 특성 값과 출력하지 않을 특성 값을 지정할 수 있습니다. 레코드 사이에 빈 줄을 넣기 위해 Wscript.Echo 명령도 추가했습니다. 이 스크립트를 실행하면 다음과 같은 결과를 얻게 됩니다.

atl-ws-001
Human Resources

atl-ws-002
Finance

atl-ws-007
Sales

atl-ws-009
Human Resources

물론 필요한 경우 더 복잡한 쿼리를 작성할 수 있습니다. 예를 들어 Windows XP 또는 Windows Vista를 실행하는 모든 컴퓨터 목록이 필요하다고 가정해 보십시오. SQL에서는 OR 쿼리를 사용하면 됩니다. 물론 XML 파일을 쿼리하는 방법으로도 똑같은 일을 할 수 있습니다.

Set colNodes=xmlDoc.selectNodes _
    ("//HARDWARE/COMPUTER" & _
     "[@os='Windows XP' or " & _
     "@os='Windows Vista']")

한번 볼까요? 한 쌍의 대괄호 안에 @os='Windows XP' or @os='Windows Vista'라는 두 개의 조건을 제시했습니다. 그게 전부입니다. 그러면 그림 3과 같은 보고서를 얻게 됩니다.

그림 3 OR 쿼리의 결과

atl-ws-001
Human Resources

atl-ws-002
Finance

atl-ws-004
IT

atl-ws-005
Human Resources

atl-ws-006
Finance

atl-ws-007
Sales

atl-ws-009
Human Resources

atl-ws-010
Sales

XML 파일을 다시 확인해 보면 Windows XP 또는 Windows Vista를 실행하는 컴퓨터는 출력에 포함되고 Windows Server 2003 또는 Windows Server 2008을 실행하는 컴퓨터는 출력에 포함되지 않음을 알 수 있습니다. 당연히 출력에 포함되면 안 되지요.

질문이 있나요? Windows XP를 실행하고 Finance 부서에 속하는 컴퓨터만 반환하는, 보다 한정된 쿼리를 작성하고 싶나요? 이미 짐작하겠지만 간단합니다. 다음과 같이 AND 쿼리만 작성하면 됩니다.

Set colNodes=xmlDoc.selectNodes _
    ("//HARDWARE/COMPUTER" & _
     "[@os='Windows XP' and " & _
     "@department='Finance']")

그러면 다음과 같은 데이터 집합이 반환됩니다.

atl-ws-002
Finance

왜 항목이 하나밖에 없을까요? 맞습니다. Finance 부서에는 Windows XP를 실행하는 컴퓨터가 한 대뿐이기 때문입니다.

이러한 유형의 XML 파일을 다루는 데 있어 이 칼럼이 도움이 되면 좋겠습니다. 혹시 또 다른 유형의 XML 파일이 발견된다면 그건 알아서 잘 해결하세요.

이번 달 칼럼은 유명한 격언으로 시작했으니 끝낼 때도 비슷하게 하는 편이 좋겠습니다. 그런 의미에서 Scripting Guys가 마음속에 새겨두고 있는 카이저 빌헬름 2세의 말로 칼럼을 마치겠습니다. "이로써 나는 프로이센 왕위, 그리고 이와 함께 독일 제국의 왕위를 영원히 포기하노라."

Dr. Scripto의 Scripting Perplexer

매달 퍼즐 풀기 실력뿐만 아니라 스크립팅 능력까지 시험해 볼 수 있는 문제입니다.

2008년 10월: VBScript 빈 칸 채우기

이 퍼즐을 풀려면 VBScript 함수의 이름으로 각 행을 채우면 됩니다. 다 채웠으면 파란색 사각형 안의 문자를 짜맞춰 함수 이름을 하나 더 찾으십시오.

fig10.gif

정답:

Dr. Scripto의 Scripting Perplexer

정답: 2008년 10월: VBScript 빈 칸 채우기

puzzle_answer.gif

The Scripting Guys(Greg Stemp와 Jean Ross)는 Microsoft 직원들입니다.