2010.09.07 Update: 아크몬드님의 오픈 캐스트 목록에 이 글이 올라왔습니다. 많은 홍보 부탁드립니다. :-)

한동안 잊고 있다가 뒤늦게 창고 한구석에서 꺼내는 포스트입니다. (입이 열개라도 할 말이 없군요. ㅋㅋㅋ)

지난번 구관이 명관! ([Software Development/Windows NT Command Interpreter] - 구관이 명관! Batch File 활용 - 배치 파일로 프로그램을 작성하는데에 필요한 기본 사항 살펴보기)시리즈를 마무리하기 위해서 글을 좀 더 써봅니다.

반복문을 사용할 수 있다는 점은 상당히 중요한 부분입니다. 배치 파일의 경우도, 열거할 수 있는 대상이 있고, 이를 정확하게 열거하고 제어할 수 있으려면 반복문이 당연히 필요합니다. 이를 돕는 명령어가 바로 for 문인데 간단히 다음의 예시를 살펴보기로 하겠습니다. 아래는 한데 묶여있지 않고 각각 분리된 build.xml 파일을 한꺼번에 컴파일하기 위한 목적으로 작성된 배치 파일의 예시입니다. W 은행의 전산 시스템을 자동화하기 위하여 1년전 처음 작성하였으며, 최근 C 은행의 닷넷 프레임워크 기반 프로젝트 (이 때는 msbuild를 호출하기 위함이었습니다.) 에서도 비슷한 이유로 재사용한 적이 있는 저만의 snippet입니다. :-)

@rem Java Source 컴파일 도구
@rem 작성일 - 2010년 8월 14일
@rem 작성자 - 남정현

@rem 명령줄 입력을 숨깁니다.
@echo off

@rem 화면을 초기화합니다.
@cls

@echo Java Source 컴파일 작업 시작
@echo.

@rem /d 스위치는 디렉터리 반복 시 사용합니다.
@rem 배치 파일 내에서 변수 선언을 전달하려면 이스케이프 처리를 해야 합니다.
@rem if 문은 아래 명령과 같이 여러 줄 입력이 가능합니다.
@rem ant 명령이 실패하는 경우 Jakarta Ant Build System이 PATH에 등록되어
@rem 있는 상태인지 확인해야 합니다.

@for /d %%i in (*.*) do @if exist "%%i\build.xml". (
  @echo 처리 중: "%%i"
  @ant -buildfile "%%i\build.xml"
  @echo.
)

@echo on

위의 부분에서 굵게 표시한 부분이 배치 파일 상에서의 for 문의 사용 예시입니다. 배치 파일 안에서 for 문을 사용하기 때문에 조금 변수명에 이스케이프 처리를 위해서 % 지시자를 한번 더 사용한다는 특이 사항만 제외하면 무난한 문법구조를 나타내고 있습니다.

for 문 바로 뒤에 오는 /d 스위치는 열거할 파일 시스템 대상 객체를 디렉터리만으로 한정하는 것으로 이 스위치를 생략할 경우 파일명이 해당 경로상에 존재할 때 같이 열거됩니다. 그 다음, 변수명, 검색할 객체 이름의 패턴 (DOS 방식이므로 전형적인 와일드 카드 패턴을 따릅니다)과 수행할 명령을 do 지시자 다음에 지정합니다. 배치 파일 안이므로 당연히 여러줄의 명령을 한번에 그룹으로 묶을 수 있습니다. 만약 /d 스위치 대신 /f 스위치를 사용할 경우 파일만이 열거될 수 있습니다. 그 외에 /r 스위치를 사용하여 재귀 탐색을 할 수 있고, /l 스위치를 사용하면 우리가 일반적인 프로그래밍 언어에서 사용하는 인덱스 기반 탐색을 위한 카운트 증감 (증가 및 감소 모두 포함됩니다.)을 수행할 수도 있습니다.

@rem 아래 명령은 시작값 1 부터 종료값 5까지 1씩 더하며 반복됩니다.
@rem C 언어로 번역한다면 for (int i = 1; i <= 5; i += 1) 과 같은 의미입니다.
@for /l %%i in (1,1,5) do @echo %%i

