Computer >> Máy Tính >  >> Lập trình >> Ruby

Chụp stdout &stderr từ các lệnh shell thông qua Ruby

tl; dr Nếu bạn muốn chạy một lệnh shell từ Ruby và nắm bắt trạng thái stdout, stderr và return của nó, hãy xem Open3.capture3 phương pháp. Nếu bạn muốn xử lý dữ liệu stdout và stderr theo cách truyền trực tuyến, hãy xem Open3.popen3 .

Rất nhiều lựa chọn tồi

Thực sự có 492 cách để thực thi các lệnh shell từ ruby ​​và mỗi cách trong số chúng hoạt động hơi khác nhau. Tôi cá là bạn đã sử dụng một trong các cách tiếp cận dưới đây. Việc cần làm của tôi luôn là dấu tích (``).

exec("echo 'hello world'") # exits from ruby, then runs the command
system('echo', 'hello world') # returns the status code
sh('echo', 'hello world') # returns the status code
`echo "hello world"` # returns stdout
%x[echo 'hello world'] # returns stdout

Nhưng những cách tiếp cận này khá hạn chế. Giả sử rằng bạn không chỉ cần nắm bắt stdout của lệnh shell mà còn cả stderr của nó. Bạn chỉ là do may mắn. Hoặc giả sử bạn muốn xử lý dữ liệu stdout trong một luồng và không phải tất cả cùng một lúc khi lệnh chạy xong? Không may mắn.

Có một lựa chọn khác. Một cung cấp cho bạn khả năng chạy các lệnh không đồng bộ và cung cấp cho bạn mã stdout, stderr, thoát và PID. Hãy cùng khám phá!

Open3

Mô-đun open3 có tên kỳ lạ là một phần của thư viện tiêu chuẩn của Ruby. Nó làm gì?

