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
- Chạy xong có quay lại Ruby script không?
- Block chương trình cho đến khi subprocess chạy xong có ok không?
- Có cần trả về output của subprocess?
- Có cần tương tác với output của subprocess?
- Có cần stderr?
- Có cần trả về stderr trên stream tách biệt?