@rem 아래 명령은 시작값 5 부터 종료값 1까지 1씩 빼면서 반복됩니다.
@rem C 언어로 번역한다면 for (int i = 5; i >= 1; i -= 1) 과 같은 의미입니다.
@for /l %%i in (5,-1,1) do @echo %%i

보너스: Unix 및 Linux Build Script에서 흔히 사용하는 Stack 기반 Directory 탐색 구현하기

일반적으로 cd 명령 - 혹은 - chdir 명령은 forward only 탐색으로 드라이브를 바꾸거나, 전혀 다른 위치로 갑자기 이동하게 되는 경우 과거로 되돌아갈 근거가 없습니다. 그리고 DOS 기반의 명령이므로 UNIX와 같은 기능은 제공하지 않을 것이라는 일반적인 선입견까지 더해지면서 이런 기능이 "없는 것"으로 분류되기도 합니다만 실제로는 이런 기능이 NT 인터프리터에 존재합니다.

다음의 명령어를 한 번씩 실행해보면 Stack 기반의 탐색이 어떤 의미를 지니는지 아실 수 있습니다.

@rem 참고로 아래에 \ 기호가 더 붙은 이유는 %systemdrive%가 C: 까지이므로 경로임을 표시하기 위해 \를 붙여 C:\로 인지시키기 위함이었습니다.
@pushd %systemdrive%\
@pushd %windir%
@pushd %programfiles%
@popd
@popd
@popd

현재 위치가 어디이든 관계없이 첫 번째 pushd 명령으로 인해서 %systemdrive%\ 경로로 이동하고 내부 스택에 주어진 경로가 Push됩니다. 두 번째 pushd 명령으로 %windir%가 그 뒤를 이어서 내부 스택에 Push되며, 마지막으로 %programfiles% 경로가 내부 스택에 Push됩니다. 여기까지는 출력 결과만으로 놓고 보았을 때 cd 명령과 차이가 없어 보입니다. 그러나 popd 명령을 사용하면 차이가 나타납니다.

popd 명령을 처음 실행하면, 가장 마지막에 내부 스택에 들어갔던 %windir%가 Pop되고 현재 디렉터리로 변경됩니다. 그리고 다시 popd 명령을 실행하면 이번엔 %systemdrive%\로 현재 디렉터리가 변경됩니다. 그리고 다시 popd 명령을 실행하면 원래 위치로 되돌아가는 것을 볼 수 있습니다.

여기서 중요한 점은, 지금 스택에 집어넣고 빼낸 디렉터리들이 원래 위치했던 디렉터리 기준으로는 제각기 독립적인 노드들이었다는 점이며, 디렉터리를 스택 구조에 넣고 관리할 수 있으므로 배치 파일에서 탐색이 자유로워질 수 있다는 이점을 확인할 수 있습니다.

보너스 2: FOR 문으로 텍스트 검색하기

아래의 명령줄은 정규 표현식을 지원하지는 않지만 GREP이 아쉬운 분들을 위한, 간단히 사용할 수 있는 재귀 검색이 지원되는 파일 검색 도구입니다. (출처: http://kennethhunt.com/archives/000173.html)

문법: for /r "검색할 디렉터리" %v in (*.확장자) do find /N /I "검색할 키워드" "%~fv" >> "결과를 저장할 파일 혹은 장치"
예시: for /r "C:\Windows" %v in (*.txt) do find /n /i "Windows" "%~fv" >> "%userprofile%\result.txt"

마무리

보잘것 없어보이는 수단이지만, 때로는 PowerShell을 검토하는 것보다, VBScript를 검토하는 것보다, 그리고 우리 프로그래머 입장에서는 C++이나 C#을 검토하는 것보다 배치 파일에 기대는 것이 더 편할 수 있습니다. 파리를 잡기 위하여 패트리어트 미사일을 날리지 말고, 가까이 있고 쉽게 사용할 수 있는 도구를 여러분의 친구로 만들기를 바라며 소소하게 써내려간 시리즈를 뒤늦게나마 마무리합니다.

감사합니다. :-)

크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by Windows Azure MVP 남정현 (rkttu.com)