Open3 cấp cho bạn quyền truy cập vào các mã stdout, stderr, exit và một chuỗi để chờ xử lý con khi chạy một chương trình khác. Bạn có thể chỉ định các thuộc tính khác nhau, chuyển hướng, thư mục hiện tại, v.v. của chương trình theo cách tương tự như đối với Process.spawn. (_Nguồn:[Open3 Docs] (https://ruby-doc.org/stdlib-2.1.0/libdoc/open3/rdoc/Open3.html)) _

Chưa bao giờ sử dụng nó? Chưa bao giờ nghe tới nó? Tôi đoán đó là bởi vì nó không được coi là thư viện thân thiện nhất. Bản thân cái tên này nghe giống C hơn là Ruby. Và tài liệu hướng dẫn khá cứng đầu. Nhưng một khi bạn thử, bạn sẽ thấy rằng nó không đáng sợ như nó nghe.

capture3

Điều gì sẽ xảy ra nếu có một cách dễ dàng để nắm bắt stdout, stderr VÀ mã trạng thái? Có đấy. Nếu bạn không có thời gian để đọc phần còn lại của bài viết này, chỉ cần biết rằng bạn có thể sử dụng một phương pháp có tên là capture3 và gọi nó là một ngày.

Hãy xem một ví dụ. Giả sử bạn muốn lấy danh sách các tệp trong thư mục hiện tại của mình. Để làm điều đó, bạn có thể chạy ls lệnh.

Nếu bạn sử dụng cú pháp đánh dấu ngược, nó sẽ giống như sau:

puts(`ls`)

Với capture3 nó trông giống như vậy:

require 'open3'
stdout, stderr, status = Open3.capture3("ls")

Thao tác này sẽ chạy lệnh của bạn và cung cấp cho bạn stdout và stderr dưới dạng chuỗi. Không phiền phức.

Bảo mật

Bạn thường không muốn cung cấp cho người dùng khả năng chạy các lệnh tùy ý trên máy chủ web của mình. Đó là lý do tại sao mã như identify #{ params[:filename] } quả là một ý tưởng kinh khủng.

Open3 cho phép bạn tránh những vấn đề như thế này bằng cách tách các lệnh khỏi dữ liệu. Nó hoạt động giống như phương pháp hệ thống.

Open3.capture3("identify", params[:filename], other_unsafe_params)

popen3

Bên cạnh đó, capture3 sử dụng một phương pháp mạnh mẽ hơn nhiều được gọi là popen3. Phương thức này hoạt động hơi khác so với các phương thức quen thuộc hơn như system ().

Đây là những gì nó trông như thế nào:

require 'open3'
Open3.popen3("ls") do |stdout, stderr, status, thread|
  puts stdout.read
end

Nó giống như khi bạn mở và đọc từ một tệp. Tôi chắc rằng bạn đã thấy mã như thế này:

File.open("my/file/path", "r") do |f|
  puts f.read
end

Đường ống

Với Open3, stdout và stderr đều là các đường dẫn, hoạt động giống như bộ đệm tệp. Và giống như các tệp, chúng cần được đóng lại khi bạn hoàn tất. Đó là lý do cho cú pháp khối. (Có một cú pháp không phải là khối, nhưng bạn phải gọi thủ công close trên stdout và stderr.)

Phương thức đọc đợi cho đến khi các đường ống được đóng lại trước khi trả về một giá trị. Nhưng các đường ống cũng hỗ trợ đường đọc khi chúng có sẵn. Hãy tưởng tượng lệnh shell của bạn mất vài giây để chạy. Trong thời gian đó, nó sẽ in một thông báo trạng thái tới stderr. Bạn muốn nắm bắt và hiển thị nó cho người dùng của bạn.

Đây là cách bạn ghi lại từng dòng một.

require 'open3'
Open3.popen3("sleep 2; ls") do |stdout, stderr, status, thread|
  while line=stderr.gets do 
    puts(line) 
  end
end

Chủ đề

Có một lý lẽ mà chúng tôi chưa nói đến. Đó là chủ đề.

Đối số luồng cung cấp cho bạn một tham chiếu đến một chuỗi ruby ​​đang chờ lệnh của bạn kết thúc. Bây giờ, lệnh không chạy trong chuỗi. Nó đang chạy trong một quy trình hoàn toàn riêng biệt. Chuỗi chỉ xem quá trình và đợi cho đến khi hoàn tất.

Tuy nhiên, bạn có thể nhận được một số dữ liệu hữu ích từ tham chiếu chuỗi đó.

  • thread.pid - chứa id tiến trình của lệnh shell của bạn. Bạn sẽ cần điều này nếu bạn muốn thực hiện các hoạt động cấp hệ điều hành bổ sung đối với quy trình đó.

  • thread.status - chứa trạng thái thoát của tiến trình. 1 hoặc 0 để thành công hay thất bại.

Lưu ý

Từ tài liệu Open3:

Bạn nên cẩn thận để tránh deadlock. Vì đường ống là bộ đệm có độ dài cố định, [::popen3] (https://ruby-doc.org/stdlib-2.1.0/libdoc/open3/rdoc/Open3.html#method-c-popen3) (“prog”) {| i, o, e, t | o.read} deadlock nếu chương trình tạo ra quá nhiều đầu ra trên stderr. Bạn nên đọc stdout và stderr đồng thời (sử dụng chủ đề hoặc IO.select). Tuy nhiên, nếu bạn không cần đầu ra stderr, bạn có thể sử dụng [::popen2] (https://ruby-doc.org/stdlib-2.1.0/libdoc/open3/rdoc/Open3.html#method-c- popen2). Nếu đầu ra stdout và stderr được hợp nhất không phải là vấn đề, bạn có thể sử dụng [::popen2e] (https://ruby-doc.org/stdlib-2.1.0/libdoc/open3/rdoc/Open3.html#method-c- popen2e). Nếu bạn thực sự cần đầu ra stdout và stderr dưới dạng các chuỗi riêng biệt, bạn có thể xem xét [::capture3] (https://ruby-doc.org/stdlib-2.1.0/libdoc/open3/rdoc/Open3.html#method-c -capture3).