Open3 모듈
Open3 모듈
Open3 모듈은 자식 프로세스의 $stdin, $stdout, $stderr 스트림에 접근하면서 자식 프로세스를 만드는 것을 지원해요.
출처: Ruby 3.3 API
본문
여기 있는 것들 (What's Here)
아래 메서드들은 각각 주어진 명령을 새 프로세스나 하위 셸에서 실행하거나, 여러 명령을 새 프로세스와/또는 하위 셸에서 실행해요.
단일 명령을 프로세스/하위 셸에서 실행하고, $stdin에 넣을 문자열을 받아 $stdout, $stderr(또는 둘 다)의 문자열을 반환하는 메서드들:
Open3.capture2: 명령을 실행하고$stdout의 문자열을 반환해요.Open3.capture2e: 명령을 실행하고 합쳐진$stdout/$stderr의 문자열을 반환해요.Open3.capture3: 명령을 실행하고$stdout/$stderr의 문자열을 반환해요.
단일 명령을 프로세스/하위 셸에서 실행하고, $stdin, $stdout, $stderr(중 하나 이상)용 파이프를 반환하는 메서드들:
Open3.popen2: 명령을 실행하고$stdin/$stdout용 파이프를 반환해요.Open3.popen2e: 명령을 실행하고$stdin/합쳐진$stdout/$stderr용 파이프를 반환해요.Open3.popen3: 명령을 실행하고$stdin/$stdout/$stderr용 파이프를 반환해요.
하나 이상의 명령을 프로세스/하위 셸에서 실행하고, 처음 $stdin, 마지막 $stdout(또는 둘 다)용 파이프를 반환하는 메서드들:
Open3.pipeline_r: 마지막$stdout용 파이프를 반환해요.Open3.pipeline_rw: 처음$stdin과 마지막$stdout용 파이프를 반환해요.Open3.pipeline_w: 처음$stdin용 파이프를 반환해요.Open3.pipeline_start: 프로세스가 끝나기를 기다리지 않아요.Open3.pipeline: 프로세스가 끝나기를 기다려요.
위 메서드들은 모두 다음을 인자로 받을 수 있어요:
- 환경 변수 이름/값의 선택적 hash (
env); 실행 환경 참조. command_line또는exe_path인 필수 문자열 인자.- 실행 옵션을 담은 선택적 hash (
options); 실행 옵션 참조.
상수
VERSION
공개 클래스 메서드
capture2([env, ] command_line, options = {}) → [stdout_s, status]
기본적으로 Open3.popen3을 감싼 래퍼예요. 다음을 수행해요.
- 주어진 인자로
Open3.popen3을 호출해 자식 프로세스를 만듭니다 (hashoptions의 특정 항목 제외). - 자식 프로세스의 표준 출력을 문자열
stdout_s로 반환합니다. - 자식 프로세스의 종료 상태를 나타내는
Process::Status객체를status로 반환합니다.
배열 [stdout_s, status]를 반환해요:
stdout_s, status = Open3.capture2('echo "Foo"')
# => ["Foo\n", #<Process::Status: pid 2326047 exit 0>]
Process.spawn처럼, 신뢰할 수 없는 입력으로 호출하면 보안 취약점이 있을 수 있어요. Process.spawn과 달리 이 메서드는 반환 전에 자식 프로세스가 종료되기를 기다려요.
첫 인자가 hash면 Open3.popen3 호출에서 앞의 env 인자가 되고, 마지막 인자가 hash면 Open3.popen3의 options 인자가 돼요.
options hash에서 Open3.capture2에만 국소적으로 영향 주는 두 옵션이 있어요:
options[:stdin_data]항목이 있으면 그 항목을 제거하고 그 문자열 값을 명령의 표준 입력으로 보내요:
Open3.capture2('tee', stdin_data: 'Foo')
# => ["Foo", #<Process::Status: pid 2326087 exit 0>]
options[:binmode]항목이 있으면 그 항목을 제거하고 내부 스트림을 이진 모드로 설정해요.
필수 인자 하나는 다음 중 하나예요:
- 문자열이면서 셸 예약어나 특수 내장으로 시작하거나 메타문자를 하나 이상 포함하면
command_line. - 그 외에는
exe_path.
Argument command_line — 셸에 전달되는 명령줄 문자열로, 셸 예약어로 시작하거나 특수 내장으로 시작하거나 메타문자를 포함해야 해요:
Open3.capture2('if true; then echo "Foo"; fi') # Shell reserved word.
# => ["Foo\n", #<Process::Status: pid 2326131 exit 0>]
Open3.capture2('echo') # Built-in.
# => ["\n", #<Process::Status: pid 2326139 exit 0>]
Open3.capture2('date > date.tmp') # Contains meta character.
# => ["", #<Process::Status: pid 2326174 exit 0>]
명령줄은 명령의 인자와 옵션도 포함할 수 있어요:
Open3.capture2('echo "Foo"')
# => ["Foo\n", #<Process::Status: pid 2326183 exit 0>]
Argument exe_path — 다음 중 하나예요: 호출할 실행 파일의 문자열 경로, 또는 실행 파일 경로와 실행 프로세스 이름으로 쓸 문자열의 2요소 배열.
Open3.capture2('/usr/bin/date')
# => ["Fri Sep 29 01:00:39 PM CDT 2023\n", #<Process::Status: pid 2326222 exit 0>]
Ruby는 셸이나 셸 확장 없이 실행 파일을 직접 호출해요:
Open3.capture2('doesnt_exist') # Raises Errno::ENOENT
args가 하나 이상 주어지면 각각 실행 파일에 전달할 인자나 옵션이에요:
Open3.capture2('echo', 'C #')
# => ["C #\n", #<Process::Status: pid 2326267 exit 0>]
Open3.capture2('echo', 'hello', 'world')
# => ["hello world\n", #<Process::Status: pid 2326299 exit 0>]
capture2e([env, ] command_line, options = {}) → [stdout_and_stderr_s, status]
기본적으로 Open3.popen3을 감싼 래퍼예요. 자식 프로세스의 합쳐진 표준 출력/표준 오류를 문자열로, 종료 상태를 Process::Status로 반환해요.
배열 [stdout_and_stderr_s, status]를 반환해요:
stdout_and_stderr_s, status = Open3.capture2e('echo "Foo"')
# => ["Foo\n", #<Process::Status: pid 2371692 exit 0>]
options[:stdin_data]와 options[:binmode]의 국소 처리는 capture2와 동일해요. command_line/exe_path 인자 구분도 동일해요:
Open3.capture2e('echo', 'hello', 'world')
# => ["hello world\n", #<Process::Status: pid 2371894 exit 0>]
capture3([env, ] command_line, options = {}) → [stdout_s, stderr_s, status]
기본적으로 Open3.popen3을 감싼 래퍼예요. 표준 출력과 표준 오류를 문자열로, 종료 상태를 Process::Status로 반환해요.
배열 [stdout_s, stderr_s, status]를 반환해요:
stdout_s, stderr_s, status = Open3.capture3('echo "Foo"')
# => ["Foo\n", "", #<Process::Status: pid 2281954 exit 0>]
options[:stdin_data] 처리 등은 capture2와 동일해요.
pipeline([env, ] *cmds, options = {}) → array_of_statuses
기본적으로 Process.spawn을 감싼 래퍼예요. 각 cmds에 대해 Process.spawn을 호출해 자식 프로세스를 만들고, 각 자식의 stdout을 다음 자식의 stdin(마지막 자식은 호출자의 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 스트림과 모든 자식의 wait thread 배열의 2요소 배열을 반환해요.
last_stdout, wait_threads = Open3.pipeline_r('ls', 'grep R')
puts last_stdout.read
wait_threads.each { |wait_thread| wait_thread.join }
블록을 주면 마지막 자식의 stdout 스트림과 wait process 배열로 블록을 호출해요.
pipeline_rw([env, ] *cmds, options = {}) → [first_stdin, last_stdout, wait_threads]
Process.spawn을 감싼 래퍼로, 자식들이 종료되길 기다리지 않아요. 블록이 없으면 처음 자식의 stdin 스트림, 마지막 자식의 stdout 스트림, 모든 자식의 wait thread 배열의 3요소 배열을 반환해요.
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 { |wait_thread| wait_thread.join }
출력:
1 bar
2 baz
3 foo
pipeline_start([env, ] *cmds, options = {}) → [wait_threads]
Process.spawn을 감싼 래퍼로, 자식들이 종료되길 기다리지 않아요. 블록이 없으면 모든 자식 프로세스의 wait thread 배열을 반환해요.
wait_threads = Open3.pipeline_start('ls', 'grep R')
wait_threads.each { |wait_thread| wait_thread.join }
pipeline_w([env, ] *cmds, options = {}) → [first_stdin, wait_threads]
Process.spawn을 감싼 래퍼로, 자식들이 종료되길 기다리지 않아요. 블록이 없으면 처음 자식의 stdin 스트림과 모든 자식의 wait thread 배열의 2요소 배열을 반환해요.
first_stdin, wait_threads = Open3.pipeline_w('sort', 'cat -n')
first_stdin.puts("foo\nbar\nbaz")
first_stdin.close # Send EOF to sort.
wait_threads.each { |wait_thread| wait_thread.join }
출력:
1 bar
2 baz
3 foo
popen2([env, ] command_line, options = {}) → [stdin, stdout, wait_thread]
Process.spawn을 감싼 래퍼예요. 자식 프로세스의 표준 입력 스트림 stdin, 표준 출력 스트림 stdout, 그리고 자식이 종료되길 기다리는 thread wait_thread(메서드 pid로 자식의 프로세스 ID를 얻을 수 있어요)를 만들어요. 블록이 없으면 배열 [stdin, stdout, wait_thread]를 반환하고, 호출자가 반환된 두 스트림을 닫아야 해요.
stdin, stdout, wait_thread = Open3.popen2('echo')
stdin.close
stdout.close
wait_thread.pid # => 2263572
wait_thread.value # => #<Process::Status: pid 2263572 exit 0>
블록을 주면 세 변수(두 스트림과 wait thread)로 블록을 호출하고 블록의 반환값을 돌려줘요. 호출자가 스트림을 닫지 않아도 돼요.
command_line 인자:
Open3.popen2('if true; then echo "Foo"; fi') {|*args| p args } # Shell reserved word.
Open3.popen2('echo') {|*args| p args } # Built-in.
Open3.popen2('date > date.tmp') {|*args| p args } # Contains meta character.
exe_path 인자와 추가 인자:
Open3.popen2('/usr/bin/date') { |i, o, t| o.gets }
# => "Thu Sep 28 09:41:06 AM CDT 2023\n"
Open3.popen2('echo', 'C #') { |i, o, t| o.gets }
# => "C #\n"
Open3.popen2('doesnt_exist') { |i, o, t| o.gets } # Raises Errno::ENOENT
관련: Open3.popen2e, Open3.popen3.
popen2e([env, ] command_line, options = {}) → [stdin, stdout_and_stderr, wait_thread]
Process.spawn을 감싼 래퍼예요. 자식의 표준 입력 스트림 stdin과 표준 출력/표준 오류 합친 스트림 stdout_and_stderr, 그리고 wait thread를 만들어요. 블록이 없으면 [stdin, stdout_and_stderr, wait_thread]를 반환해요.
stdin, stdout_and_stderr, wait_thread = Open3.popen2e('echo')
stdin.close; stdout_and_stderr.close
wait_thread.pid # => 2274600
wait_thread.value # => #<Process::Status: pid 2274600 exit 0>
관련: Open3.popen2, Open3.popen3.
popen3([env, ] command_line, options = {}) → [stdin, stdout, stderr, wait_thread]
Process.spawn을 감싼 래퍼예요. 자식 프로세스의 표준 입력 stdin, 표준 출력 stdout, 표준 오류 stderr 스트림과 wait thread를 만들어요. 블록이 없으면 [stdin, stdout, stderr, wait_thread]를 반환하고, 호출자가 세 스트림을 닫아야 해요.
stdin, stdout, stderr, wait_thread = Open3.popen3('echo')
stdin.close; stdout.close; stderr.close
wait_thread.pid # => 2210481
wait_thread.value # => #<Process::Status: pid 2210481 exit 0>
블록을 주면 네 변수로 블록을 호출해요.
Open3.popen3('echo') do |stdin, stdout, stderr, wait_thread|
p stdin; p stdout; p stderr
p wait_thread.pid; p wait_thread.value
end
교착 상태(deadlock)를 조심하세요. stdout과 stderr 출력 스트림은 고정 크기 버퍼를 가지므로, 한쪽만 많이 읽고 다른 쪽은 읽지 않으면 읽지 않은 버퍼가 가득 차서 교착 상태가 될 수 있어요. 그러려면 stdout과 stderr를 동시에(thread나 IO.select로) 읽어야 해요.
관련: Open3.popen2, Open3.popen2e.
비공개 인스턴스 메서드
capture2, capture2e, capture3, pipeline, pipeline_r, pipeline_rw, pipeline_start, pipeline_w, popen2, popen2e, popen3은 비공개(private) 인스턴스 메서드로도 존재해요. 동작은 위의 모듈 함수와 완전히 동일해요.
더 알아보기
- 각 메서드의 인자(
command_line/exe_path) 구분과 실행 환경(env), 실행 옵션(options)에 대한 자세한 내용은 원본 문서를 참고하세요. - 명령 주입(Command Injection) 방지를 위해 신뢰할 수 없는 입력을
command_line으로 쓰지 말고exe_path형태를 쓰는 걸 권장해요.