지난번 강좌에 이어서 오늘은 if 문을 사용하여 파일, 디렉터리, 프로그램의 실행 결과를 평가하는 방법, 파일 출력을 수행하는 방법, 그리고 명령줄 인수를 받아들이는 방법을 살펴보도록 하겠습니다. 앞서 살펴본 강좌의 비교 연산자 기능을 바탕으로, 실제 수행 결과를 평가할 수 있다면 배치 파일 구문을 스크립트 프로그래밍 언어처럼 활용할 수 있을 것입니다.

 

1. 파일 및 디렉터리의 존재 여부를 판정하기

 

파일 및 디렉터리의 존재 여부를 판정하기 위하여 exist 키워드를 사용할 수 있습니다. 다음은 if문와 exist 키워드를 조합한 예시입니다.

 

if exist "%windir%\fonts\consola.ttf" echo Your system has Consola font.

 

위의 구문에서 exist 키워드 다음에 오는 인자는 존재 여부를 검사하고자 하는 경로에 대한 것으로, 환경 변수를 사용하여 혼합할 수도 있습니다. 또한, exist 키워드 앞에 not 키워드를 붙여 부정의 의미로 명령을 실행하여 결과를 뒤집는 것도 가능합니다.

 

if not exist "%windir%\fonts\consola.ttf" echo Your system does not have Consola font.

 

만약 디렉터리의 존재 여부를 파악하고자 한다면 파일 경로 대신 디렉터리 경로 대입만 하면 됩니다.

 

if exist "%windir%" echo WINDIR environment variable contains valid path.

 

여기서 한 가지 생각해봐야 할 문제점이 있는데, Windows의 경우 기본 배치 파일 명령만으로는 존재한다고 인지된 대상이 실제로 파일인지 디렉터리인지 파악할 방법이 명확하지 않다는 문제점이 있습니다.

 

파일 및 디렉터리 관련 API를 호출하면서 파일이나 디렉터리를 생성할 수 없다는 오류를 만나는 경우가 종종 있는데, 대개의 경우 권한 문제로 볼 수 있지만, 아주 드물게 디렉터리가 생성되기를 기대한 위치에서 같은 이름의 확장자가 없는 파일이 존재하여 이름이 겹치게 되어 동작이 실패할 수 있고, 반대의 경우도 있을 수 있습니다.

 

이 문제는 배치 파일에 대한 문제 뿐만이 아니라, Win32 프로그래밍을 하면서도 한번쯤 고려해볼 필요가 있는 주제일 것입니다.

 

2. 프로그램의 실행 결과를 판정하기

 

표준 Windows 배치 명령어를 통하여 프로그램의 실행 결과를 판정하는 방법은, 유닉스 기반 시스템의 경우처럼 프로그램이 반환하는 종료 코드를 기준으로 하는 것이 보편적입니다. 명령줄 해석기의 경우, 이를 %ERRORLEVEL% 변수에 보관하며, 우리가 앞서 살펴본 강좌에서처럼 %ERRORLEVEL% 변수에 대해 연산자를 사용하여 코드를 분석할 수 있습니다. 그리고 지금 소개하는 errorlevel 키워드를 if 문과 조합하여 사용하는 방법도 있습니다.

 

if errorlevel 0 echo Program is succeeded.

 

위와 같이 작성하면, 실제로는 %errorlevel% geq 0 로 해석되는 것입니다. 그리고 errorlevel 키워드 앞에 not이 붙으면 geq에 대한 대우 연산자 lss로 의미가 바뀌어 실제로는 %errorlevel% lss 0로 해석될 것입니다.

 

3. 간단한 파일 출력

 

보통의 경우, 리디렉션을 이용하여 프로그램이 표준 출력 스트림에 송신하는 컨텐츠를 하드디스크에 파일로 저장하는 기법을 많이 이용합니다. 이를 좀 더 응용하면, 연속되는 내용을 쌓아서 보관할 수도 있으므로, 이제까지 설명한 기법들을 이용하여 프로그램의 실행 결과나 변수의 내용을 분석하여 출력 파일을 작성하는 똑똑한 배치 파일도 작성할 수 있습니다.

 

echo "Hello World!" > test.txt

echo "Overwrited?" > test.txt

 

