如何从Ruby调用Shell命令


1076

如何从Ruby程序内部调用Shell命令?然后如何将这些命令的输出返回到Ruby?


3
虽然这个问题很有用,但提出的问题并不好。Ruby有很多调用子shell的方法,这些子shell有充分的文档证明,可以通过阅读KernelOpen3文档并在此处搜索找到。
Tin Man

1
可悲的是,这个话题非常复杂。Open3docs)是大多数情况下IMO的最佳选择,但是在Ruby的旧版本上,它不会遵循已修改的PATHbugs.ruby-lang.org/issues/8004),并且取决于您传递args的方式(具体来说是,如果您在非关键字中使用opts哈希),则可能会中断。但是,如果遇到这种情况,那么您正在做一些相当高级的事情,您可以通过阅读的实现来弄清楚该怎么做Open3
约书亚脸颊

3
我很惊讶没有人提到Shellwords.escapedoc)。您不想将用户输入直接插入shell命令中-请先对其进行转义!另请参阅命令注入
开尔文

Answers:


1318

该说明基于我的一个朋友的注释Ruby脚本。如果要改进脚本,请随时在链接上进行更新。

首先,请注意,当Ruby调用shell时,通常调用/bin/sh而不是 Bash。并非/bin/sh所有系统都支持某些Bash语法。

以下是执行Shell脚本的方法:

cmd = "echo 'hi'" # Sample string that can be used
  1. Kernel#` ,通常称为反引号– `cmd`

    就像许多其他语言一样,包括Bash,PHP和Perl。

    返回shell命令的结果(即标准输出)。

    文件:http : //ruby-doc.org/core/Kernel.html#method-i-60

    value = `echo 'hi'`
    value = `#{cmd}`
    
  2. 内置语法 %x( cmd )

    x字符是一个分隔符,它可以是任何字符。如果分隔符是字符中的一个([{,或<,文字由字符直到匹配的结束分隔符,以嵌套定界符对帐户。对于所有其他定界符,文字包括直到下一个定界符出现的字符。#{ ... }允许字符串插值。

    像反引号一样,返回shell命令的结果(即标准输出)。

    文件:https//docs.ruby-lang.org/en/master/syntax/literals_rdoc.html#label-Percent+Strings

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

    在子shell中执行给定命令。

    返回true是否找到命令并成功运行,false否则返回。

    文件:http : //ruby-doc.org/core/Kernel.html#method-i-system

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

    通过运行给定的外部命令来替换当前进程。

    不返回任何值,当前进程将被替换且永远不会继续。

    文件: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
    

以下是一些额外的建议: $?与一样$CHILD_STATUS,如果使用反引号system()或,则访问上一次系统执行的命令的状态%x{}。然后,您可以访问exitstatuspid属性:

$?.exitstatus

有关更多阅读,请参阅:


4
我需要在生产服务器上记录可执行文件的输出,但找不到任何方法。我使用了puts #{cmd}和logger.info(#{cmd})。有什么办法可以记录他们的生产产量?
Omer Aslam

5
以及IO#popen()和Open3#popen3()。mentalized.net/journal/2010/03/08/...
hughdbrown

6
为了完整起见(我最初认为这也是Ruby命令):Rake拥有sh,它可以“运行系统命令cmd。如果给出了多个参数,则该命令不能与外壳一起运行(与Kernel:的语义相同:: exec和Kernel :: system)”。
sschuberth 2013年

40
反引号默认情况下不捕获STDERR。如果要捕获,将`2>&1`附加到命令中
Andrei Botalov

14
我认为,如果说反引号和%x返回给定命令的“输出”而不是“结果”,则此答案会有所改善。后者可能被误认为退出状态。还是仅仅是我?
skagedal


160

我喜欢这样做的方式是使用%x文字,这使得在命令中使用引号变得容易(且易于阅读!),如下所示:

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

在这种情况下,它将用当前目录下的所有测试文件填充文件列表,您可以按预期进行处理:

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

4
是否%x[ cmd ]向您返回数组?
x-yuri 2014年

2
以上对我不起作用。``<main>':未定义的方法each' for :String (NoMethodError) 它如何为您工作?我正在使用ruby -v ruby 1.9.3p484 (2013-11-22 revision 43786) [i686-linux]是否确定从命令中返回了一个数组,以便循环真正起作用?
纳赛尔2015年

%x [cmd] .split(“ \ n”)虽然会返回一个列表:)
Ian Ellis

65

在我看来,这是关于在Ruby中运行Shell脚本的最佳文章:“在Ruby 中运行Shell命令的6种方法 ”。

如果只需要获取输出,请使用反引号。

我需要像STDOUT和STDERR这样的更高级的东西,所以我使用了Open4 gem。您已在此处说明了所有方法。


2
这里描述的文章不讨论%x语法选项。

+1代表Open4。spawn当我发现它时,我已经开始尝试实现自己的方法版本。
布兰登

40

我最喜欢的是Open3

  require "open3"

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

2
我也很喜欢open3,尤其是Open3.capture3:ruby-doc.org/stdlib-1.9.3/libdoc/open3/rdoc/... - > stdout, stderr, status = Open3.capture3('nroff -man', :stdin_data => stdin)
塞弗林

在Ruby std-lib中是否有关于如何使用Open3或其他Open进行规格和单元测试的文档?以我目前的理解水平,很难测试外壳。
FilBot3 '16

29

在这些机制之间进行选择时,需要考虑以下几点:

  1. 您只是想要stdout还是需要stderr?甚至分开?
  2. 您的输出多少?您是否要将整个结果保存在内存中?
  3. 您是否想在子进程仍在运行时读取一些输出?
  4. 您需要结果代码吗?
  5. 您是否需要一个代表该流程并允许您按需将其杀死的Ruby对象?

您可能需要从简单的反引号什么(``) system(),并IO.popen以全面的Kernel.fork/ Kernel.execIO.pipeIO.select

如果子流程执行时间过长,您可能还想将超时投入混合。

不幸的是,这在很大程度上取决于


25

另一种选择:

当你:

  • 需要stderr以及stdout
  • 无法/不会使用Open3 / Open4(它们在Mac上的NetBeans中抛出异常,不知道为什么)

您可以使用外壳重定向:

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

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

自MS-DOS成立以来,该2>&1语法就可以在Linux,Mac和Windows上使用


25

我绝对不是Ruby专家,但我会给您一个机会:

$ irb 
system "echo Hi"
Hi
=> true

您还应该能够执行以下操作:

cmd = 'ls'
system(cmd)

21

上面的答案已经很不错了,但是我真的很想分享以下摘要文章:“ 在Ruby中运行Shell命令的6种方法

基本上,它告诉我们:

Kernel#exec

exec 'echo "hello $HOSTNAME"'

system$?

system 'false' 
puts $?

反引号(`):

today = `date`

IO#popen

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

Open3#popen3 -stdlib:

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

Open4#popen4 -宝石:

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

15

如果您确实需要Bash,请按照“最佳”答案中的注释进行操作。

首先,请注意,当Ruby调用shell时,通常调用/bin/sh而不是 Bash。并非/bin/sh所有系统都支持某些Bash语法。

如果需要使用Bash,bash -c "your Bash-only command"请在所需的调用方法中插入:

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

去测试:

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

或者,如果您正在运行现有的脚本文件,例如

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

Ruby 应该尊重Shebang,但您可以随时使用

system("bash ./my_script.sh")

确保尽管/bin/sh运行可能会产生一些开销/bin/bash,但您可能不会注意到。


11

您还可以使用反引号运算符(`),类似于Perl:

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

如果您需要简单的东西,则非常方便。

您要使用哪种方法取决于您要完成的工作。检查文档以获取有关不同方法的更多详细信息。


10

我们可以通过多种方式实现它。

使用Kernel#exec,执行此命令后不执行任何操作:

exec('ls ~')

使用 backticks or %x

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

使用Kernel#system命令,true如果成功false则返回;如果不成功,则返回;nil如果命令执行失败,则返回:

system('ls ~')
=> true


9

使用这里的答案并链接到Mihai的答案中,我组成了一个满足这些要求的函数:

  1. 整洁地捕获STDOUT和STDERR,以便从控制台运行我的脚本时它们不会“泄漏”。
  2. 允许将参数作为数组传递给外壳,因此无需担心转义。
  3. 捕获命令的退出状态,以便在发生错误时可以将其清除。

另外,如果shell命令成功退出(0)并将任何内容放入STDOUT,则此命令也将返回STDOUT。以这种方式,它不同于systemtrue在这种情况下简单地返回。

代码如下。具体功能是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

不要忘记spawn创建后台进程以执行指定命令的命令。您甚至可以使用Process类和返回的内容等待其完成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

该文档说:此方法类似于,#system但它不等待命令完成。


2
Kernel.spawn()似乎比其他所有选项都通用。
Kashyap 2015年

6

如果您遇到的案例比无法处理的常见案例更为复杂``,请签出 Kernel.spawn()。这似乎是普通Ruby提供的用于执行外部命令的最通用/最全面的功能。

您可以使用它来:

  • 创建进程组(Windows)。
  • 将错误重定向到文件或其他文件。
  • 设置环境变量,umask。
  • 在执行命令之前更改目录。
  • 设置CPU /数据/等的资源限制。
  • 用其他答案中的其他选项来完成所有事情,但是要编写更多代码。

Ruby文档具有很好的足够的例子:

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

反引号(`)方法是从Ruby调用shell命令的最简单方法。它返回shell命令的结果:

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

5

给定像这样的命令attrib

require 'open3'

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

我发现虽然这种方法不如让人难忘

system("thecommand")

要么

`thecommand`

在反引号中,与其他方法相比,此方法的优点是反引号似乎不让我puts运行/存储要运行的命令的命令存储在变量中,并且system("thecommand")似乎不让我获取输出,而此方法使我可以同时执行这两项操作,并且可以独立访问stdin,stdout和stderr。

请参阅“ 在ruby中执行命令 ”和Ruby的Open3文档


3

这并不是真正的答案,但也许有人会发现它有用:

在Windows上使用TK GUI时,您需要从rubyw调用shell命令,您总是会在不到一秒钟的时间内弹出一个烦人的CMD窗口。

为了避免这种情况,您可以使用:

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

要么

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

两者都将ipconfig输出存储在内部log.txt,但是不会出现任何窗口。

您将需要require 'win32ole'在脚本中。

system()exec()并且spawn()在使用TK和rubyw时都会弹出该烦人的窗口。


-2

这是我在OS X上的ruby脚本中使用的一个很酷的脚本(以便即使从窗口切换后也可以启动脚本并获得更新):

cmd = %Q|osascript -e 'display notification "Server was reset" with title "Posted Update"'|
system ( cmd )
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.