如何从 Ruby 调用 shell 命令
Posted
技术标签:
【中文标题】如何从 Ruby 调用 shell 命令【英文标题】:How to call shell commands from Ruby 【发布时间】:2010-09-05 08:36:31 【问题描述】:如何从 Ruby 程序内部调用 shell 命令?然后如何将这些命令的输出返回到 Ruby?
【问题讨论】:
虽然这个问题很有用,但问得不好。 Ruby 有许多调用子shell 的方法,这些方法都有很好的文档记录,通过阅读Kernel 和Open3 文档并在SO 上搜索即可轻松找到。 遗憾的是,这个话题相当复杂。Open3
(docs) 是大多数情况下的最佳选择,IMO,但在旧版本的 Ruby 上,它不会尊重修改后的 PATH
(bugs.ruby-lang.org/issues/8004),这取决于你如何传递参数(特别是,如果您使用带有非关键字的 opts 哈希),它可能会中断。但是,如果你遇到了这些情况,那么你正在做一些非常高级的事情,你可以通过阅读 Open3
的实现来弄清楚该怎么做。
我很惊讶没有人提到Shellwords.escape
(doc)。您不想将用户输入直接插入到 shell 命令中 - 首先将其转义!另见command injection。
【参考方案1】:
您可以使用下面的format
方法打印一些信息:
puts format('%s', `ps`)
puts format('%d MB', (`ps -o rss= -p #Process.pid`.to_i / 1024))
【讨论】:
【参考方案2】:不确定 shell 命令。我使用以下方法将系统命令的输出捕获到变量 val:
val = capture(:stdout) do
system("pwd")
end
puts val
缩短版:
val = capture(:stdout) system("pwd")
capture方法由active_support/core_ext/kernel/reporting.rb
提供同样,我们也可以使用:stderr
捕获标准错误
【讨论】:
【参考方案3】:此解释基于我的一位朋友对Ruby script 的评论。如果您想改进脚本,请随时通过链接更新。
首先,请注意,当 Ruby 调用 shell 时,它通常调用 /bin/sh
,不是 Bash。 /bin/sh
并非在所有系统上都支持某些 Bash 语法。
以下是执行 shell 脚本的方法:
cmd = "echo 'hi'" # Sample string that can be used
Kernel#`
,俗称反引号——`cmd`
这与许多其他语言一样,包括 Bash、php 和 Perl。
返回 shell 命令的结果(即标准输出)。
文档:http://ruby-doc.org/core/Kernel.html#method-i-60
value = `echo 'hi'`
value = `#cmd`
内置语法,%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 ]
Kernel#system
在子shell中执行给定的命令。
如果找到并成功运行命令,则返回true
,否则返回false
。
文档:http://ruby-doc.org/core/Kernel.html#method-i-system
wasGood = system( "echo 'hi'" )
wasGood = system( cmd )
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
,则访问最后一个系统执行命令的状态。
然后您可以访问exitstatus
和pid
属性:
$?.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【讨论】:
我需要在生产服务器上记录我的可执行文件的输出,但没有找到办法。我使用 puts#cmd
和 logger.info(#cmd
)。有没有办法在生产中记录他们的输出?
还有 IO#popen() 和 Open3#popen3()。 mentalized.net/journal/2010/03/08/…
为了完整起见(我最初认为这也是一个 Ruby 命令):Rake 具有 sh,它执行“运行系统命令 cmd
。如果给出多个参数该命令不与 shell 一起运行(与 Kernel::exec 和 Kernel::system 语义相同)”。
默认情况下,反引号不会捕获 STDERR。如果要捕获,请将 `2>&1` 添加到命令
如果它说反引号和 %x 返回给定命令的“输出”而不是“结果”,我认为这个答案会略有改进。后者可能被误认为是退出状态。还是只有我一个人?【参考方案4】:
这不是一个真正的答案,但也许有人会觉得它有用:
在 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 时都会弹出那个烦人的窗口。
【讨论】:
【参考方案5】:给定一个类似attrib
的命令:
require 'open3'
a="attrib"
Open3.popen3(a) do |stdin, stdout, stderr|
puts stdout.read
end
我发现虽然这种方法不如
system("thecommand")
或
`thecommand`
在反引号中,与其他方法相比,这种方法的一个好处是
反引号似乎不允许我 puts
运行命令/存储我想在变量中运行的命令,而 system("thecommand")
似乎不允许我获得输出,而这种方法让我可以做到这两个东西,它让我可以独立访问标准输入、标准输出和标准错误。
参见“Executing commands in ruby”和Ruby's Open3 documentation。
【讨论】:
【参考方案6】:反引号 (`) 方法是从 Ruby 调用 shell 命令的最简单方法。它返回 shell 命令的结果:
url_request = 'http://google.com'
result_of_shell_command = `curl #url_request`
【讨论】:
【参考方案7】:如果您有比普通情况更复杂且无法使用``
处理的情况,请查看Kernel.spawn()
。这似乎是 Ruby 提供的用于执行外部命令的最通用/功能最全面的。
你可以用它来:
创建进程组 (Windows)。 将输入、输出、错误重定向到文件/彼此。 设置环境变量、umask。 在执行命令之前更改目录。 为 CPU/数据/等设置资源限制。 做所有可以在其他答案中使用其他选项完成的事情,但需要更多代码。Ruby documentation 有足够好的例子:
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)
【讨论】:
【参考方案8】:最简单的方法是,例如:
reboot = `init 6`
puts reboot
【讨论】:
【参考方案9】:如果您真的需要 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
可能会产生轻微的开销,但您可能不会注意到。
【讨论】:
【参考方案10】:在这些机制之间进行选择时需要考虑的一些事项是:
-
你只是想要标准输出还是你想要
还需要标准错误吗?甚至
分开了吗?
您的输出有多大?你想要
将整个结果保存在内存中?
您想阅读一些您的
在子进程仍然存在时输出
跑步吗?
您需要结果代码吗?
您需要一个 Ruby 对象吗?
代表过程并让您
按需杀死它?
您可能需要任何东西,从简单的反引号 (``)、system()
和 IO.popen
到完整的 Kernel.fork
/Kernel.exec
和 IO.pipe
和 IO.select
。
如果子流程执行时间过长,您可能还想在混合中加入超时。
不幸的是,它非常取决于。
【讨论】:
【参考方案11】:这是一个基于“When to use each method of launching a subprocess in Ruby”的流程图。另请参阅“Trick an application into thinking its stdout is a terminal, not a pipe”。
【讨论】:
哇哈哈。非常有用,即使它必须存在的事实是不幸的 附带说明,我发现 spawn() 方法在许多不同的地方(例如Kernel
和 Process
是最通用的。它或多或少与 PTY.spawn()
相同,但是更通用。
IMO、反引号和%x
仅适用于编写脚本,因为它们不会将 stderr 返回给您,并且您无法向它们传递参数(请注意,您可以使用 $?
来获取退出状态)。所以我几乎总是使用Open3.capture3
而不是反引号或%x
【参考方案12】:
上面的答案已经很不错了,但是我真的很想分享以下总结文章:“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
-- 标准库:
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>]
【讨论】:
【参考方案13】:不要忘记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()
似乎比所有其他选项更通用。【参考方案14】:
这是我在 OS X 上的 ruby 脚本中使用的一个很酷的脚本(这样即使在离开窗口后我也可以启动脚本并获得更新):
cmd = %Q|osascript -e 'display notification "Server was reset" with title "Posted Update"'|
system ( cmd )
【讨论】:
【参考方案15】:我绝对不是 Ruby 专家,但我会试一试:
$ irb
system "echo Hi"
Hi
=> true
您还应该能够执行以下操作:
cmd = 'ls'
system(cmd)
【讨论】:
【参考方案16】:这是我认为关于在 Ruby 中运行 shell 脚本的最佳文章:“6 Ways to Run Shell Commands in Ruby”。
如果您只需要获取输出,请使用反引号。
我需要更高级的东西,比如 STDOUT 和 STDERR,所以我使用了 Open4 gem。那里解释了所有方法。
【讨论】:
这里描述的帖子没有讨论%x
语法选项。
+1 表示 Open4。当我发现这个时,我已经开始尝试实现我自己版本的 spawn
方法。【参考方案17】:
另一种选择:
当你:
需要标准错误和标准输出 不能/不会使用 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。
【讨论】:
【参考方案18】:我们可以通过多种方式实现它。
使用Kernel#exec
,这条命令执行后什么都没有:
exec('ls ~')
使用backticks or %x
`ls ~`
=> "Applications\nDesktop\nDocuments"
%x(ls ~)
=> "Applications\nDesktop\nDocuments"
使用Kernel#system
命令,成功返回true
,失败返回false
,命令执行失败返回nil
:
system('ls ~')
=> true
【讨论】:
【参考方案19】:使用这里的答案和米海的答案中的链接,我整理了一个满足这些要求的函数:
-
整齐地捕获 STDOUT 和 STDERR,因此当我的脚本从控制台运行时它们不会“泄漏”。
允许将参数作为数组传递给 shell,因此无需担心转义。
捕获命令的退出状态,以便在发生错误时一目了然。
作为奖励,如果 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"
【讨论】:
【参考方案20】:我喜欢这样做的方式是使用 %x
文字,这使得在命令中使用引号变得容易(并且可读!),如下所示:
directorylist = %x[find . -name '*test.rb' | sort]
在这种情况下,它将使用当前目录下的所有测试文件填充文件列表,您可以按预期进行处理:
directorylist.each do |filename|
filename.chomp!
# work with file
end
【讨论】:
%x[ cmd ]
是否向您返回一个数组?
以上对我不起作用。 ``each' for :String (NoMethodError)
它是如何为你工作的?我正在使用ruby -v ruby 1.9.3p484 (2013-11-22 revision 43786) [i686-linux]
你确定从命令返回一个数组以便循环实际工作吗?我最喜欢的是Open3
require "open3"
Open3.popen3('nroff -man') |stdin, stdout, stderr| ...
【讨论】:
我也喜欢open3,尤其是Open3.capture3:ruby-doc.org/stdlib-1.9.3/libdoc/open3/rdoc/… ->stdout, stderr, status = Open3.capture3('nroff -man', :stdin_data => stdin)
是否有关于如何使用 Open3 或 Ruby 标准库中的其他 Open 执行规范和单元测试的文档?以我目前的理解水平很难测试外壳。
open3和open4有什么区别?【参考方案22】:
您也可以使用反引号运算符 (`),类似于 Perl:
directoryListing = `ls /`
puts directoryListing # prints the contents of the root directory
如果你需要一些简单的东西会很方便。
您要使用哪种方法取决于您要完成的具体任务;查看文档以了解有关不同方法的更多详细信息。
【讨论】:
以上是关于如何从 Ruby 调用 shell 命令的主要内容,如果未能解决你的问题,请参考以下文章
如何在命令行上使用相同的命令,通过Ruby shell命令运行app