위와 같이 작성할 경우, > 연산자는 파일 연산에서 매번 새로운 파일을 만들고 덮어쓰는 의미로 해석되므로 먼저 실행한 명령어에 의하여 저장된 문자열이 담긴 파일은 사라지고, "Overwrited?" 문자열만을 보관하는 파일만 남게 됩니다. 그렇다면, 두 줄을 이어서 저장하려면 어떻게 바꾸어야 할까요? ">" 대신 ">>" 연산자로 바꾸면 답이 됩니다.

 

echo "Hello World!" >> test.txt

echo "Overwrited?" >> test.txt

 

이와 같이 작성한 파일을 화면에 출력하고 싶다면 con 이라는 예약 장치명을 지정하면 됩니다.

 

copy test.txt con

 

PRN이나 LPT1과 같은 예약 장치명이 동작한다면, 사실 더 좋을 수 있겠지만, 아쉽게도 이러한 예약 장치명을 사용하면 경우에 따라서는 인쇄 기능이 실행되지 않는 대신 명령줄 인터프리터의 실행이 중단되고 맙니다. 인쇄 기능을 사용하고자 한다면, 해당 파일에 관련이 있는 소프트웨어의 인쇄 기능을 대신 사용하는 것이 좋습니다. 메모장이나 워드패드의 경우, /p 스위치 다음에 파일 이름을 지정하여 파일을 인쇄하도록 지시할 수 있습니다.

 

notepad /p test.txt

wordpad /p test.txt

 

4. 명령줄 매개 변수 받아들이기

 

명령줄 매개 변수를 받아들이도록 배치 파일을 구성하면, 배치 파일의 활용 용도가 한층 더 깊어질 수 있습니다. 이 기능은 기본으로 제공되는 기능으로 %0 부터 %9까지의 예약 변수를 활용하면 쉽습니다. 이들 예약 변수는 C 언어를 사용하여 작성하는 main() 메서드의 char **argv 변수에서처럼 0번째 인덱스는 실행하는 배치 파일 그 자체의 Full Path를 포함하고, 1부터 실제 명령줄 인자가 포함됩니다.

 

@echo off

echo %0

if %1 equ Test echo Hello World

echo on

 

위와 같이 프로그래밍한 배치 파일을 Hi.bat 으로 저장하였을 때, 명령줄에서 아래와 같이 명령을 입력하면 Hello World라는 문구가 나타나는 것을 볼 수 있습니다.

 

Hi.bat Test

 

배치 파일 명령줄을 위한 인수로 공백을 포함한 문자열을 넘길 필요가 있을 때는, 기존과 마찬가지로 따옴표로 감싸어 넘기면 동일하게 동작합니다.

크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by Windows Azure MVP 남정현 (rkttu.com)

요즈음 빌드 자동화 기능을 제공하는 프레임워크와 도구들이 굉장히 많이 늘었습니다. 각기 다양한 특색이 있고 장점이 존재하지만 가끔 이와 같은 도구들의 힘을 빌리지 않고 이와 유사한 기능을 작성할 필요가 있는 경우가 있습니다. 이럴 때 한치의 손색이 없는 도구가 하나 있는데 그것이 바로 명령줄 인터프리터가 직접 처리하는 Batch File입니다. 생각보다 Batch File의 능력은 다양합니다. :-)

Note: 이 Article은 Windows XP 이상의 운영 체제에 내장되어있는 Command Interpreter에 대하여 서술한 것으로 Windows 2000, Windows 9x (Microsoft DOS 7.x), Microsoft DOS 6.x 이하에는 맞지 않는 내용도 포함되어있습니다. 착오 없으시기 바랍니다. 더불어, 다른 DOS (예: 4DOS, DR-DOS 등)에서 자체적으로 제공하는 내장 Command Interpreter의 경우 자체 내장되어있는 도움말을 참고하여야 할 수 있습니다.

문자열과 숫자의 비교

일부러 찾아보고 활용해보지 않았을 뿐이지 배치 파일에도 if와 else 구문을 그대로 사용할 수 있습니다. 물론 진짜 제대로된 프로그래밍 언어에서처럼 완벽한 논리 연산을 구현한 것은 아닙니다만 배치 파일을 사용하려는 목적에는 알맞을 수 있는 기능들을 빠짐없이 훌륭하게 구현하고 있습니다.

