如何从Ruby调用shell命令

问题描述 投票:1033回答:20

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

ruby shell interop
20个回答
1287
投票

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

首先,请注意,当Ruby调用shell时,通常会调用/bin/shnot 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命令的结果(即标准输出),就像反引号一样。

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

    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

更多阅读,请参阅:


14
投票

如果您真的需要Bash,请按照“最佳”答案中的注释。


10
投票

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


9
投票

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


9
投票

最简单的方法是,例如:


8
投票

使用此处的答案并链接到Mihai的答案中,我建立了一个满足这些要求的功能:


7
投票

不要忘记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

5
投票

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

您可以使用它来:


5
投票

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

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)

4
投票

给出类似 url_request = 'http://google.com' result_of_shell_command = `curl #{url_request}` 的命令:

attrib

2
投票

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

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

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



-2
投票

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

spawn()

158
投票

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

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

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

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

63
投票

这是我认为有关在Ruby中运行Shell脚本的最佳文章:“ 6 Ways to Run Shell Commands in Ruby”。

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

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


37
投票

我的最爱是Open3

  require "open3"

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

27
投票

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

  1. 您只想要标准输出还是您还需要stderr吗?甚至分开吗?
  2. 您的输出量是多少?你想要将整个结果保存在内存中?
  3. 您是否想阅读一些子流程仍在时输出正在运行?
  4. 您需要结果代码吗?
  5. 您是否需要一个代表过程,让您按需杀死它?

您可能需要从简单的反引号(``),system()IO.popen到带有Kernel.forkKernel.exec的成熟的IO.pipe / IO.select

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

不幸的是,非常取决于

]。

另一个选择:

当您:

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

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中使用。

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

$ irb 
system "echo Hi"
Hi
=> true

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

cmd = 'ls'
system(cmd)

上面的答案已经很不错了,但是我真的很想分享以下摘要文章:“ 6 Ways to Run Shell Commands in Ruby

基本上,它告诉我们:

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>]

如果您真的需要Bash,请按照“最佳”答案中的注释。

首先,请注意,当Ruby调用shell时,通常会调用/bin/shnot

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 should

尊重shebang,但您可以始终使用
system("bash ./my_script.sh")

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

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

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

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

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

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

使用Kernel#exec,此命令执行后什么都没有:

exec('ls ~')

使用backticks or %x

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

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

system('ls ~')
=> true

最简单的方法是,例如:

reboot = `init 6`
puts reboot

使用此处的答案并链接到Mihai的答案中,我建立了一个满足这些要求的功能:

  1. 整齐地捕获STDOUT和STDERR,以便从控制台运行我的脚本时它们不会“泄漏”。
  • 允许将参数作为数组传递给外壳,因此无需担心转义。
  • 捕获命令的退出状态,以便在发生错误时可以清楚地看到。
  • 作为奖励,如果shell命令成功退出(0)并将任何内容放在STDOUT上,则此命令也将返回STDOUT。以这种方式,它不同于system,在这种情况下,它仅返回true

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

    不要忘记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,但它不等待命令完成。

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

    您可以使用它来:

    • 创建进程组(Windows)。
  • 将文件重定向到文件/其他错误。
  • 设置环境变量,umask。
  • 在执行命令之前更改目录。
  • 设置CPU /数据/等的资源限制
  • [使用其他答案中的其他选项来完成,但要提供更多代码。
  • Kernel.spawn()有足够好的示例:

    Ruby documentation 

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

    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)
    

    给出类似 url_request = 'http://google.com' result_of_shell_command = `curl #{url_request}` 的命令:

    attrib

    我发现虽然这种方法不如]难忘>

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

    system("thecommand")
    

    反引号中,与其他方法相比,该方法的优点是反引号似乎不让我`thecommand` 我运行的命令/将要运行的命令存储在变量中,并且puts似乎不让我获取输出,而此方法可以让我完成这两个操作东西,它使我可以独立访问stdin,stdout和stderr。

    请参见“ system("thecommand")”和Executing commands in ruby

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

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

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

    Ruby's Open3 documentation

    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()将在使用TK和rubyw时都弹出该烦人的窗口。

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

    spawn()

    24
    投票

    另一个选择:


    23
    投票

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


    20
    投票

    上面的答案已经很不错了,但是我真的很想分享以下摘要文章:“ 6 Ways to Run Shell Commands in Ruby

    © www.soinside.com 2019 - 2024. All rights reserved.