-
- 1. 번역: 펄의 system() 함수 사용하기 ( Using the Perl system() function )
-
-
- 1.1. system() 기초
-
- 1.2. system() 함수의 두 번째 형태
-
- 1.3. system() 함수의 세 번째 형태
-
- 1.4. system() 함수는 정확히 무엇을 반환하는가?
-
- 1.5. 특별히 주의해야 할 점
-
- 1.6. 개인적인 스타일 취향
-
- 1.7. system() 테마의 변주
-
2. 기타 & comments
-
1. 번역: 펄의 system() 함수 사용하기 ( Using the Perl system() function )
(원문: [Using the Perl system() function])
여러 매뉴얼들에서 펄의 system() 함수를 조금씩 다르게 설명하며, 중요한 부분을 빼먹곤 한다. 이 글은 내가 직접 펄 system() 함수를 문서화하고 추가로 몇 가지 용례와 잘못된 사용 예를 추가하려고 만들었다.
1.1. system() 기초
system() 함수는 자식 프로세스를 생성하고(fork) 운영 체제 명령문을 실행한 후, 자식 프로세스가 완료될 때까지 대기하고, 일종의 자식 프로세스 종료 상태값을 반환하여 여러분이 검사할 수 있도록 한다.
system() 함수의 인자는 세 가지 형태로 주어질 수 있다 (괄호는 생략 가능하다):
1. system($STRING);
2. system($PROGRAM, @ARGUMENT_LIST);
3. system { $PROGRAM } $FAUX_PROGRAM, @ARGUMENT_LIST;
첫 번째 형태가 가장 흔히 사용된다. 여러분이 어느 형태를 쓸지(또는 펄이 진짜로 사용할지)는 실행하려는 명령문에 셸 메타캐릭터가 들어있는지에 따라 결정된다. 셸 메타캐릭터는 다음과 같은 파이프, 리다이렉트, 구문 구분자 등을 포함한다: ( ps -aux | column 1 > pids ); date
정확히는 다음 문자들이다:
& ; ` ' \ " | * ? ~ < > ^ ( ) [ ] { } $ \n \r
만일 여러분이 실행하려는 명령문에 저 문자들 중 일부가 포함되어 있다면, 첫 번째 형태를 사용하여야 한다. 예를 들면:
system("$tace < $tcmd > $tout");
이 경우 하나의 문자열 인자가 커맨드 셸에 전달된다.
여기서 기억해야 할 중요한 점 하나는, 일단 system() 함수를 사용했다면 여러분이 만든 스크립트는 더 이상 이식성이 있지(portable) 않을 것이라는 점이다. 윈도우와 유닉스 시스템을 오가며 사용하려면 적당히 고쳐야 할 것이다.
그런데 어떤 셸을 쓰는가?
유닉스 같은 운영체제에는 여러 가지 커맨드 셸이 있다: sh, csh, tcsh, bash, zsh 등등. 이 중 어느 것이 사용되는가? /etc/passwd 파일에 정의되어 있는, 이 펄 스크립트를 실행한 사용자의 기본 셸이 사용된다. 여기서 문제의 소지가 있는데, 만일 여러분이 한 명의 사용자로서 스크립트를 개발하였고 이 스크립트를 다른 사용자에게 넘겨주거나, 'root' 등 다른 사용자의 자격으로 시스템 cron 작업으로 등록했다면, system() 함수가 여러분이 사용했던 셸이 아닌 다른 셸에서 실행될 수 있다는 점이다. 셸 내장 명령어의 종류나 $PATH 변수에 등록된 명령어들의 순서가 여러분이 스크립트를 만들 때와 다를 수 있다는 것이다.
여러분이 원하는 셸을 사용하도록 추가적인 셸 실행 계층을 넣어서 이런 문제의 소지를 최소화할 수 있다.
system("/bin/tcsh -c 'diff file file.old > file.diff 2>&1 /dev/null'");
하지만 더 나은 접근법은 이런 것이다:
1. 가능하면 모든 유닉스 셸에 공통적으로 사용되는 셸 연산자들만 사용한다(단순한 리다이렉션, 파이프 등).
2. 언제나 실행하려는 운영체제 명령어의 전체 경로명을 적는다. (이러면 다른 유닉스 시스템으로의 이식성이 떨어지지만, 이것은 보안과 관련하여 양보해야 할 중요한 문제이다.)
이렇게 하지 마라:
system("chown $user.sgd newdata.txt");
다음과 같이 하라:
system("/usr/bin/chown $user:sgd newdata.txt");
system("/usr/ucb/chown $user.sgd newdata.txt");
1.2. system() 함수의 두 번째 형태
system() 함수의 두 번째 형태에서는:
system($PROGRAM, @ARGUMENT_LIST);
셸 메타캐릭터 체크가 이루어지지 않는다. 여러분은 단순히 명령어에 인자 목록을 주어 실행하여야 한다:
system('/usr/bin/cp', $from, $to);
이것은 범용적인 문자열 인자 형태의 system()과 비교했을 때 두 가지 측면에서 효율적일 수 있다. 첫째로, 이 형태는 명령어를 실행하기 위해 셸을 띄우지 않고, 유닉스의 execvp 시스템 콜을 써서 직접 실행한다. 따라서 런타임에 프로세스 오버헤드가 줄어든다. 둘째로, 컴파일타임과 런타임에 인자들을 하나의 문자열로 만들기 위해 문자열 결합을 할 필요가 없다.
펄은 더 빠른 쪽을 선택한다
만일 첫 번째 형태를 사용하였는데 인자에 셸 메타캐릭터가 전혀 들어있지 않다면, 펄은 여러분이 인자로 준 문자열을 공백 문자를 기준으로 분리한 후 두 번째 형태로 바꾸어 실행한다. 이렇게 함으로써 추가로 셸을 띄우지 않도록 한다.
system('mv /etc/group_comb /etc/group') => system('mv', '/etc/group_comb', '/etc/group')
따라서 여러분은 두 번째 형태를 쓰려고 고민할 필요가 없다. 펄이 알아서 하게 두라.
1.3. system() 함수의 세 번째 형태
system() 함수의 세 번째 형태는 다음과 같다:
system { $PROGRAM } $FAUX_PROGRAM, @ARGUMENT_LIST
이것은 두 번째 형태의 변형으로, 펄의 간접 객체 표기 문법을 사용한 것이다. 이렇게 하면 실제 실행되는 명령어와, 'ps'나 'top' 같은 프로그램을 써서 그 명령어의 프로세스 테이블을 들여다볼 때 나타나는 이름을 서로 다르게 할 수 있다:
system { '/bin/sleep' } 'sleeping', 1000;
이 형태를 써먹을 기회는 드물지만, 이 형태가 유용할 수 있는 특별한 케이스가 있다:
system { $args[0] } @args;
이것은 @args 배열의 원소가 여러 개이든, 아니면 한 개뿐이고 그 안에 셸 메타캐릭터가 들어있든 상관없이 강제로 system() 함수가 execvp를 사용하는 형태로 실행되도록 한다.
주인장 주: 예를 들어서,
system { 'ls | less' } 'LISTING';
위와 같이 파이프를 쓰더라도 셸을 따로 띄우지 않고 직접 ls | less
라는 이름의 파일을 찾아서 실행하려고 한다는 것이다. 물론 실제로 그런 이름의 파일이 없으니, 이 실행은 실패한다.
1.4. system() 함수는 정확히 무엇을 반환하는가?
system() 함수는 두 개의 숫자를 하나로 합쳐서 반환한다. 첫 번째 값은 이 명령어를 종료시킨 유닉스 시그널이 있었다면 그 시그널의 번호이다(예를 들어 INT=2, QUIT=3, KILL=9). 이것은 system() 함수의 반환값 중 하위 8비트에 저장된다. 상위 8비트에는 여러분이 실행한 명령어의 종료 코드가 저장된다.
명령어가 실행될 때 아무 문제가 없었는지 확인하는 간단한 방법은 반환값을 '0'과 비교하는 것이다.
다음과 같이 하라:
if (system("$sub_script $user $file") == 0) {
print "success!\n";
}
하지만 이것을 논리 반전 연산에 넣지는 마라:
이렇게 하지 마라:
if (!system("$sub_script $user $file")) {
print "success!\n";
}
저 결과는 숫자가 조합된 값이지 논리적 부울 값이 아니다. 또한 반전되어 있어 혼동의 여지가 있다.
에러에 대한 더 자세한 정보가 필요하다면, 반환값을 쪼개어 확인할 수 있다:
my $status = system("rotate_quickly.pl");
my $signal = $status & 0xff;
my $exit_code = ($status >> 8) & 0xff;
1.5. 특별히 주의해야 할 점
system() 함수와 관련하여, 여러분이 진정 이식성 있는 펄 스크립트를 작성하는 유일한 희망은... 이 함수를 쓰지 않는 것이다.
같은 일을 하는 펄 함수가 있다면 system() 함수를 쓰지 마라
이렇게 하지 마라:
system("rm $dir/orf_geneontology.tmp");
system "mkdir $tempDir";
system("/bin/kill -HUP $pid");
대신 이렇게 하라:
unlink("$dir/orf_geneontology.tmp");
mkdir $tempDir;
$number_killed = kill('HUP', $pid);
같은 일을 하는 펄 모듈이 있다면 system() 함수를 쓰지 마라
이렇게 하지 마라:
system("cp $datafile $cpfile");
system("/usr/bin/mv $tab_file $arcFile");
대신 이렇게 하라:
use File::Copy;
copy($datafile, $cpfile);
move($tab_file, $arcFile);
1.6. 개인적인 스타일 취향
여러 옵션 스위치가 들어간 복잡한 명령어를 쓰는데 셸 메타캐릭터는 들어가지 않는 경우라면, 나는 system() 함수의 두 번째 형태를 쓰면서 펄의 =>
연산자를 조합하는 것을 선호한다:
system('/gnu/cvs', '-d' => '/share/cvs', 'commit', '-m' => "'$dstr'", "$cvsdir/genes.txt");
이렇게 쓰는 것은 일부 옵션은 앞에서 미리 결정하고 일부 옵션은 뒤에 가서 정해지는 경우에 빛을 발한다:
my @backup_save_command = ("$oracle/nsr_backup", '-b' => $backup_pool, '-g' => $backup_group);
...
system(@backup_save_command, '-f' => $source_file);
1.7. system() 테마의 변주
system() 함수 테마곡에는 몇 가지 변주곡이 존재한다. 여러분도 알고 있어야 하지만 여기서 자세히 다루지는 않겠다.
역따옴표 또는 qx//
역따옴표 ` `
는 system()과 비슷하지만 출력 결과를 반환한다:
chomp(my date = `/usr/bin/date`);
my $ps = `/usr/bin/ps -aux`; # ps의 출력 전체를 하나의 문자열로 얻는다
system() 호출의 출력 결과를 얻어야 하고 종료 코드값은 필요가 없을 때는 이것을 써야 한다.
파이프를 쓰는 open()
그 다음 변주는 파이프를 사용하는 `open()` 함수이다.
이렇게 하라:
open(PS, "/usr/bin/ps -aux |");
while (<PS>) { print } # ps의 출력을 한 번에 한 줄씩 처리한다
close(PS);
주인장 주:
최신 버전의 펄은 3인자 open을 쓰는 것을 권장한다.
open my $ps, "-|", "/usr/bin/ps -aux";
while (<$ps>) { print }
close $ps;
일반적으로 출력을 임시 파일에 저장하고 그것을 다시 읽는 것보다는 위와 같이 하는 게 낫다.
이렇게 하지 마라:
my $temporary_file = "/tmp/ps_output$$";
system("/usr/bin/ps -aux > $temporary_file");
open(PS, $temporary_file);
while (<PS>) { print } # ps의 출력을 한 번에 한 줄씩 처리한다
close(PS);
unlink($temporary_file);
exec() 함수
system() 함수의 문서에서 불만스러운 점은 종종 이 함수를 fork를 동반한 exec() 함수라고 묘사하고는 많은 부분을 exec() 함수의 문서를 참고하라고 하는 것이다. 실제로는 대부분의 펄 프로그래머들은 exec()보다 system() 함수를 열 배 백 배 더 많이 사용하는데 말이다.
exec() 함수는 system() 함수와 유사하지만 절대로 리턴하지 않는다. 그 대신 제어 흐름을 새로 실행한 명령어에 완전히 넘겨버린다. 이 함수는 system()과 동일한 인자 형태를 사용한다. exec()은 보통 매우 짧은 스크립트에 쓰여서, 유닉스 실행 환경을 특정하게 변경한 후 그 변경된 환경에서 어떤 명령어를 실행하는 용도로 사용된다:
#!/usr/bin/perl -w
$ENV{LD_LIBRARY_PATH} = "$ENV{LD_LIBRARY_PATH}:/non_standard/lib";
exec ${0}_original, @ARGV;
exec() 함수는 절대 리턴하는 일이 없으므로 이 함수 아래에 다른 펄 구문이 적혀 있는 것은 오류로 간주된다. 다만 exec() 호출이 어떤 이유에선가(예를 들어 해당 명령을 실행할 권한이 없거나 하여) 실패할 경우에 대비하여 die, warn, 또는 exit를 부르는 경우는 예외이다.
exec('netscape');
die "oops, nobody installed netscape on this system";
2. 기타 & comments
컴퓨터분류