간단한 if / else 명령문 하나를 입력해 보겠습니다.

if test equ test (echo same string) else (echo different string)
if test neq Test (echo different string) else (echo same string)

위의 명령문을 실행하면 same string 이라는 문자열이 나타납니다. 여기서 test는 보통의 DOS 명령에서 요구하는 파일 경로에 대한 것이 아니라 순수한 문자열이나 숫자가 됩니다. 그리고 equ가 동일성 여부를 판정하는 연산자가 됩니다. 만약 같지 않음을 비교하려면 neq 연산자를 대신 사용하시면 됩니다. 나머지는 보시는 그대로 괄호로 명령문이 묶인 것을 볼 수 있습니다. 괄호를 입력하지 않으면 참 조건시 실행될 echo 문의 나머지 뒷쪽 부분들이 전부 echo 명령에 대한 매개 변수로 지정되기 때문에 이를 구분한 것입니다. 그러면 이번엔 아래와 같이 특정 문자를 대문자로 바꿔서 명령을 수행해보도록 하겠습니다.

if Test equ test (echo same string) else (echo different string)

위의 명령문을 실행하면 기대한 대로 different string 이라는 문자열이 나타납니다. 여기에 부수적으로, 대/소문자 구분을 무시하여 비교하는 명령을 수행해보기로 하겠습니다.

if /i Test equ test (echo same string) else (echo different string)

/i 스위치가 대/소문자 구분을 무시하도록 지정한 것으로 결과는 기대한 대로 same string 이라는 문자열이 될 것입니다. 그렇다면 이 결과에 부정 연산자를 취하여 결과를 뒤집는 것도 가능할까요? 방법은 간단합니다.

if /i not Test equ test (echo same string) else (echo different string)

/i 스위치가 없다면 if 문 바로 다음 앞에, /i 스위치가 있다면 /i 스위치 바로 다음 앞에 not 연산자를 지정하여 Test equ test 식에 대한 결과를 반전 연산자를 통하여 뒤집게 되므로 기대하였던대로 different string 이라는 문자열이 대신 출력될 것입니다.

그렇다면 같음을 비교하는 것 말고 문자열과 숫자의 크고 작음을 비교하는 것도 가능할까요? 이번엔 아래와 같이 명령어를 넣어보겠습니다.

if test lss tess (echo a) else (echo b)
if test lss test (echo a) else (echo b)
if test lss tesu (echo a) else (echo b)

위의 명령들에 대한 수행 결과는 각각 b, b, a 가 됩니다. 프로그래밍 코드 식으로 풀이해보면 compare("test", x) < 0 에 비유할 수 있으며 앞서 본 tess와 test는 test보다 앞서거나 동일하므로 else 절의 문장이 수행된 것입니다. 반면 tesu는 test보다 뒤에 오는 단어이므로 if 절의 문장이 수행된 것입니다.

만약 위의 예제에서 lss 대신 leq를 선택한다면 test와 test를 비교하는 부분에서 b 대신 a가 선택 될 것입니다. 프로그래밍 코드 식으로 풀이한다면 compare("test", "test") <= 0 가 되므로 참에 해당되기 때문입니다. 더불어, 위의 연산 식 역시 /i 스위치와 not 연산자의 적용을 그대로 유지할 수 있으므로 이들 옵션이 위의 식에 대하여 끼치는 영향은 동일합니다.

프로그래밍 코드 식으로 compare("test", x) > 0 연산에 해당되는 것은 gtr 연산자이며, compare("test", x) >= 0 연산에 해당되는 것은 geq 연산자가 될 것입니다.

지금 이야기한 기능들은 단독으로 사용될 때 보다는 배치 파일에 주어지는 매개 변수들이나 미리 설정된 환경 변수들의 값을 다루는 데에 더 요긴하게 사용됩니다.

다음 Chapter에서는 if / else 문을 이용하여 파일이나 디렉터리의 존재 여부를 파악하는 방법, 환경 변수의 정의 여부를 파악하고 그 값을 파악하는 방법, 프로그램이나 배치 파일의 수행 결과를 if 문으로 파악하는 방법 등을 다뤄보기로 하겠습니다. :-)

크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by Windows Azure MVP 남정현 (rkttu.com)