Cách chạy các subprocess in Ruby

Trong quá trình làm việc với Ruby, khi bạn muốn chạy 1 số câu lệnh hệ thống, đơn giản chỉ cần chọn phương thức phù hợp với bạn:

  • `backticks`
  • %x[different backticks]
  • Kernel.system()
  • Kernel.spawn()
  • IO.popen()
  • Open3.capture2, Open3.capture2e, Open3.capture3
  • Open3.popen2, Open3.popen2e, Open3.popen3
  • ...

Tuy nhiên, việc có quá nhiều phương thức khiến ta không biết lựa chọn như thế nào cho phù hợp.
Sau đây là 1 số pattern phổ biến khi chạy subprocess với Ruby:

1. Chạy subprocess không cần output

Khi bạn muốn chạy một cái gì đó, nhưng không cần đầu ra của nó, thì ta có thể dùng Kernel.system()

system('rm', '-r', directory) or raise "Failed to remove #{directory}"

system trả về true nếu lệnh trả về trạng thái zero, false nếu lệnh trả về trạng thái non-zero. Và trả về nil nếu thực thi lệnh thất bại. Ta có thể check status lỗi bằng cách sử dụng $?

2. Muốn capture stdout dưới dạng string và kế thừa stderr

Trường hợp này rất hay gặp phải trong thực tế. Ta có thể dùng các phương thức Open3.capture

  • Open3.capture3 : Trả về stdout, stderr dưới dạng string
  • Open3.capture2 : Trả về stdout dưới dạng string
  • Open3.capture2e : Trả về 1 string kết hợp giữa stdout và stderr

Ví dụ với Open3.capture3 ta có thể nhận lại stdout, stderr. Nếu muốn kiểm tra trạng thái thực thi câu lệnh ta có thể dùng status.success?

stdout, stderr, status = Open3.capture3(command, opts)

3. Muốn capture stdout dưới dạng stream

Với trường hợp stdout lớn hoặc ta muốn xử lý từng dòng của stdout, thì việc capture stdout dưới dạng stream là rất cần thiết. Lúc này ta có thể sử dụng các phương thức Open3.popen, với đầu vào cũng là dạng stream

  • Open3.popen3 : pipes for stdin, stdout, stderr
  • Open3.popen2 : pipes for stdin, stdout
  • Open3.popen2e : pipes for stdin, merged stdout and stderr

Ví dụ giải nén 1 file và in ra từng dòng như sau

Open3.popen2('unzip', '-l', zipfile) do |stdin, stdout, status_thread|
 stdout.each_line do |line|
   puts "LINE: #{line}"
 end
 raise "Unzip failed"  unless status_thread.value.success?
end

Flowchart

Ngoài những trường hợp phổ biến ở trên, ta còn có thể lựa chọn theo flowchart sau

Bằng việc đặt ra các câu hỏi dưới đây để xác định xem phương thức nào là phù hợp nhất

  1. Chạy xong có quay lại Ruby script không?
  2. Block chương trình cho đến khi subprocess chạy xong có ok không?
  3. Có cần trả về output của subprocess?
  4. Có cần tương tác với output của subprocess?
  5. Có cần stderr?
  6. Có cần trả về stderr trên stream tách biệt?

Tham khảo

  1. When to use each method of launching a subprocess in Ruby
  2. A dozen (or so) ways to start sub-processes in Ruby: Part 1
  3. A dozen (or so) ways to start sub-processes in Ruby: Part 2
  4. A Dozen (or so) Ways to Start Subprocesses in Ruby: Part 3