Cách gọi lệnh shell từ Ruby


1077

Làm cách nào để gọi các lệnh shell từ bên trong chương trình Ruby? Làm thế nào để tôi có được đầu ra từ các lệnh này trở lại vào Ruby?


3
Trong khi câu hỏi này hữu ích, nó không được hỏi tốt. Ruby có nhiều cách để gọi các shell con được ghi lại tốt và dễ dàng tìm thấy bằng cách đọc tài liệu KernelOpen3 và tìm kiếm ở đây trên SO.
Tin Man

1
Đáng buồn là chủ đề này khá phức tạp. Open3( docs ) là lựa chọn tốt nhất cho hầu hết các tình huống, IMO, nhưng trên các phiên bản cũ hơn của Ruby, nó sẽ không tôn trọng một sửa đổi PATH( bug.ruby-lang.org/issues/8004 ), và tùy thuộc vào cách bạn vượt qua args (cụ thể , nếu bạn sử dụng hàm băm opts với các từ khóa không), nó có thể bị hỏng. Nhưng, nếu bạn gặp phải những tình huống đó, thì bạn đang làm một điều gì đó khá tiên tiến và bạn có thể tìm ra những việc cần làm bằng cách đọc việc thực hiện Open3.
Joshua Cheek

3
Tôi ngạc nhiên không ai nhắc đến Shellwords.escape( doc ). Bạn không muốn chèn trực tiếp đầu vào của người dùng vào các lệnh shell - thoát nó trước! Xem thêm lệnh tiêm .
Kelvin

Câu trả lời:


1319

Lời giải thích này dựa trên một kịch bản Ruby được nhận xét từ một người bạn của tôi. Nếu bạn muốn cải thiện tập lệnh, vui lòng cập nhật nó tại liên kết.

Đầu tiên, lưu ý rằng khi Ruby gọi ra một vỏ, nó thường gọi /bin/sh, không Bash. Một số cú pháp Bash không được hỗ trợ bởi /bin/shtrên tất cả các hệ thống.

Dưới đây là các cách để thực thi tập lệnh shell:

