exec — 하위 프로세스 호출하기
exec — 하위 프로세스 호출하기
Tcl 스크립트에서 외부 프로그램을 실행해야 할 때가 많아요. 명령어 결과를 받아오거나, 파이프라인을 구성해 프로그램들을 이어붙이거나, 파일을 리다이렉트하고 싶을 때요. 그때 쓰는 명령이 exec예요.
출처: Tcl 공식 문서 — exec
본문
시그니처
exec ?switches? arg ?arg ...? ?&?
설명
이 명령은 인자들을 실행할 하위 프로세스 하나 이상의 명세로 취급해요. 인자들은 표준 셸 파이프라인 형태를 띠는데, 각 arg는 명령의 한 단어가 되고 각각의 개별 명령은 하위 프로세스가 돼요. 명령의 결과는 파이프라인 마지막 하위 프로세스의 표준 출력을 시스템 인코딩으로 해석한 것이에요. 다른 인코딩(특히 이진 데이터)을 쓰려면 파이프라인을 명시적으로 열고 구성하고 읽어야 해요.
exec의 첫 인자들이 -로 시작하면 명령줄 스위치로 취급되고 파이프라인 명세의 일부가 아니에요. 현재 지원되는 스위치는 다음과 같아요.
-ignorestderr
exec 명령이 파이프라인의 표준 에러 채널로 보내진 메시지 출력을 에러 케이스로 취급하지 않게 해요.
-keepnewline
파이프라인 출력의 끝에 있는 개행을 유지해요. 보통은 끝 개행이 삭제돼요.
--
스위치의 끝을 나타내요. 이 다음 인자는 -로 시작해도 첫 arg로 취급돼요.
인자(또는 인자 쌍)가 아래 설명된 형태 중 하나면 exec가 하위 프로세스 사이의 입출력 흐름을 제어하는 데 써요. 그런 인자는 하위 프로세스에 전달되지 않아요. "< fileName" 같은 형태에서 fileName은 "<"와 별개 인자로 주거나 공백 없이 같은 인자("<fileName")로 줄 수 있어요.
|
파이프라인에서 개별 명령을 구분해요. 앞 명령의 표준 출력이 다음 명령의 표준 입력으로 파이프돼요.
|&
파이프라인에서 개별 명령을 구분해요. 앞 명령의 표준 출력과 표준 에러가 모두 다음 명령의 표준 입력으로 파이프돼요. 이 리다이렉트 형태는 2>·>& 같은 형태를 덮어써요.
< fileName
fileName이 가리키는 파일을 열어 파이프라인의 첫 명령의 표준 입력으로 써요.
<@ fileId
fileId는 open 같은 이전 호출의 반환값 같은 열려 있는 파일의 식별자여야 해요. 파이프라인의 첫 명령의 표준 입력으로 쓰여요. fileId는 읽기용으로 열려 있어야 해요.
<< value
value가 첫 명령의 표준 입력으로 전달돼요.
> fileName
마지막 명령의 표준 출력이 fileName 파일로 리다이렉트되고, 이전 내용은 덮어써져요.
2> fileName
파이프라인 모든 명령의 표준 에러가 fileName 파일로 리다이렉트되고, 이전 내용은 덮어써져요.
>& fileName
마지막 명령의 표준 출력과 모든 명령의 표준 에러가 fileName 파일로 리다이렉트되고, 이전 내용은 덮어써져요.
>> fileName
마지막 명령의 표준 출력이 fileName 파일로 리다이렉트되고, 덮어쓰지 않고 이어 붙여져요.
2>> fileName
파이프라인 모든 명령의 표준 에러가 fileName 파일로 리다이렉트되고, 이어 붙여져요.
>>& fileName
마지막 명령의 표준 출력과 모든 명령의 표준 에러가 fileName 파일로 리다이렉트되고, 이어 붙여져요.
>@ fileId
fileId는 열려 있는 파일의 식별자여야 해요. 마지막 명령의 표준 출력이 fileId의 파일로 리다이렉트되고, 그 파일은 쓰기용으로 열려 있어야 해요.
2>@ fileId
fileId는 열려 있는 파일의 식별자여야 해요. 파이프라인 모든 명령의 표준 에러가 fileId의 파일로 리다이렉트돼요. 그 파일은 쓰기용으로 열려 있어야 해요.
2>@1
파이프라인 모든 명령의 표준 에러가 명령 결과로 리다이렉트돼요. 이 연산자는 명령 파이프라인의 끝에서만 유효해요.
>&@ fileId
fileId는 열려 있는 파일의 식별자여야 해요. 마지막 명령의 표준 출력과 모든 명령의 표준 에러가 fileId의 파일로 리다이렉트돼요. 그 파일은 쓰기용으로 열려 있어야 해요.
결과와 에러 처리
표준 출력이 리다이렉트되지 않았으면 exec 명령은 파이프라인 마지막 명령의 표준 출력을 반환해요. 다만 "2>@1"이 지정되면 표준 에러도 포함돼요. 파이프라인 중 어떤 명령이 비정상 종료하거나 죽거나 멈추면 exec는 에러를 반환하고, 에러 메시지에는 파이프라인의 출력 다음에 비정상 종료를 설명하는 에러 메시지들이 들어가요. -errorcode 반환 옵션은 마지막 비정상 종료에 대한 추가 정보를 담아요.
어떤 명령이 표준 에러에 쓰면서 그 표준 에러가 리다이렉트되지 않고 -ignorestderr가 지정되지 않았다면 exec는 에러를 반환해요. 에러 메시지에는 파이프라인의 표준 출력, 비정상 종료 메시지(있다면), 그다음 표준 에러 출력이 포함돼요.
결과나 에러 메시지의 마지막 문자가 개행이면 그 문자는 보통 결과·에러 메시지에서 삭제돼요. 이것은 보통 개행으로 끝나지 않는 다른 Tcl 반환값과 일관돼요. 다만 -keepnewline이 지정되면 끝 개행이 유지돼요.
표준 입력이 "<", "<<", "<@"로 리다이렉트되지 않았으면 파이프라인 첫 명령의 표준 입력은 응용 프로그램의 현재 표준 입력에서 가져와요.
마지막 인자가 "&"이면 파이프라인은 백그라운드로 실행돼요. 이 경우 exec 명령은 파이프라인의 모든 하위 프로세스 프로세스 식별자를 요소로 한 목록을 반환해요. 마지막 명령의 표준 출력은 리다이렉트되지 않았으면 응용 프로그램의 표준 출력으로 가고, 모든 명령의 에러 출력은 리다이렉트되지 않았으면 응용 프로그램의 표준 에러 파일로 가요.
각 명령의 첫 단어는 명령 이름으로 취급돼요. 그것에 틸드-치환이 수행되고, 결과에 슬래시가 없으면 PATH 환경 변수의 디렉터리들이 그 이름의 실행 파일을 찾는 데 탐색돼요. 이름에 슬래시가 있으면 현재 디렉터리에서 도달 가능한 실행 파일을 가리켜야 해요. 명령 인자에는 "glob" 확장이나 다른 셸-류 치환이 수행되지 않아요.
이식성 이슈
Windows (모든 버전)
"@ fileId" 표기법으로 소켓에서 읽거나 쓰는 것은 동작하지 않아요. 소켓에서 읽을 때 16비트 DOS 응용 프로그램은 멈추고 32비트 응용 프로그램은 파일 끝과 함께 즉시 반환해요. 어느 쪽 응용 프로그램이든 소켓에 쓰면 그 정보는 콘솔이 있으면 콘솔로 보내지거나 버려져요.
Windows의 현재 인자 이스케이프/인용은 CommandLineToArgv, CRT 라이브러리 등을 쓰는 실행 파일과 Windows 배치 파일에만 동작해요(개행은 아래 참고). 흔한 이스케이프 알고리즘이긴 하지만, 실제로 결정적인 것은 실행 파일이 명령줄을 파싱(개별 인자로 분리)하는 방식이에요.
아쉽게도 배치 파일(.cmd 또는 .bat)이나 명령 프로세서(cmd.exe /c)에는 인자 안에 개행 문자를 공급할 방법이 현재 없어요. 첫 개행 문자에서 명령줄(과 인자 체인)이 잘리기 때문이에요. 하지만 실행 파일(CommandLineToArgv 등을 쓰는)에는 제대로 동작해요.
인자 인용 — exec 명령의 인자는 호출되는 프로그램의 인자에 매핑돼요. 예상되면 인자 주위에 추가 인용 문자(")가 자동으로 추가돼요. 특수 문자가 백슬래시 문자를 삽입해 이스케이프돼요.
MS-Windows 환경은 인자에 언급된 프로그램과 호출된 배치 파일(conspec)을 실행하며 환경 변수를 치환하는데, 이는 부작용(취약점)을 만들거나 기존 인용을 깨뜨릴 수 있어요(예: 환경 변수에 " 같은 특수 문자가 포함된 경우). 예:
% exec my-echo.cmd {test&whoami}
test
mylogin
% exec my-echo.cmd "ENV X:%X%"
ENV X: CONTENT OF X
하위 프로그램 실행을 피하기 위해 모든 인자 항목에 다음 포매팅이 자동 수행돼요: 특수 문자(&, |, ^, <, >, !, (, ), %)를 포함한 특수 문자 인자는 자동으로 인용 부호(")로 감싸져요. 데이터 인용부호는 백슬래시 문자 삽입으로 이스케이프돼요.
"%var%"를 쓰는 환경 변수의 자동 해결은 중요하지만 유용성이 위험보다 크며 이스케이프되지 않아요.
TCL 8.6.10은 데이터 인용부호 인용과 %의 개별 인용을 추가해 이 인용을 다듬었어요. 이는 환경 변수 치환 기능에 의존하는 기존 스크립트를 깨뜨릴 수 있어요. 그래서 %의 개별 인용은 TCL 8.6.14에서 제거됐는데, 환경 변수가 문제보다 유용하다고 보기 때문이에요. 명령 파라미터가 있는 해결책은 향후 TCL 릴리스에서 구상되고 있어요.
Tk 콘솔 텍스트 위젯은 실제 표준 IO 기능을 제공하지 않아요. Tk에서 표준 입력에서 리다이렉트할 때 모든 응용 프로그램은 즉시 파일 끝을 보고, 표준 출력·표준 에러로 리다이렉트된 정보는 버려져요.
Tcl 명령 인자의 경로 구분자로는 정방향·역방향 슬래시 둘 다 받아들여져요. 응용 프로그램을 실행할 때 그 응용 프로그램에 지정된 경로 이름도 양쪽 슬래시를 경로 구분자로 포함할 수 있어요. 다만 대부분의 Windows 응용 프로그램은 정방향 슬래시가 있는 인자를 옵션 구분자로만 받고, 경로에는 역방향 슬래시만 받는다는 점을 명심하세요. 정방향 슬래시가 경로 구분자로 있는 경로 이름을 지정하는 인자는 자동으로 역방향 슬래시로 변환되지 않아요.
경로에 정방향·역방향 슬래시가 둘 이상 연속하면 네트워크 경로를 가리켜요. 예를 들어 루트 디렉터리 c:/와 서브디렉터리 /windows/system을 단순히 이어붙이면 c://windows/system(슬래시 두 개)이 되는데, 이것은 windows라는 기계의 system이라는 마운트 포인트를 가리키고(c:/는 무시됨) 현재 컴퓨터의 디렉터리를 설명하는 c:/windows/system과 같지 않아요. 경로 구성 요소를 이어붙이려면 file join 명령을 써야 해요.
Win32 콘솔 응용 프로그램에는 두 가지 일반적인 유형이 있어요:
- CLI — CommandLine Interface, 단순 stdio 교환. 예:
netstat.exe. - TUI — Textmode User Interface, 커서 이동·텍스트 색·키·마우스 감지 등에 콘솔 API에 접근하는 응용 프로그램. 예: Windows 2000의
telnet.exe. 이런 응용 프로그램은 Windows 환경에서 흔하지 않지만 존재해요.
wish 아래에서 응용 프로그램을 실행할 때처럼 콘솔이 없으면 exec는 TUI 응용 프로그램에서 잘 동작하지 않아요. 콘솔 응용 프로그램을 숨기고 분리하는 것이 바람직해요. 이것은 exec가 파이프로 통신하려 하므로 설계된 한계예요. TUI 응용 프로그램과 통신할 때는 Expect 확장이 이 문제를 해결해 줘요.
응용 프로그램을 실행하려 할 때 exec는 먼저 지정된 그대로의 이름을 탐색해요. 그다음 순서대로 .com, .exe, .bat, .cmd를 지정된 이름 끝에 붙여 더 긴 이름을 탐색해요. 응용 프로그램 이름의 일부로 디렉터리 이름이 지정되지 않았다면, 응용 프로그램을 찾을 때 다음 디렉터리를 순서대로 자동 탐색해요:
- Tcl 실행 파일이 로드된 디렉터리
- 현재 디렉터리
- Windows 32비트 시스템 디렉터리
- Windows 홈 디렉터리
- path에 나열된 디렉터리
dir·copy 같은 셸 내장 명령을 실행하려면 호출자가 원하는 명령 앞에 "cmd.exe /c "를 붙여야 해요. 내장 명령은 실행 파일로 구현되지 않았기 때문이에요.
Unix (macOS 포함)
exec 명령은 완전히 기능하며 설명한 대로 동작해요.
Unix 예제
간단한 프로그램을 실행하고 그 결과를 얻으려면:
exec uname -a
0이 아닌 결과 다루기
0이 아닌 결과를 반환할 수 있는 프로그램을 실행하려면 exec 호출을 catch로 감싸고, 에러가 있으면 -errorcode 반환 옵션의 내용을 확인해야 해요:
set status 0
if {[catch {exec grep foo bar.txt} results options]} {
set details [dict get $options -errorcode]
if {[lindex $details 0] eq "CHILDSTATUS"} {
set status [lindex $details 2]
} else {
# Some other error; regenerate it to let caller handle
return -options $options -level 0 $results
}
}
이것은 try 명령을 쓰면 더 쉽게 쓸 수 있어요. 특정 종류의 에러를 더 간단히 잡을 수 있기 때문이에요:
try {
set results [exec grep foo bar.txt]
set status 0
} trap CHILDSTATUS {results options} {
set status [lindex [dict get $options -errorcode] 2]
}
인용 인자 다루기
Unix 셸 호출에서 명령을 옮길 때 단일 인용 문자(')가 Tcl에서 특별한 의미가 없다는 사실에 주의해야 해요. 그래서:
awk '{sum += $1} END {print sum}' numbers.list
는 이렇게 옮겨져요:
exec awk {{sum += $1} END {print sum}} numbers.list
글롭(glob) 다루기
셸 글롭을 포함한 호출을 변환한다면, Tcl은 기본적으로 글롭을 처리하거나 여러 인자로 확장하지 않는다는 걸 기억해야 해요. 대신 이렇게 써요:
exec ls -l {*}[glob *.tcl]
사용자 제공 셸 스크립트 조각 다루기
유용한 기법 하나는 스크립트 사용자에게 표준 입력으로 전달될 데이터를 갖고 실행할 셸 스크립트 조각을 지정할 수 있게 하는 거예요. 인쇄에 lpr 프로그램을 쓰는 데 흔한 기법이에요. 가장 간단한 방법은 사용자 스크립트를 사용자의 셸에 넘겨 처리하는 거예요. 다른 언어를 파싱하는 복잡함을 크게 피할 수 있기 때문이에요.
set lprScript [get from user...]
set postscriptData [generate somehow...]
exec $env(SHELL) -c $lprScript << $postscriptData
Windows 예제
사용자가 파일 편집을 끝낼 때까지 기다리지 않고 notepad 인스턴스를 시작하려면:
exec notepad myfile.txt &
notepad로 텍스트 파일을 인쇄하려면:
exec notepad /p myfile.txt
콘솔 프로그램 다루기
컴파일러에서 흔히 그렇듯 프로그램이 다른 프로그램을 호출하면, 가끔 뜨는 콘솔 창을 숨기려면 배치 파일에 의존해야 할 수 있어요:
exec cmp.bat somefile.c -o somefile
cmp.bat 파일 내용이 이렇게 생겼다면:
@gcc %*
또는 단일 파라미터를 쓰는 다른 변형:
@gcc %1 %2 %3 %4 %5 %6 %7 %8 %9
명령 내장 다루기
다른 프로그램이 같은 이름을 가지며 path에 있을 수 있어 주의가 필요해요. DOS 프롬프트에서 명령을 치면 exec로 실행한 같은 명령과 다른 프로그램을 찾는 일이 일어날 수 있어요. exec와 DOS 배치 파일의 동작 차이(문서화됨) 때문이에요.
의심스러우면 auto_execok 명령을 써요. 이것은 exec 명령이 보는 프로그램의 전체 경로를 반환해요. dir 같은 "내부" 명령을 Tcl 스크립트에서 실행하려 할 때 특히 그렇습니다(파일 이름만 나열하려면 glob 명령을 써요). 이렇게 해요:
exec {*}[auto_execok dir] *.tcl
네이티브 파일 이름 다루기
Windows의 많은 프로그램은 파일명 인자를 경로 구분자로 역방향 슬래시로 전달하길 요구해요. 이는 file nativename 명령으로 해요. 예를 들어 (NTFS에서) 현재 사용자만 접근할 수 있도록 디렉터리를 암호화하려면 CIPHER 명령을 써요:
set secureDir "~/Desktop/Secure Directory"
file mkdir $secureDir
exec CIPHER /e /s:[file nativename $secureDir]
더 알아보기
error,file,open- 키워드:
execute,pipeline,redirection,subprocess