如何从Ruby程序内部调用shell命令?然后如何将这些命令的输出返回Ruby?
这个解释是基于我朋友的一个评论过的Ruby脚本。如果您想改进脚本,请随时在链接中更新它。
首先,请注意,当Ruby调用shell时,它通常调用/bin/sh,而不是bash。在所有系统上,/bin/sh不支持某些bash语法。
以下是执行shell脚本的方法:
1
| cmd ="echo 'hi'" # Sample string that can be used |
Kernel#`,通常称为backticks——`cmd`。
这和许多其他语言一样,包括bash、php和perl。
返回shell命令的结果。
文档:http://ruby doc.org/core/kernel.html method-i-60
1 2
| value = `echo 'hi'`
value = `#{cmd}` |
内置语法,%x( cmd )。
在x字符之后是一个分隔符,可以是任何字符。如果分隔符是(、[、{或<字符之一,文字由匹配结束分隔符之前的字符组成,考虑到嵌套的分隔符对。对于所有其他分隔符,文字包含字符,直到下一次出现分隔符字符。允许字符串插入#{ ... }。
返回shell命令的结果,就像反勾号一样。
文档:http://www.ruby-doc.org/docs/programmingruby/html/language.html
1 2
| value = %x( echo 'hi' )
value = %x[ #{cmd} ] |
Kernel#system
在子shell中执行给定的命令。
返回true,如果找到命令并成功运行,则返回false。
文档:http://ruby doc.org/core/kernel.html method-i-system
1 2
| wasGood = system("echo 'hi'" )
wasGood = system( cmd ) |
Kernel#exec
通过运行给定的外部命令替换当前进程。
返回none,当前进程将被替换,并且永不继续。
文档:http://ruby doc.org/core/kernel.html method-i-exec
1 2
| exec("echo 'hi'" )
exec( cmd ) # Note: this will never be reached because of the line above |
以下是一些额外的建议:$?与$CHILD_STATUS相同,如果使用backticks、system()或%x{}访问最后一个系统执行命令的状态。然后可以访问exitstatus和pid属性:
欲了解更多信息,请参阅:
- 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
这是基于这个答案的流程图。另请参见,使用script模拟终端。

我喜欢这样做的方式是使用%x字面值,这使得它很容易(并且可读!)要在命令中使用引号,请执行以下操作:
1
| directorylist = %x[find . -name '*test.rb' | sort] |
在本例中,它将用当前目录下的所有测试文件填充文件列表,您可以按预期处理这些文件:
1 2 3 4
| directorylist.each do |filename|
filename.chomp!
# work with file
end |
下面是我对在Ruby中运行shell脚本的看法最好的一篇文章:"在Ruby中运行shell命令的6种方法"。
如果只需要获取输出,请使用倒计时。
我需要更高级的东西,比如stdout和stderr,所以我使用了open4 gem。你已经解释了所有的方法。
我最喜欢的是Open3
1 2 3
| require"open3"
Open3.popen3('nroff -man') { |stdin, stdout, stderr| ... } |
在这些机制之间进行选择时需要考虑的一些事情是:
你只是想要性传播疾病还是还需要stderr?甚至分出?
你的产量有多大?你想要把整个结果保存在内存中?
你想看看你的子进程仍在时输出跑步?
是否需要结果代码?
你需要一个Ruby对象吗?表示进程并允许您随叫随到?
您可能需要任何东西,从简单的backticks(``)、system()和IO.popen到全面的Kernel.fork/Kernel.exec和IO.pipe和IO.select。
如果子进程执行时间太长,您可能还希望将超时值抛出到组合中。
不幸的是,这要看情况而定。
还有一个选择:
当你:
- 需要stderr和stdout
- 不能/不会使用Open3/Open4(他们在我的Mac上的NetBeans中抛出异常,不知道为什么)
可以使用shell重定向:
1 2 3 4 5 6
| puts %x[cat bogus.txt].inspect
=>""
puts %x[cat bogus.txt 2>&1].inspect
=>"cat: bogus.txt: No such file or directory\
" |
从MS-DOS早期开始,2>&1语法就适用于Linux、Mac和Windows。
我绝对不是红宝石专家,但我会试一试:
1 2 3 4
| $ irb
system"echo Hi"
Hi
=> true |
您还应该能够执行以下操作:
上面的答案已经很好了,但是我真的想分享下面的摘要文章:"在Ruby中运行shell命令的6种方法"
基本上,它告诉我们:
Kernel#exec:
1
| exec 'echo"hello $HOSTNAME"' |
system和$?:
Backticks(:):
IO#popen:
1
| IO.popen("date") { |f| puts f.gets } |
Open3#popen3—标准库:
1 2
| require"open3"
stdin, stdout, stderr = Open3.popen3('dc') |
Open4#popen4——宝石:
1 2
| require"open4"
pid, stdin, stdout, stderr = Open4::popen4"false" # => [26327, #<IO:0x6dff24>, #<IO:0x6dfee8>, #<IO:0x6dfe84>] |
如果你真的需要bash,按照"最佳"答案中的注释。
First, note that when Ruby calls out to a shell, it typically calls /bin/sh, not Bash. Some Bash syntax is not supported by /bin/sh on all systems.
如果需要使用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可能会有一些开销),您可能不会注意到。
您还可以使用反勾号操作符(`),与Perl类似:
1 2
| directoryListing = `ls /`
puts directoryListing # prints the contents of the root directory |
如果你需要一些简单的东西,很方便。
您希望使用哪种方法取决于您正试图完成的具体工作;请查看文档以了解有关不同方法的更多详细信息。
最简单的方法是,例如:
1 2
| reboot = `init 6`
puts reboot |
使用这里的答案并链接到mihai的答案中,我构建了一个满足这些要求的函数:
巧妙地捕获stdout和stderr,这样在从控制台运行脚本时它们就不会"泄漏"。
允许将参数作为数组传递给shell,因此无需担心转义。
捕获命令的退出状态,以便在发生错误时清除。
另外,在shell命令成功退出(0)并将任何内容放入stdout的情况下,此命令还将返回stdout。在这种情况下,它不同于system,后者只返回true。
代码如下。具体功能为system_quietly:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| 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等待其完成:
1 2 3 4 5
| pid = spawn("tar xf ruby-2.0.0-p195.tar.bz2")
Process.wait pid
pid = spawn(RbConfig.ruby,"-eputs'Hello, world!'")
Process.wait pid |
Doc说:这个方法类似于#system,但它不等待命令完成。
我们可以通过多种方式实现。
使用Kernel#exec,执行此命令后不执行任何操作:
使用backticks or %x。
1 2 3 4 5 6 7 8
| `ls ~`
=>"Applications\
Desktop\
Documents"
%x(ls ~)
=>"Applications\
Desktop\
Documents" |
使用Kernel#system命令,成功返回true,失败返回false,命令执行失败返回nil:
如果您的案件比普通案件更复杂(不能用``处理),请在这里查看Kernel.spawn()。这似乎是StockRuby为执行外部命令而提供的最通用/最全面的功能。
例如,您可以使用它来:
- 创建进程组(Windows)
- 将入、出、错误重定向到文件/彼此。
- 设置env vars,umask
- 执行命令前更改目录
- 设置CPU/data/的资源限制。
- 做所有可以用其他答案中的其他选项完成的事情,但要用更多的代码。
官方的Ruby文档有足够好的例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| 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) |
给定一个橡木ATTRIB命令
1 2 3 4 5 6
| require 'open3'
a="attrib"
Open3.popen3(a) do |stdin, stdout, stderr|
puts stdout.read
end |
在这里发现的,虽然这个方法不memorable AA AA(如系统的"命令"在命令提示符)或向后的记号,一个好的事关于这两种方法相对其他方法。例如冰 向后的记号不似乎很容易推我的命令在命令行运行大的/我想运行在一个变量和系统("命令")不似乎很容易的输出。这个方法的尽头都让我做的那些事情,这让我访问stdin,stdout和stderr independently。
http:/ / / / blog.bigbinary.com 2012年10月18日backtick-system-exec-in-ruby.html
http:/ / / stdlib - ruby-doc.org 2.4.1 libdoc / / / / open3.html RDoc open3
没有真正的答案,但这也许有人会找到有用的和它对这两种。
当Windows GUI用TK和美国在线,需要呼叫的壳从rubyw commands,你始终有一个annoying CMD窗口弹出IP较小的那一秒。
这两个你可以避免使用
1
| WIN32OLE.new('Shell.Application').ShellExecute('ipconfig > log.txt','','','open',0) |
或
1
| WIN32OLE.new('WScript.Shell').Run('ipconfig > log.txt',0,0) |
将两个巨大的输出地址内的log.txt’,但没有Windows将来的IP。
你将需要你的require 'win32ole'里面的脚本。
system(),exec()全鸭spawn()会弹出窗口,annoying rubyw TK和当用。
这是一个很酷的脚本,我在OSX上的Ruby脚本中使用(这样我就可以启动一个脚本并获取更新,即使在切换到远离窗口的位置之后):
1 2
| cmd = %Q|osascript -e 'display notification"Server was reset" with title"Posted Update"'|
system ( cmd ) |