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을 호출해 자식 프로세스를 만듭니다 (hash options의 특정 항목 제외).
  • 자식 프로세스의 표준 출력을 문자열 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.popen3options 인자가 돼요.

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)를 조심하세요. stdoutstderr 출력 스트림은 고정 크기 버퍼를 가지므로, 한쪽만 많이 읽고 다른 쪽은 읽지 않으면 읽지 않은 버퍼가 가득 차서 교착 상태가 될 수 있어요. 그러려면 stdoutstderr를 동시에(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 형태를 쓰는 걸 권장해요.