从Ruby调用shell命令

Calling shell commands from Ruby

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


这个解释是基于我朋友的一个评论过的Ruby脚本。如果您想改进脚本,请随时在链接中更新它。

首先,请注意,当Ruby调用shell时,它通常调用/bin/sh,而不是bash。在所有系统上,/bin/sh不支持某些bash语法。

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

1
cmd ="echo 'hi'" # Sample string that can be used
  • Kernel#`,通常称为backticks——`cmd`

    这和许多其他语言一样,包括bash、php和perl。

    返回shell命令的结果。

    文档:http://ruby doc.org/core/kernel.html method-i-60

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

    x字符之后是一个分隔符,可以是任何字符。如果分隔符是([{<字符之一,文字由匹配结束分隔符之前的字符组成,考虑到嵌套的分隔符对。对于所有其他分隔符,文字包含字符,直到下一次出现分隔符字符。允许字符串插入#{ ... }

    返回shell命令的结果,就像反勾号一样。

    文档:http://www.ruby-doc.org/docs/programmingruby/html/language.html

    1
    2
    value = %x( echo 'hi' )
    value = %x[ #{cmd} ]
  • Kernel#system

    在子shell中执行给定的命令。

    返回true,如果找到命令并成功运行,则返回false

    文档:http://ruby doc.org/core/kernel.html method-i-system

    1
    2
    wasGood = system("echo 'hi'" )
    wasGood = system( cmd )
  • Kernel#exec

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

    返回none,当前进程将被替换,并且永不继续。

    文档:http://ruby doc.org/core/kernel.html method-i-exec

    1
    2
    exec("echo 'hi'" )
    exec( cmd ) # Note: this will never be reached because of the line above
  • 以下是一些额外的建议:$?$CHILD_STATUS相同,如果使用backticks、system()%x{}访问最后一个系统执行命令的状态。然后可以访问exitstatuspid属性:

    1
    $?.exitstatus

    欲了解更多信息,请参阅:

    • http://www.elctech.com/blog/i-m-in-ur-commandline-executin-ma-commands
    • http://blog.jayfields.com/2006/06/ruby-kernel-system-exec-and-x.html
    • http://tech.natemurray.com/2007/03/ruby-shell-commands.html


    这是基于这个答案的流程图。另请参见,使用script模拟终端。

    enter image description here


    我喜欢这样做的方式是使用%x字面值,这使得它很容易(并且可读!)要在命令中使用引号,请执行以下操作:

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

    在本例中,它将用当前目录下的所有测试文件填充文件列表,您可以按预期处理这些文件:

    1
    2
    3
    4
    directorylist.each do |filename|
      filename.chomp!
      # work with file
    end


    下面是我对在Ruby中运行shell脚本的看法最好的一篇文章:"在Ruby中运行shell命令的6种方法"。

    如果只需要获取输出,请使用倒计时。

    我需要更高级的东西,比如stdout和stderr,所以我使用了open4 gem。你已经解释了所有的方法。


    我最喜欢的是Open3

    1
    2
    3
      require"open3"

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


    在这些机制之间进行选择时需要考虑的一些事情是:

  • 你只是想要性传播疾病还是还需要stderr?甚至分出?
  • 你的产量有多大?你想要把整个结果保存在内存中?
  • 你想看看你的子进程仍在时输出跑步?
  • 是否需要结果代码?
  • 你需要一个Ruby对象吗?表示进程并允许您随叫随到?
  • 您可能需要任何东西,从简单的backticks(``)、system()和IO.popen到全面的Kernel.fork/Kernel.execIO.pipeIO.select

    如果子进程执行时间太长,您可能还希望将超时值抛出到组合中。

    不幸的是,这要看情况而定。


    还有一个选择:

    当你:

    • 需要stderr和stdout
    • 不能/不会使用Open3/Open4(他们在我的Mac上的NetBeans中抛出异常,不知道为什么)

    可以使用shell重定向:

    1
    2
    3
    4
    5
    6
    puts %x[cat bogus.txt].inspect
      =>""

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

    从MS-DOS早期开始,2>&1语法就适用于Linux、Mac和Windows。


    我绝对不是红宝石专家,但我会试一试:

    1
    2
    3
    4
    $ irb
    system"echo Hi"
    Hi
    => true

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

    1
    2
    cmd = 'ls'
    system(cmd)

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

    基本上,它告诉我们:

    Kernel#exec

    1
    exec 'echo"hello $HOSTNAME"'

    system$?

    1
    2
    system 'false'
    puts $?

    Backticks(:):

    1
    today = `date`

    IO#popen

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

    Open3#popen3—标准库:

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

    Open4#popen4——宝石:

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


    如果你真的需要bash,按照"最佳"答案中的注释。

    First, note that when Ruby calls out to a shell, it typically calls /bin/sh, not Bash. Some Bash syntax is not supported by /bin/sh on all systems.

    如果需要使用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可能会有一些开销),您可能不会注意到。


    您还可以使用反勾号操作符(`),与Perl类似:

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

    如果你需要一些简单的东西,很方便。

    您希望使用哪种方法取决于您正试图完成的具体工作;请查看文档以了解有关不同方法的更多详细信息。


    最简单的方法是,例如:

    1
    2
    reboot = `init 6`
    puts reboot

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

  • 巧妙地捕获stdout和stderr,这样在从控制台运行脚本时它们就不会"泄漏"。
  • 允许将参数作为数组传递给shell,因此无需担心转义。
  • 捕获命令的退出状态,以便在发生错误时清除。
  • 另外,在shell命令成功退出(0)并将任何内容放入stdout的情况下,此命令还将返回stdout。在这种情况下,它不同于system,后者只返回true

    代码如下。具体功能为system_quietly

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    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"

    不要忘记spawn命令来创建后台进程来执行指定的命令。您甚至可以使用Process类和返回的pid等待其完成:

    1
    2
    3
    4
    5
    pid = spawn("tar xf ruby-2.0.0-p195.tar.bz2")
    Process.wait pid

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

    Doc说:这个方法类似于#system,但它不等待命令完成。


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

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

    1
    exec('ls ~')

    使用backticks or %x

    1
    2
    3
    4
    5
    6
    7
    8
    `ls ~`
    =>"Applications\
    Desktop\
    Documents"

    %x(ls ~)
    =>"Applications\
    Desktop\
    Documents"

    使用Kernel#system命令,成功返回true,失败返回false,命令执行失败返回nil

    1
    2
    system('ls ~')
    => true

    如果您的案件比普通案件更复杂(不能用``处理),请在这里查看Kernel.spawn()。这似乎是StockRuby为执行外部命令而提供的最通用/最全面的功能。

    例如,您可以使用它来:

    • 创建进程组(Windows)
    • 将入、出、错误重定向到文件/彼此。
    • 设置env vars,umask
    • 执行命令前更改目录
    • 设置CPU/data/的资源限制。
    • 做所有可以用其他答案中的其他选项完成的事情,但要用更多的代码。

    官方的Ruby文档有足够好的例子。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    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)

    • backticks`方法是从ruby调用shell命令最简单的方法。它返回shell命令的结果。

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

    给定一个橡木ATTRIB命令

    1
    2
    3
    4
    5
    6
    require 'open3'

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

    在这里发现的,虽然这个方法不memorable AA AA(如系统的"命令"在命令提示符)或向后的记号,一个好的事关于这两种方法相对其他方法。例如冰 向后的记号不似乎很容易推我的命令在命令行运行大的/我想运行在一个变量和系统("命令")不似乎很容易的输出。这个方法的尽头都让我做的那些事情,这让我访问stdin,stdout和stderr independently。

    http:/ / / / blog.bigbinary.com 2012年10月18日backtick-system-exec-in-ruby.html

    http:/ / / stdlib - ruby-doc.org 2.4.1 libdoc / / / / open3.html RDoc open3


    没有真正的答案,但这也许有人会找到有用的和它对这两种。

    当Windows GUI用TK和美国在线,需要呼叫的壳从rubyw commands,你始终有一个annoying CMD窗口弹出IP较小的那一秒。

    这两个你可以避免使用

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

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

    将两个巨大的输出地址内的log.txt’,但没有Windows将来的IP。

    你将需要你的require 'win32ole'里面的脚本。

    system()exec()全鸭spawn()会弹出窗口,annoying rubyw TK和当用。


    这是一个很酷的脚本,我在OSX上的Ruby脚本中使用(这样我就可以启动一个脚本并获取更新,即使在切换到远离窗口的位置之后):

    1
    2
    cmd = %Q|osascript -e 'display notification"Server was reset" with title"Posted Update"'|
    system ( cmd )