从Ruby调用shell命令

Cod*_*nts 1024 ruby shell interop

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

Ste*_*ard 1280

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

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

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

cmd = "echo 'hi'" # Sample string that can be used
Run Code Online (Sandbox Code Playgroud)
  1. Kernel#` ,通常称为反引号 - `cmd`

    这和许多其他语言一样,包括Bash,PHP和Perl.

    返回shell命令的结果.

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

    value = `echo 'hi'`
    value = `#{cmd}`
    
    Run Code Online (Sandbox Code Playgroud)
  2. 内置语法, %x( cmd )

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

    返回shell命令的结果,就像反引号一样.

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

    value = %x( echo 'hi' )
    value = %x[ #{cmd} ]
    
    Run Code Online (Sandbox Code Playgroud)
  3. Kernel#system

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

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

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

    wasGood = system( "echo 'hi'" )
    wasGood = system( cmd )
    
    Run Code Online (Sandbox Code Playgroud)
  4. Kernel#exec

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

    返回none,当前进程被替换并且永远不会继续.

    文档: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
    
    Run Code Online (Sandbox Code Playgroud)

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

$?.exitstatus
Run Code Online (Sandbox Code Playgroud)

如需更多阅读,请参阅

  • 反引号默认不捕获STDERR.如果要捕获,请将"2>&1"附加到命令 (40认同)
  • 我认为如果说反引号和%x返回给定命令的"输出"而不是"结果",这个答案会略有改善.后者可能被误认为退出状态.还是那只是我? (14认同)
  • 为了完整起见(我最初认为这也是一个Ruby命令):[Rake](http://rake.rubyforge.org/)有[sh](http://rake.rubyforge.org/classes /FileUtils.html#M000018)执行"运行系统命令`cmd`.如果给出多个参数,则命令不与shell一起运行(与Kernel :: exec和Kernel :: system相同的语义)". (6认同)
  • 而IO#popen()和Open3#popen3().http://mentalized.net/journal/2010/03/08/5_ways_to_run_commands_from_ruby/ (5认同)
  • 我需要在生产服务器上记录我的可执行文件的输出,但没有找到办法.我使用了put`#{cmd}`和logger.info(`#{cmd}`).有没有办法在生产中记录他们的产出? (4认同)
  • 此外,[此博客文章](https://makandracards.com/makandra/1243-execution-of-shell-code-in-ruby-scripts)还包括`spawn`和`open3`. (2认同)
  • 这是一个(好的?)示例,说明如何将spawn与ruby程序中的嵌入式shell脚本结合使用:https://github.com/baitisj/spark-ping/blob/master/spark-ping查看第76行 - 94. (2认同)

Ian*_*Ian 248

这是基于此答案的流程图.也参见,使用__CODE__以模拟终端.

在此输入图像描述

  • 哇哈哈.尽管这必须存在,但非常有用,这是非常不幸的 (19认同)

cyn*_*man 157

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

directorylist = %x[find . -name '*test.rb' | sort]
Run Code Online (Sandbox Code Playgroud)

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

directorylist.each do |filename|
  filename.chomp!
  # work with file
end
Run Code Online (Sandbox Code Playgroud)

  • `%x [cmd]`是否为您返回一个数组? (4认同)
  • 以上对我不起作用。``&lt;main&gt;'::String(NoMethodError)`的未定义方法`each'对您有什么作用?我正在使用`ruby -v ruby​​ 1.9.3p484(2013-11-22修订版43786)[i686-linux]`您确定从命令返回了一个数组,这样循环才能真正起作用吗? (2认同)

Mih*_*i A 62

这是我在Ruby中运行shell脚本的最佳文章:"在Ruby 中运行Shell命令的6种方法 ".

如果你只需要输出使用反引号.

我需要更高级的东西,如STDOUT和STDERR,所以我使用了Open4 gem.你有解释的所有方法.

  • 这里描述的文章没有讨论%x语法选项。 (2认同)

ans*_*hul 36

我最喜欢的是Open3

  require "open3"

  Open3.popen3('nroff -man') { |stdin, stdout, stderr| ... }
Run Code Online (Sandbox Code Playgroud)

  • 我也喜欢open3,尤其是Open3.capture3:http://www.ruby-doc.org/stdlib-1.9.3/libdoc/open3/rdoc/Open3.html#method-c-capture3-&gt;`stdout,stderr,状态= Open3.capture3('nroff -man',:stdin_data =&gt; stdin)` (2认同)

小智 26

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

  1. 你想要stdout还是需要stderr?甚至分开了?
  2. 你的产量有多大?你想把整个结果保存在记忆中吗?
  3. 在子进程仍在运行时,是否要读取一些输出?
  4. 你需要结果代码吗?
  5. 您是否需要一个代表该过程的ruby对象并让您按需杀死它?

你可能需要从简单的反引号(``),system()IO.popen到full-blown Kernel.fork/ Kernel.execwith IO.pipeIO.select.

如果子进程执行时间过长,您可能还希望将超时投入到混合中.

不幸的是,这非常取决于.


j-g*_*tus 23

还有一个选择:

当你:

  • 需要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"
Run Code Online (Sandbox Code Playgroud)

从早期的MS-DOS开始,该2>&1语法适用于Linux,Mac和Windows.


Ste*_*ard 22

我绝对不是Ruby专家,但我会试一试:

$ irb 
system "echo Hi"
Hi
=> true
Run Code Online (Sandbox Code Playgroud)

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

cmd = 'ls'
system(cmd)
Run Code Online (Sandbox Code Playgroud)


小智 18

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

基本上,它告诉我们:

Kernel#exec:

exec 'echo "hello $HOSTNAME"'
Run Code Online (Sandbox Code Playgroud)

system并且$?:

system 'false' 
puts $?
Run Code Online (Sandbox Code Playgroud)

反引号(`):

today = `date`
Run Code Online (Sandbox Code Playgroud)

IO#popen:

IO.popen("date") { |f| puts f.gets }
Run Code Online (Sandbox Code Playgroud)

Open3#popen3 - stdlib:

require "open3"
stdin, stdout, stderr = Open3.popen3('dc') 
Run Code Online (Sandbox Code Playgroud)

Open4#popen4 - 宝石:

require "open4" 
pid, stdin, stdout, stderr = Open4::popen4 "false" # => [26327, #<IO:0x6dff24>, #<IO:0x6dfee8>, #<IO:0x6dfe84>]
Run Code Online (Sandbox Code Playgroud)


dra*_*788 13

如果你真的需要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,您可能不会注意到.


Ruf*_*hez 9

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

directoryListing = `ls /`
puts directoryListing # prints the contents of the root directory
Run Code Online (Sandbox Code Playgroud)

如果你需要简单的东西,很方便.

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


nkm*_*nkm 8

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

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

exec('ls ~')
Run Code Online (Sandbox Code Playgroud)

运用 backticks or %x

`ls ~`
=> "Applications\nDesktop\nDocuments"
%x(ls ~)
=> "Applications\nDesktop\nDocuments"
Run Code Online (Sandbox Code Playgroud)

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

system('ls ~')
=> true
Run Code Online (Sandbox Code Playgroud)


Rya*_*ate 8

使用这里的答案并在Mihai的答案中链接,我整理了一个满足这些要求的功能:

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

作为奖励,如果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"
Run Code Online (Sandbox Code Playgroud)


小智 8

最简单的方法是,例如:

reboot = `init 6`
puts reboot
Run Code Online (Sandbox Code Playgroud)


Mon*_*art 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
Run Code Online (Sandbox Code Playgroud)

该文档说:这种方法类似#system但不等待命令完成.

  • `Kernel.spawn()`似乎比其他所有选项都通用。 (2认同)

bar*_*lop 7

给出如下命令attrib

require 'open3'

a="attrib"
Open3.popen3(a) do |stdin, stdout, stderr|
  puts stdout.read
end
Run Code Online (Sandbox Code Playgroud)

我发现虽然这种方法不像

system("thecommand")
Run Code Online (Sandbox Code Playgroud)

或者

`thecommand`
Run Code Online (Sandbox Code Playgroud)

在反引号中,与其他方法相比,这种方法的好处是反引号似乎不允许puts我运行/存储我想在变量中运行的命令,并且system("thecommand")似乎不允许我获得输出,而这个方法让我可以做这两件事,它让我独立访问 stdin、stdout 和 stderr。

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


Kas*_*yap 5

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

例如,您可以使用它来:

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

官方的红宝石文档有足够好的例子。

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)
Run Code Online (Sandbox Code Playgroud)


ysk*_*ysk 5

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

     url_request = 'http://google.com'
     result_of_shell_command = `curl #{url_request}`
    
    Run Code Online (Sandbox Code Playgroud)