프로세스 간 통신
프로세스 간 통신 (Inter-process communication)
프로그램을 짜다 보면 다른 프로그램을 실행하고, 그 프로그램에 정보를 넘기고, 실행 결과나 종료 상태까지 받아와야 하는 일이 생겨요. Raku에서는 다른 언어보다 이 작업이 훨씬 단순한데요. 이 문서에서 Raku가 다른 프로세스와 소통하는 방법을 하나씩 살펴볼게요.
본문
프로그램 실행하기 (Running programs)
Raku에서 다른 프로그램을 실행하는 일은 생각보다 아주 간단해요. 이 한 줄이면 git 프로그램을 실행하면서 명령줄에 git과 status를 넘겨요.
run 'git', 'status';
이 줄은 git이라는 이름의 프로그램을 실행하면서 그 프로그램의 명령줄에 git과 status 두 인자를 전달해요. 프로그램을 찾을 때는 %*ENV<PATH> 설정을 따라요.
프로그램을 셸에 명령줄을 보내는 방식으로 실행하고 싶다면 그 용도의 도구도 있어요. 파이프, 리다이렉트, 환경 변수 치환 같은 셸 메타 문자가 모두 셸에서 해석돼요.
shell 'ls -lR | gzip -9 > ls-lR.gz';
다만 shell은 사용자 입력과 함께 쓸 때 주의해야 해요. 입력값이 그대로 셸 명령으로 해석되니까요.
Proc 객체
run과 shell 둘 다 Proc 객체를 반환해요. 이 객체를 쓰면 그 프로세스와 좀 더 세밀하게 소통할 수 있어요. 한 가지 꼭 기억할 점은, 모든 출력 파이프를 닫지 않으면 프로그램이 보통 종료되지 않는다는 거예요. 아래 예시처럼 출력 파이프를 읽고 나서 반드시 닫아줘야 해요.
my $git = run 'git', 'log', '--oneline', :out;
for $git.out.lines -> $line {
my ($sha, $subject) = $line.split: ' ', 2;
say "$subject [$sha]";
}
$git.out.close();
실행한 프로그램이 실패하면, 즉 0이 아닌 종료 코드로 끝나면, 반환된 Proc 객체가 sink될 때 예외를 던져요. 그 sink를 막으려면 객체를 변수에 담아두면 돼요. 익명 변수여도 괜찮아요.
$ = run '/bin/false'; # Proc을 sink하지 않으므로 예외를 던지지 않음
Proc 객체에 :out과 :err 플래그를 넘기면 출력을 파일핸들로 잡아둘 수 있어요. 입력은 :in 플래그로 넘길 수 있고요. 아래 예시는 echo의 출력을 cat의 입력으로 연결하는 모습이에요.
my $echo = run 'echo', 'Hello, world', :out;
my $cat = run 'cat', '-n', :in($echo.out), :out;
say $cat.out.get;
$cat.out.close();
Proc으로 PID를 얻어내거나, 프로그램에 신호를 보내거나, 종료 코드를 확인할 수도 있어요.
my $crontab = run 'crontab', '-l';
if $crontab.exitcode == 0 {
say 'crontab -l ran ok';
}
else {
say 'something went wrong';
}
셸을 쓰는 예시 (Example with shell)
STDIN에서 입력을 받아 STDOUT으로 출력해주는 유틸리티가 있는데 아직 Raku 래퍼가 없는 상황을 생각해볼게요. 이 예시에서는 SCSS라는 좀 더 편한 형태의 CSS를 표준 CSS로 변환해주는 sass를 쓸게요. 터미널에서는 sass --stdin처럼 사용하는 유틸리티예요.
우리가 원하는 건 SCSS 문자열 여러 개를 CSS로 변환하는 거예요. 아래 코드는 문자열 하나만 변환해 출력을 변수에 담지만, 이걸 반복문으로 확장하는 방법은 금방 눈에 보일 거예요.
#| 유틸리티로 프로세스 시작, 먼저 환경에 존재하는지 확인
my $proc = shell( <<sass --version>>, :out, :merge);
exit note 'Cannot run sass' unless $proc.out.slurp(:close) ~~ / \d \. \d+ /;
# 여러 SCSS 문자열을 처리하려면 여기서부터 반복문 시작
# stdin에 대한 프로세스를 설정
$proc = shell( <<sass --stdin --style=compressed>>, :in, :err, :out );
my $scss = q:to/SCSS/;
div.rendered-formula {
display: flex;
justify-content: space-around;
align-items: center;
img.logo {
align-self: center;
}
}
SCSS
# 이제 SCSS 문자열을 STDIN으로 프로세스에 파이프 (파이프를 닫는 것을 잊지 말 것)
$proc.in.spurt($scss,:close);
# 출력이나 오류를 추출
my $error = $_ with $proc.err.slurp(:close);
my $output = $_ with $proc.out.slurp(:close);
# 반복문은 여기서 끝
Proc::Async 객체
다른 프로세스와의 소통을 더 세밀하게 제어하고 싶다면 Proc::Async를 써야 해요. 이 클래스는 프로그램과의 비동기 통신을 지원하고, 프로그램에 신호를 보내는 기능도 있어요.
# 프로그램 실행 준비
my $log = Proc::Async.new('tail', '-f', '/var/log/system.log');
$log.stdout.tap(-> $buf { print $buf });
$log.stderr.tap(-> $buf { $*ERR.print($buf) });
# 프로그램 시작
my $done = $log.start;
sleep 10;
# 프로그램에 멈추라고 지시
$log.kill('QUIT');
# 프로그램이 끝날 때까지 대기
await $done;
이 작은 프로그램은 tail 프로그램으로 system.log 로그 내용을 10초 동안 출력하다가 QUIT 신호로 프로그램을 멈추게 해요.
Proc이 출력에 IO::Handle로 접근한다면, Proc::Async는 비동기 Supply로 접근해요 (Supply 문서를 참고하세요).
실행한 프로그램이 끝나길 기다리면서 그 사이에 다른 작업을 하고 싶다면, start 루틴이 반환하는 Promise를 이용하면 돼요. 그 Promise는 프로그램이 종료되면 fulfilled 상태가 돼요.
프로그램에 데이터를 넘길 때는 write 메서드를 사용해요.