Open3
Open3
Open3 모듈은 자식 프로세스를 만들 때, 그 프로세스의 $stdin, $stdout, $stderr 스트림에 접근할 수 있게 해 주는 모듈이에요. 명령을 실행하고 그 표준 입출력을 파이프로 잡아내는 작업이 이 모듈의 핵심이죠.
출처: Ruby 4.0 API
본문
무엇이 있나요(What's Here)
아래 메서드들은 각각 주어진 명령을 새 프로세스나 서브셸에서 실행해요. 목적에 따라 셋으로 나눌 수 있어요.
- 단일 명령을 실행하고 문자열을 돌려받는 메서드
Open3.capture2: 명령을 실행하고$stdout의 문자열을 돌려줘요.Open3.capture2e: 명령을 실행하고 합쳐진$stdout+$stderr문자열을 돌려줘요.Open3.capture3: 명령을 실행하고$stdout,$stderr문자열을 돌려줘요. (stdin 입력은stdin_data옵션으로 전달)
- 단일 명령을 실행하고 파이프를 돌려받는 메서드
Open3.popen2: 명령을 실행하고$stdin,$stdout용 파이프를 돌려줘요.Open3.popen2e: 명령을 실행하고$stdin과 합쳐진$stdout+$stderr용 파이프를 돌려줘요.Open3.popen3: 명령을 실행하고$stdin,$stdout,$stderr용 파이프를 돌려줘요.
- 하나 이상의 명령을 파이프라인으로 실행하는 메서드
Open3.pipeline_r: 마지막$stdout용 파이프를 돌려줘요.Open3.pipeline_rw: 첫$stdin과 마지막$stdout용 파이프를 돌려줘요.Open3.pipeline_w: 첫$stdin용 파이프를 돌려줘요.Open3.pipeline_start: 프로세스가 끝나길 기다리지 않아요.Open3.pipeline: 프로세스가 끝나길 기다려요.
위 메서드들은 모두 다음 인자를 받아요 (대부분 선택적).
- 환경 변수 이름·값의 선택적 해시(실행 환경(Execution Environment) 참고)
- 명령줄(
command_line)이나 실행 파일 경로(exe_path)인 필수 문자열 인자 - 실행 옵션의 선택적 해시(실행 옵션(Execution Options) 참고)
공통 인자 규칙
거의 모든 메서드에서 두 가지 인자 규칙이 반복돼요. 한번 정리해 둘게요.
env: 첫 인자가 해시이면Process.spawn(또는popen3)을 호출할 때 맨 앞 인자env가 돼요. 실행 환경을 참고하세요.options: 마지막 인자가 해시이면 뒤쪽 인자options가 돼요. 실행 옵션을 참고하세요.command_linevsexe_path: 단일 필수 문자열 인자는, 셸 예약어(reserved word)나 특수 내장(special built-in)으로 시작하거나 메타문자를 하나 이상 포함하면command_line으로, 그 외에는exe_path로 취급돼요.command_line: 셸에 전달할 명령줄이에요. 셸 예약어로 시작하거나, 특수 내장으로 시작하거나, 메타문자를 포함해야 해요.Open3.capture2('date > date.tmp')처럼 메타문자>가 있으면 셸이 처리해요.exe_path: 실행할 실행 파일의 경로 문자열이거나, 실행 파일 경로와 프로세스 이름으로 쓸 문자열을 담은 2-요소 배열이에요. Ruby가 셸 없이 직접 실행 파일을 호출해요. 셸 확장이 일어나지 않죠. 그래서 없는 명령은Errno::ENOENT를 발생시켜요. 추가 인자는 실행 파일에 그대로 전달돼요.
보안 경고: Process.spawn처럼 이 메서드들도 신뢰할 수 없는 입력과 함께 호출하면 명령 주입(command injection) 보안 취약점이 있을 수 있어요. 다만 Process.spawn과 달리 이 메서드들은 자식 프로세스가 종료할 때까지 기다렸다가 돌아오므로, 호출자가 따로 기다릴 필요가 없어요.
Public Class Methods
capture2([env, ] command_line, options = {}) → [stdout_s, status] / capture2([env, ] exe_path, *args, options = {}) → [stdout_s, status]
Open3.popen3의 래퍼예요. 자식 프로세스를 만들고 그 표준 출력을 문자열 stdout_s로, 종료 상태를 Process::Status 객체 status로 돌려줘요. 배열 [stdout_s, status]를 반환해요.
stdout_s, status = Open3.capture2('echo "Foo"')
# => ["Foo\n", #<Process::Status: pid 2326047 exit 0>]
options 해시에서 이 메서드에 국한된 두 옵션이 있어요.
options[:stdin_data]가 있으면 그 항목을 제거하고, 그 문자열 값을 명령의 표준 입력으로 보내요.Open3.capture2('tee', stdin_data: 'Foo') # => ["Foo", ...]options[:binmode]가 있으면 그 항목을 제거하고 내부 스트림을 이진 모드로 설정해요.
capture2e([env, ] command_line, options = {}) → [stdout_and_stderr_s, status] / capture2e([env, ] exe_path, *args, options = {}) → [stdout_and_stderr_s, status]
capture2와 같은 구조지만, 자식 프로세스의 표준 출력과 표준 오류가 합쳐진(merged) 문자열을 돌려줘요. 배열 [stdout_and_stderr_s, status]를 반환해요.
stdout_and_stderr_s, status = Open3.capture2e('echo "Foo"')
# => ["Foo\n", #<Process::Status: pid 2371692 exit 0>]
stdin_data/binmode 옵션 처리와 공통 인자 규칙은 capture2와 같아요.
capture3([env, ] command_line, options = {}) → [stdout_s, stderr_s, status] / capture3([env, ] exe_path, *args, options = {}) → [stdout_s, stderr_s, status]
capture2와 같은 구조로, 표준 출력과 표준 오류를 따로따로 문자열로 돌려줘요. 배열 [stdout_s, stderr_s, status]를 반환해요.
stdout_s, stderr_s, status = Open3.capture3('echo "Foo"')
# => ["Foo\n", "", #<Process::Status: pid 2281954 exit 0>]
stdin_data/binmode 옵션 처리와 공통 인자 규칙은 capture2와 같아요.
pipeline([env, ] *cmds, options = {}) → array_of_statuses
Process.spawn의 래퍼예요. 주어진 각 cmds에 대해 Process.spawn으로 자식 프로세스를 만들고, 각 자식의 stdout을 다음 자식의 stdin으로, 마지막 자식의 stdout은 호출자의 stdout으로 연결해요. 자식 프로세스들이 종료할 때까지 기다리고, 각 자식의 Process::Status 객체 배열을 돌려줘요.
wait_threads = Open3.pipeline('ls', 'grep R')
# => [#<Process::Status: pid 2139200 exit 0>, #<Process::Status: pid 2139202 exit 0>]
cmds의 각 남은 인자는 셋 중 하나예요.
command_line: 셸 예약어나 특수 내장으로 시작하거나 메타문자를 포함하는 문자열exe_path: 실행할 실행 파일의 경로 문자열- 배열:
command_line또는exe_path와, 명령에 전달할 문자열 인자 0개 이상
pipeline_r([env, ] *cmds, options = {}) → [last_stdout, wait_threads]
Process.spawn의 래퍼예요. 각 자식의 stdout을 다음 자식의 stdin으로, 마지막 자식의 stdout은 호출자의 stdout으로 연결해요. 이 메서드는 자식 프로세스가 끝나길 기다리지 않으므로 호출자가 기다려야 해요.
블록이 없으면 2-요소 배열을 돌려줘요.
- 마지막 자식 프로세스의 stdout 스트림
- 모든 자식 프로세스의 대기 스레드 배열
last_stdout, wait_threads = Open3.pipeline_r('ls', 'grep R')
puts last_stdout.read
wait_threads.each do |wait_thread|
wait_thread.join
end
블록이 주어지면 마지막 자식의 stdout 스트림과 대기 스레드 배열로 블록을 호출해요.
Open3.pipeline_r('ls', 'grep R') do |last_stdout, wait_threads|
puts last_stdout.read
wait_threads.each do |wait_thread|
wait_thread.join
end
end
pipeline_rw([env, ] *cmds, options = {}) → [first_stdin, last_stdout, wait_threads]
Process.spawn의 래퍼예요. 각 자식의 stdout을 다음 자식의 stdin으로, 첫 자식의 stdin은 호출자의 stdin에서, 마지막 자식의 stdout은 호출자의 stdout으로 연결해요. 자식 프로세스가 끝나길 기다리지 않으므로 호출자가 기다려야 해요.
블록이 없으면 3-요소 배열을 돌려줘요.
- 첫 자식 프로세스의 stdin 스트림
- 마지막 자식 프로세스의 stdout 스트림
- 모든 자식 프로세스의 대기 스레드 배열
first_stdin, last_stdout, wait_threads = Open3.pipeline_rw('sort', 'cat -n')
first_stdin.puts("foo\nbar\nbaz")
first_stdin.close # Send EOF to sort.
puts last_stdout.read
wait_threads.each do |wait_thread|
wait_thread.join
end
블록이 주어지면 첫 자식의 stdin, 마지막 자식의 stdout, 대기 스레드 배열로 블록을 호출해요.
pipeline_start([env, ] *cmds, options = {}) → [wait_threads]
Process.spawn의 래퍼예요. 각 자식에 대해 프로세스를 만들고, 자식 프로세스가 끝나길 기다리지 않아요. 블록이 없으면 모든 자식 프로세스의 대기 스레드 배열을 돌려줘요. 블록이 주어지면 대기 스레드 배열로 블록을 호출해요.
pipeline_w([env, ] *cmds, options = {}) → [first_stdin, wait_threads]
Process.spawn의 래퍼예요. 각 자식의 stdout을 다음 자식의 stdin으로, 첫 자식에는 호출자의 stdout을 자식의 stdin으로 연결해요. 자식 프로세스가 끝나길 기다리지 않으므로 호출자가 기다려야 해요.
블록이 없으면 2-요소 배열을 돌려줘요.
- 첫 자식 프로세스의 stdin 스트림
- 모든 자식 프로세스의 대기 스레드 배열
블록이 주어지면 첫 자식의 stdin 스트림과 대기 스레드 배열로 블록을 호출해요.
popen2([env, ] command_line, options = {}) → [stdin, stdout, wait_thread] / popen2([env, ] exe_path, *args, options = {}) → [stdin, stdout, wait_thread] / popen2(...) {|stdin, stdout, wait_thread| ... } → object
Process.spawn의 래퍼예요. 자식 프로세스를 만들고 표준 입력 stdin, 표준 출력 stdout 스트림과, 자식 프로세스가 종료되길 기다리는 스레드 wait_thread를 만들어요. wait_thread는 자식 프로세스의 PID를 돌려주는 pid 메서드를 가져요.
블록이 없으면 배열 [stdin, stdout, wait_thread]를 돌려줘요. 호출자는 두 스트림을 닫아야 해요.
stdin, stdout, wait_thread = Open3.popen2('echo')
stdin.close
stdout.close
wait_thread.pid # => 2274600
wait_thread.value # => #<Process::Status: pid 2274600 exit 0>
블록이 주어지면 세 변수(두 스트림과 대기 스레드)로 블록을 호출하고 블록의 반환 값을 돌려줘요. 호출자가 스트림을 닫을 필요는 없어요. 관련 메서드로는 표준 오류에 접근하지 않는 popen2(이 메서드), 두 스트림을 분리해 주는 popen2e, 세 스트림을 모두 분리해 주는 popen3이 있어요.
popen2e([env, ] command_line, options = {}) → [stdin, stdout_and_stderr, wait_thread] / popen2e([env, ] exe_path, *args, options = {}) → [stdin, stdout_and_stderr, wait_thread] / popen2e(...) {|stdin, stdout_and_stderr, wait_thread| ... } → object
popen2와 같은 구조로, 자식의 표준 입력 stdin과 표준 출력·표준 오류가 합쳐진 stdout_and_stderr 스트림을 만들어요. 블록 없이는 배열 [stdin, stdout_and_stderr, wait_thread]를, 블록과 함께면 네 블록 변수로 호출해요.
popen3([env, ] command_line, options = {}) → [stdin, stdout, stderr, wait_thread] / popen3([env, ] exe_path, *args, options = {}) → [stdin, stdout, stderr, wait_thread] / popen3(...) {|stdin, stdout, stderr, wait_thread| ... } → object
popen2와 같은 구조로, 자식의 표준 입력 stdin, 표준 출력 stdout, 표준 오류 stderr 스트림을 모두 분리해서 만들어요. 블록 없이는 배열 [stdin, stdout, stderr, wait_thread]를, 블록과 함께면 네 변수로 블록을 호출해요.
교착 상태(deadlock)를 조심하세요. stdout과 stderr 출력 스트림은 고정 크기 버퍼라서, 한쪽만 많이 읽고 다른 쪽은 안 읽으면 읽지 않은 버퍼가 가득 차면서 교착 상태가 날 수 있어요. 이를 피하려면 stdout과 stderr를 동시에(스레드나 IO.select로) 읽어야 해요.
관련 메서드: popen2(표준 오류 제외, stdin·stdout 분리), popen2e(stdin과 합쳐진 stdout+stderr 분리).
Constants
VERSION
버전 문자열