题
如何从 Ruby 程序内部调用 shell 命令?然后我如何将这些命令的输出返回到 Ruby 中?
解决方案
此解释基于评论 红宝石脚本 来自我的一个朋友。如果您想改进脚本,请随时在链接中更新它。
首先,请注意,当 Ruby 调用 shell 时,它通常会调用 /bin/sh
, 不是 猛击。某些 Bash 语法不受支持 /bin/sh
在所有系统上。
执行 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 命令的结果,就像反引号一样。
文件: http://www.ruby-doc.org/docs/ProgrammingRuby/html/language.html
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
更多阅读请参阅:
其他提示
这是一个基于的流程图 这个答案. 。也可以看看, 使用 script
模拟终端.
我喜欢这样做的方式是使用 %x
文字,这使得在命令中使用引号变得容易(并且可读!),如下所示:
directorylist = %x[find . -name '*test.rb' | sort]
在这种情况下,将使用当前目录下的所有测试文件填充文件列表,您可以按预期处理这些文件:
directorylist.each do |filename|
filename.chomp!
# work with file
end
这是我认为关于在 Ruby 中运行 shell 脚本的最好的文章:”在 Ruby 中运行 Shell 命令的 6 种方法".
如果您只需要获取输出,请使用反引号。
我需要更高级的东西,比如 STDOUT 和 STDERR,所以我使用了 Open4 gem。那里解释了所有方法。
我最喜欢的是 开放3
require "open3"
Open3.popen3('nroff -man') { |stdin, stdout, stderr| ... }
在这些机制之间进行选择时需要考虑的一些事项是:
- 您只是想要Stdout还是也需要STDERR?甚至分开?
- 你的输出有多大?您想将整个结果保存在内存中吗?
- 您想在子过程仍在运行时阅读一些输出吗?
- 您需要结果代码吗?
- 您是否需要一个代表该过程的红宝石对象并让您按需杀死它?
您可能需要任何东西,从简单的反引号 (``)、system() 到 IO.popen
到成熟的 Kernel.fork
/Kernel.exec
和 IO.pipe
和 IO.select
.
如果子进程执行时间太长,您可能还想加入超时。
不幸的是,它非常 依靠.
我绝对不是 Ruby 专家,但我会尝试一下:
$ irb
system "echo Hi"
Hi
=> true
您还应该能够执行以下操作:
cmd = 'ls'
system(cmd)
上面的答案已经很好了,但我真的想分享以下总结文章:”在 Ruby 中运行 Shell 命令的 6 种方法"
基本上,它告诉我们:
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>]
如果您确实需要 Bash,请按照“最佳”答案中的注释。
首先,请注意,当 Ruby 调用 shell 时,它通常会调用
/bin/sh
, 不是 猛击。某些 Bash 语法不受支持/bin/sh
在所有系统上。
如果需要使用 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")
) 红宝石 应该 尊重 shebang,但你总是可以使用 system("bash ./my_script.sh")
以确保(尽管可能会产生轻微的开销) /bin/sh
跑步 /bin/bash
, ,你可能不会注意到。
您还可以使用反引号运算符 (`),类似于 Perl:
directoryListing = `ls /`
puts directoryListing # prints the contents of the root directory
如果您需要一些简单的东西,这会很方便。
您想要使用哪种方法取决于您想要实现的目标;查看文档以获取有关不同方法的更多详细信息。
最简单的方法是,例如:
reboot = `init 6`
puts reboot
使用此处的答案并链接到 Mihai 的答案,我组合了一个满足这些要求的函数:
- 整齐地捕获 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"
我们可以通过多种方式实现这一目标。
使用 Kernel#exec
, ,执行此命令后没有任何内容:
exec('ls ~')
使用 backticks or %x
`ls ~`
=> "Applications\nDesktop\nDocuments"
%x(ls ~)
=> "Applications\nDesktop\nDocuments"
使用 Kernel#system
命令,返回 true
如果成功的话 false
如果不成功并返回 nil
如果命令执行失败:
system('ls ~')
=> true
不要忘记 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()
这里. 。这似乎是最通用/功能最齐全的 库存红宝石 执行外部命令。
例如。你可以用它来:
- 创建进程组 (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)
反引号 ` 方法是从 ruby 调用 shell 命令的最简单的方法。它返回 shell 命令的结果。
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") 或反引号中的命令,与其他方法相比,此方法的优点是..例如反引号似乎不允许我“放置”我运行的命令/存储我想要在变量中运行的命令,并且 system("thecommand") 似乎不允许我获得输出。而这个方法可以让我做这两件事,并且它可以让我独立访问 stdin、stdout 和 stderr。
https://blog.bigbinary.com/2012/10/18/backtick-system-exec-in-ruby.html
http://ruby-doc.org/stdlib-2.4.1/libdoc/open3/rdoc/Open3.html
不是真正的答案,但也许有人会发现这很有用,并且与此有关。
当在 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时都会弹出那个烦人的窗口。
这是我在 OS X 上的 ruby 脚本中使用的一个很酷的脚本(这样即使在离开窗口后我也可以启动脚本并获得更新):
cmd = %Q|osascript -e 'display notification "Server was reset" with title "Posted Update"'|
system ( cmd )