cmd = "echo 'hi'" # Sample string that can be used
  1. Kernel#` , thường được gọi là backticks - `cmd`

    Điều này giống như nhiều ngôn ngữ khác, bao gồm Bash, PHP và Perl.

    Trả về kết quả (tức là đầu ra tiêu chuẩn) của lệnh shell.

    Tài liệu: http://ruby-doc.org/core/Kernel.html#method-i-60

    value = `echo 'hi'`
    value = `#{cmd}`
  2. Cú pháp tích hợp, %x( cmd )

    Theo sau xnhân vật là một dấu phân cách, có thể là bất kỳ nhân vật nào. Nếu delimiter là một trong những nhân vật (, [, {, hoặc <, sự đen bao gồm các ký tự lên đến delimiter khớp đóng cửa, có tính đến các cặp delimiter lồng nhau. Đối với tất cả các dấu phân cách khác, nghĩa đen bao gồm các ký tự cho đến lần xuất hiện tiếp theo của ký tự phân cách. Nội suy chuỗi #{ ... }được cho phép.

    Trả về kết quả (tức là đầu ra tiêu chuẩn) của lệnh shell, giống như các backticks.

    Tài liệu: https://docs.ruby-lang.org/en/master/syntax/literals_rdoc.html#label-Percent+Strings

    value = %x( echo 'hi' )
    value = %x[ #{cmd} ]
  3. Kernel#system

    Thực hiện lệnh đã cho trong một subshell.

    Trả về truenếu lệnh được tìm thấy và chạy thành công, falsenếu không.

    Tài liệu: http://ruby-doc.org/core/Kernel.html#method-i-system

    wasGood = system( "echo 'hi'" )
    wasGood = system( cmd )
  4. Kernel#exec

    Thay thế quy trình hiện tại bằng cách chạy lệnh bên ngoài đã cho.

    Trả về không, quá trình hiện tại được thay thế và không bao giờ tiếp tục.

    Tài liệu: http://ruby-doc.org/core/Kernel.html#method-i-exec

    exec( "echo 'hi'" )
    exec( cmd ) # Note: this will never be reached because of the line above

Đây là một số lời khuyên bổ sung:, $?giống như $CHILD_STATUS, truy cập trạng thái của lệnh thực thi hệ thống cuối cùng nếu bạn sử dụng backticks, system()hoặc %x{}. Sau đó, bạn có thể truy cập exitstatuspidcác thuộc tính:

$?.exitstatus

Để đọc thêm xem:


4
Tôi cần phải đăng nhập các kết quả đầu ra của tôi trên máy chủ sản xuất nhưng không tìm thấy cách nào. Tôi đã sử dụng đặt #{cmd}và logger.info ( #{cmd}). Có cách nào để ghi lại kết quả đầu ra của họ vào sản xuất không?
Omer Aslam

5
Và IO # popen () và Open3 # popen3 (). mentalized.net/journal/2010/03/08/...
hughdbrown

6
Để hoàn thiện (như lần đầu tiên tôi nghĩ đây cũng sẽ là lệnh Ruby): Rakesh "Chạy lệnh hệ thống cmd. Nếu nhiều đối số được đưa ra, lệnh sẽ không chạy với shell (cùng ngữ nghĩa như Kernel :: exec và Kernel :: system) ".
sschuberth

40
Backticks không chụp STDERR theo mặc định. Nối `2> & 1` vào lệnh nếu bạn muốn chụp
Andrei Botalov

14
Tôi nghĩ rằng câu trả lời này sẽ được cải thiện đôi chút nếu nó nói rằng backticks và% x trả về "đầu ra", thay vì "kết quả" của lệnh đã cho. Cái sau có thể bị nhầm với trạng thái thoát. Hoặc là chỉ cho tôi?
skagedal

275

24
Ái chà. Rất hữu ích mặc dù thực tế điều này phải tồn tại là không may
Josh Bodah

Một ghi chú bên lề, tôi thấy phương thức spawn () được tìm thấy ở nhiều nơi khác nhau (ví dụ KernelProcesslinh hoạt nhất. Nó ít nhiều giống với PTY.spawn(), nhưng chung chung hơn.
Smar

160

Cách tôi muốn làm điều này là sử dụng %xnghĩa đen, giúp dễ dàng (và có thể đọc được!) Để sử dụng dấu ngoặc kép trong một lệnh, như vậy:

directorylist = %x[find . -name '*test.rb' | sort]

Trong trường hợp này, sẽ điền vào danh sách tệp với tất cả các tệp kiểm tra trong thư mục hiện tại mà bạn có thể xử lý như mong đợi:

directorylist.each do |filename|
  filename.chomp!
  # work with file
end

4
%x[ cmd ]trả về một mảng cho bạn?
x-yuri

2
ở trên không làm việc cho tôi. `` <main> ': phương thức không xác định each' for :String (NoMethodError) làm thế nào nó hoạt động với bạn? Tôi đang sử dụng ruby -v ruby 1.9.3p484 (2013-11-22 revision 43786) [i686-linux]Bạn có chắc chắn một mảng được trả về từ lệnh để vòng lặp thực sự hoạt động không?
Nasser

% x [cmd] .split ("\ n") sẽ trả về một danh sách mặc dù :)
Ian Ellis

65

Đây là bài viết hay nhất theo quan điểm của tôi về việc chạy các kịch bản shell trong Ruby: " 6 cách để chạy các lệnh Shell trong Ruby ".

Nếu bạn chỉ cần lấy đầu ra, hãy sử dụng backticks.

Tôi cần những thứ nâng cao hơn như STDOUT và STDERR vì vậy tôi đã sử dụng đá quý Open4. Bạn có tất cả các phương pháp giải thích ở đó.


2
Bài viết được mô tả ở đây không thảo luận về %xtùy chọn cú pháp.
Mei

+1 cho Open4. Tôi đã bắt đầu thử thực hiện phiên bản spawnphương thức của riêng mình khi tôi tìm thấy nó.
Brandan

40

Yêu thích của tôi là Open3

  require "open3"

  Open3.popen3('nroff -man') { |stdin, stdout, stderr| ... }

2
Tôi cũng thích open3, đặc biệt là Open3.capture3: ruby-doc.org/stdlib-1.9.3/libdoc/open3/rdoc/iêu -> stdout, stderr, status = Open3.capture3('nroff -man', :stdin_data => stdin)
severin

Có tài liệu nào về cách thực hiện kiểm tra Spec và Unit với Open3 hoặc các Open khác trong Ruby std-lib không? Thật khó để kiểm tra vỏ ngoài ở mức độ hiểu biết hiện tại của tôi.
FilBot3

29

Một số điều cần suy nghĩ khi lựa chọn giữa các cơ chế này là:

  1. Bạn chỉ muốn stdout hay bạn cũng cần stderr? Hoặc thậm chí tách ra?
  2. Làm thế nào lớn là đầu ra của bạn? Bạn có muốn giữ toàn bộ kết quả trong bộ nhớ?
  3. Bạn có muốn đọc một số đầu ra của bạn trong khi tiến trình con vẫn đang chạy không?
  4. Bạn có cần mã kết quả?
  5. Bạn có cần một đối tượng Ruby đại diện cho quá trình và cho phép bạn giết nó theo yêu cầu không?

Bạn có thể cần bất cứ thứ gì từ backticks đơn giản (``) system(), và IO.popencho đến toàn bộ Kernel.fork/ Kernel.execvới IO.pipeIO.select.

Bạn cũng có thể muốn ném thời gian chờ vào hỗn hợp nếu một quy trình phụ mất quá nhiều thời gian để thực hiện.

Thật không may, nó phụ thuộc rất nhiều .


25

Thêm một lựa chọn:

Khi bạn:

  • cần thiết bị lỗi chuẩn cũng như thiết bị xuất chuẩn
  • không thể / sẽ không sử dụng Open3 / Open4 (họ ném ngoại lệ vào NetBeans trên máy Mac của tôi, không biết tại sao)

Bạn có thể sử dụng chuyển hướng vỏ:

puts %x[cat bogus.txt].inspect
  => ""

puts %x[cat bogus.txt 2>&1].inspect
  => "cat: bogus.txt: No such file or directory\n"

Các 2>&1cú pháp công trình trên Linux , Mac và của Windows kể từ những ngày đầu của MS-DOS.


25

Tôi chắc chắn không phải là chuyên gia về Ruby, nhưng tôi sẽ thử.

$ irb 
system "echo Hi"
Hi
=> true

Bạn cũng có thể làm những việc như:

cmd = 'ls'
system(cmd)

21

Các câu trả lời ở trên đã khá tuyệt vời, nhưng tôi thực sự muốn chia sẻ bài viết tóm tắt sau: " 6 cách để chạy các lệnh Shell trong Ruby "

Về cơ bản, nó cho chúng ta biết:

Kernel#exec:

exec 'echo "hello $HOSTNAME"'

system$?:

system 'false' 
puts $?

Backticks (`):

today = `date`

IO#popen:

IO.popen("date") { |f| puts f.gets }

Open3#popen3 - stdlib:

require "open3"
stdin, stdout, stderr = Open3.popen3('dc') 

Open4#popen4 -- một viên ngọc:

require "open4" 
pid, stdin, stdout, stderr = Open4::popen4 "false" # => [26327, #<IO:0x6dff24>, #<IO:0x6dfee8>, #<IO:0x6dfe84>]

15

Nếu bạn thực sự cần Bash, hãy ghi chú trong câu trả lời "tốt nhất".

Đầu tiên, lưu ý rằng khi Ruby gọi ra một vỏ, nó thường gọi /bin/sh, không Bash. Một số cú pháp Bash không được hỗ trợ bởi /bin/shtrên tất cả các hệ thống.

Nếu bạn cần sử dụng Bash, hãy chèn vào bash -c "your Bash-only command"bên trong phương thức gọi bạn muốn:

quick_output = system("ls -la")
quick_bash = system("bash -c 'ls -la'")

Để kiểm tra:

system("echo $SHELL")
system('bash -c "echo $SHELL"')

Hoặc nếu bạn đang chạy một tệp script hiện có như

script_output = system("./my_script.sh")

Ruby nên tôn vinh shebang, nhưng bạn luôn có thể sử dụng

system("bash ./my_script.sh")

để chắc chắn, mặc dù có thể có một chút chi phí khi /bin/shchạy /bin/bash, bạn có thể sẽ không chú ý.


11

Bạn cũng có thể sử dụng các toán tử backtick (`), tương tự như Perl:

directoryListing = `ls /`
puts directoryListing # prints the contents of the root directory

Tiện dụng nếu bạn cần một cái gì đó đơn giản.

Phương pháp nào bạn muốn sử dụng phụ thuộc vào chính xác những gì bạn đang cố gắng thực hiện; kiểm tra các tài liệu để biết thêm chi tiết về các phương pháp khác nhau.


10

Chúng ta có thể đạt được nó theo nhiều cách.

Sử dụng Kernel#exec, không có gì sau khi lệnh này được thực thi:

exec('ls ~')

Sử dụng backticks or %x

`ls ~`
=> "Applications\nDesktop\nDocuments"
%x(ls ~)
=> "Applications\nDesktop\nDocuments"

Sử dụng Kernel#systemlệnh, trả về truenếu thành công, falsenếu không thành công và trả về nilnếu thực thi lệnh không thành công:

system('ls ~')
=> true


9

Sử dụng các câu trả lời ở đây và được liên kết trong câu trả lời của Mihai, tôi kết hợp một hàm đáp ứng các yêu cầu sau:

  1. Bắt gọn gàng STDOUT và STDERR để chúng không bị "rò rỉ" khi tập lệnh của tôi được chạy từ bảng điều khiển.
  2. Cho phép các đối số được truyền vào shell dưới dạng một mảng, do đó không cần phải lo lắng về việc thoát.
  3. Nắm bắt trạng thái thoát lệnh để rõ ràng khi xảy ra lỗi.

Như một phần thưởng, cái này cũng sẽ trả về STDOUT trong trường hợp lệnh shell thoát thành công (0) và đặt bất cứ thứ gì vào STDOUT. Theo cách này, nó khác với system, chỉ đơn giản là trả về truetrong các trường hợp như vậy.

Mã theo sau. Chức năng cụ thể là system_quietly:

require 'open3'

class ShellError < StandardError; end

#actual function:
def system_quietly(*cmd)
  exit_status=nil
  err=nil
  out=nil
  Open3.popen3(*cmd) do |stdin, stdout, stderr, wait_thread|
    err = stderr.gets(nil)
    out = stdout.gets(nil)
    [stdin, stdout, stderr].each{|stream| stream.send('close')}
    exit_status = wait_thread.value
  end
  if exit_status.to_i > 0
    err = err.chomp if err
    raise ShellError, err
  elsif out
    return out.chomp
  else
    return true
  end
end

#calling it:
begin
  puts system_quietly('which', 'ruby')
rescue ShellError
  abort "Looks like you don't have the `ruby` command. Odd."
end

#output: => "/Users/me/.rvm/rubies/ruby-1.9.2-p136/bin/ruby"

9

Đừng quên spawnlệnh để tạo một quy trình nền để thực hiện lệnh được chỉ định. Bạn thậm chí có thể chờ đợi nó hoàn thành bằng cách sử dụng Processlớp và trả lại pid:

pid = spawn("tar xf ruby-2.0.0-p195.tar.bz2")
Process.wait pid

pid = spawn(RbConfig.ruby, "-eputs'Hello, world!'")
Process.wait pid

Tài liệu nói: Phương thức này tương tự #systemnhưng không chờ lệnh kết thúc.


2
Kernel.spawn()dường như linh hoạt hơn nhiều so với tất cả các tùy chọn khác.
Kashyap

6

Nếu bạn có một trường hợp phức tạp hơn trường hợp phổ biến không thể xử lý được ``, thì hãy kiểm tra Kernel.spawn(). Đây dường như là tính năng chung / đầy đủ nhất được cung cấp bởi stock Ruby để thực thi các lệnh bên ngoài.

Bạn có thể sử dụng nó để:

  • tạo các nhóm quy trình (Windows).
  • chuyển hướng vào, ra, lỗi với các tập tin / nhau.
  • đặt env vars, umask.
  • thay đổi thư mục trước khi thực hiện một lệnh.
  • đặt giới hạn tài nguyên cho CPU / dữ liệu / vv.
  • Làm mọi thứ có thể được thực hiện với các tùy chọn khác trong các câu trả lời khác, nhưng với nhiều mã hơn.

Các tài liệu của Ruby có các ví dụ tốt, đủ:

env: hash
  name => val : set the environment variable
  name => nil : unset the environment variable
command...:
  commandline                 : command line string which is passed to the standard shell
  cmdname, arg1, ...          : command name and one or more arguments (no shell)
  [cmdname, argv0], arg1, ... : command name, argv[0] and zero or more arguments (no shell)
options: hash
  clearing environment variables:
    :unsetenv_others => true   : clear environment variables except specified by env
    :unsetenv_others => false  : dont clear (default)
  process group:
    :pgroup => true or 0 : make a new process group
    :pgroup => pgid      : join to specified process group
    :pgroup => nil       : dont change the process group (default)
  create new process group: Windows only
    :new_pgroup => true  : the new process is the root process of a new process group
    :new_pgroup => false : dont create a new process group (default)
  resource limit: resourcename is core, cpu, data, etc.  See Process.setrlimit.
    :rlimit_resourcename => limit
    :rlimit_resourcename => [cur_limit, max_limit]
  current directory:
    :chdir => str
  umask:
    :umask => int
  redirection:
    key:
      FD              : single file descriptor in child process
      [FD, FD, ...]   : multiple file descriptor in child process
    value:
      FD                        : redirect to the file descriptor in parent process
      string                    : redirect to file with open(string, "r" or "w")
      [string]                  : redirect to file with open(string, File::RDONLY)
      [string, open_mode]       : redirect to file with open(string, open_mode, 0644)
      [string, open_mode, perm] : redirect to file with open(string, open_mode, perm)
      [:child, FD]              : redirect to the redirected file descriptor
      :close                    : close the file descriptor in child process
    FD is one of follows
      :in     : the file descriptor 0 which is the standard input
      :out    : the file descriptor 1 which is the standard output
      :err    : the file descriptor 2 which is the standard error
      integer : the file descriptor of specified the integer
      io      : the file descriptor specified as io.fileno
  file descriptor inheritance: close non-redirected non-standard fds (3, 4, 5, ...) or not
    :close_others => false : inherit fds (default for system and exec)
    :close_others => true  : dont inherit (default for spawn and IO.popen)

6

Phương thức backticks (`) là cách dễ nhất để gọi các lệnh shell từ Ruby. Nó trả về kết quả của lệnh shell:

     url_request = 'http://google.com'
     result_of_shell_command = `curl #{url_request}`

5

Đưa ra một lệnh như attrib:

require 'open3'

a="attrib"
Open3.popen3(a) do |stdin, stdout, stderr|
  puts stdout.read
end

Tôi đã thấy rằng trong khi phương pháp này không đáng nhớ bằng

system("thecommand")

hoặc là

`thecommand`

trong backticks, một điều tốt về phương thức này so với các phương thức khác là backticks dường như không cho tôi putslệnh tôi chạy / lưu trữ lệnh tôi muốn chạy trong một biến và system("thecommand")dường như không cho phép tôi nhận đầu ra trong khi phương pháp này cho phép tôi thực hiện cả hai điều đó và cho phép tôi truy cập stdin, stdout và stderr một cách độc lập.

Xem " Thực thi các lệnh trong ruby " và tài liệu Open3 của Ruby .


3

Đây không thực sự là một câu trả lời nhưng có lẽ ai đó sẽ thấy nó hữu ích:

Khi sử dụng TK GUI trên Windows và bạn cần gọi các lệnh shell từ rubyw, bạn sẽ luôn có một cửa sổ CMD khó chịu bật lên trong ít hơn một giây.

Để tránh điều này, bạn có thể sử dụng:

WIN32OLE.new('Shell.Application').ShellExecute('ipconfig > log.txt','','','open',0)

hoặc là

WIN32OLE.new('WScript.Shell').Run('ipconfig > log.txt',0,0)

Cả hai sẽ lưu trữ ipconfigđầu ra bên trong log.txt, nhưng không có cửa sổ sẽ xuất hiện.

Bạn sẽ cần phải require 'win32ole'bên trong kịch bản của bạn.

system(), exec()spawn()tất cả sẽ bật lên cửa sổ gây phiền nhiễu khi sử dụng TK và rubyw.


-2

Đây là một kịch bản hay mà tôi sử dụng trong tập lệnh ruby ​​trên OS X (để tôi có thể bắt đầu tập lệnh và nhận được bản cập nhật ngay cả sau khi bật ra khỏi cửa sổ):

cmd = %Q|osascript -e 'display notification "Server was reset" with title "Posted Update"'|
system ( cmd )
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.