如何从Ruby调用shell命令

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

619948 次浏览

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

$ irbsystem "echo Hi"Hi=> true

你也应该能够做这样的事情:

cmd = 'ls'system(cmd)

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

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

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

您要使用哪种方法取决于您要完成的任务;有关不同方法的更多详细信息,请查看文档。

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

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

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

directorylist.each do |filename|filename.chomp!# work with fileend

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

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

以下是执行外壳脚本的方法:

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. Built-in syntax, %x( cmd )

    Following the x character is a delimiter, which can be any character.If the delimiter is one of the characters (, [, {, or <,the literal consists of the characters up to the matching closing delimiter,taking account of nested delimiter pairs. For all other delimiters, theliteral comprises the characters up to the next occurrence of thedelimiter character. String interpolation #{ ... } is allowed.

    Returns the result (i.e. standard output) of the shell command, just like the backticks.

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

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

    Executes the given command in a subshell.

    Returns true if the command was found and run successfully, false otherwise.

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

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

    Replaces the current process by running the given external command.

    Returns none, the current process is replaced and never continues.

    Docs: 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

Here's some extra advice:$?, which is the same as $CHILD_STATUS, accesses the status of the last system executed command if you use the backticks, system() or %x{}.You can then access the exitstatus and pid properties:

$?.exitstatus

更多阅读请见:

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

  1. 你只是想要标准还是你还需要stderr吗?甚至分手了?
  2. 你的产量有多大?你想将整个结果保存在内存中?
  3. 你想读一些你的子进程仍然存在时的输出跑?
  4. 您需要结果代码吗?
  5. 你需要一个Ruby对象吗代表过程并让您按需杀死它?

您可能需要从简单的反向标记 (``), system()IO.popen到完整的Kernel.fork/Kernel.execIO.pipeIO.select

如果子进程需要太长时间才能执行,您可能还希望将超时抛入混合中。

不幸的是,它非常取决于

这是我认为在Ruby中运行shell脚本的最佳文章:“在Ruby中运行Shell命令的6种方法”。

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

我需要更高级的东西,比如STDOUT和STDERR,所以我使用了Open4 gem。

我的最爱Open3

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

还有一个选择:

当你:

  • 需要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

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

使用Kernel#exec,执行此命令后没有任何内容:

exec('ls ~')

使用backticks or %x

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

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

system('ls ~')=> true

使用这里的答案和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=nilerr=nilout=nilOpen3.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.valueendif exit_status.to_i > 0err = err.chomp if errraise ShellError, errelsif outreturn out.chompelsereturn trueendend
#calling it:beginputs system_quietly('which', 'ruby')rescue ShellErrorabort "Looks like you don't have the `ruby` command. Odd."end
#output: => "/Users/me/.rvm/rubies/ruby-1.9.2-p136/bin/ruby"

上面的答案已经相当棒了,但我真的想分享以下总结文章:“在Ruby中运行Shell命令的6种方法

基本上,它告诉我们:

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--一个gem:

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

这是我在OS X上的ruby脚本中使用的一个很酷的脚本(这样我就可以启动脚本并在离开窗口后获得更新):

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

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

如果您有比普通情况更复杂的情况无法用``处理,请查看#1。这似乎是库存Ruby提供的最通用/功能最齐全的执行外部命令。

您可以使用它来:

  • 创建进程组(Windows)。
  • 将输入、输出、错误重定向到文件/彼此。
  • 设置环境变量,umask。
  • 在执行命令之前更改目录。
  • 设置CPU/数据/等的资源限制。
  • 在其他答案中执行可以使用其他选项完成的所有操作,但使用更多代码。

ruby留档有足够好的例子:

env: hashname => val : set the environment variablename => nil : unset the environment variablecommand...:commandline                 : command line string which is passed to the standard shellcmdname, 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: hashclearing 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 => strumask::umask => intredirection:key:FD              : single file descriptor in child process[FD, FD, ...]   : multiple file descriptor in child processvalue:FD                        : redirect to the file descriptor in parent processstring                    : 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 processFD 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 errorinteger : the file descriptor of specified the integerio      : the file descriptor specified as io.filenofile 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命令的结果:

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

最简单的方法是,例如:

reboot = `init 6`puts reboot

如果你真的需要Bash,请在“最佳”答案中注明。

首先,请注意,当Ruby调用shell时,它通常会调用/bin/sh没有 Bash。并非所有系统都支持某些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应该尊重她,但你总是可以使用

system("bash ./my_script.sh")

为了确保,尽管从/bin/sh运行/bin/bash可能会有轻微的开销,但您可能不会注意到。

给定一个像attrib这样的命令:

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

我发现虽然这种方法不像

system("thecommand")

`thecommand`

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

见“在ruby中执行命令”和Ruby的Open3留档

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

在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'

使用TK和rubyw时,system()exec()spawn()都会弹出那个烦人的窗口。

不确定shell命令。我使用以下方法将系统命令的输出捕获到变量val中:

val = capture(:stdout) dosystem("pwd")end
puts val

缩短版:

val = capture(:stdout) { system("pwd") }

捕获方法由active_support/core_ext/内核/reporting.rb提供

同样,我们也可以使用:stderr捕获标准错误

您可以使用如下format方法打印一些信息:

puts format('%s', `ps`)puts format('%d MB', (`ps -o rss= -p #{Process.pid}`.to_i / 1024))