从Ruby调用shell命令
如何从Ruby程序内部调用shell命令? 那我如何从这些命令的输出回到Ruby?
这个解释是基于我朋友的一个评论Ruby脚本 。 如果您想改进脚本,请随时在链接中更新它。
首先,请注意,当Ruby调用一个shell时,通常会调用/bin/sh
而不是 Bash。 在所有系统上,某些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
字符之后是一个分隔符,可以是任何字符。 如果分隔符是其中一个字符(
[
,{
,或<
,则文字由直到匹配的结束分隔符的字符组成,考虑到嵌套的分隔符对于所有其他分隔符,文字包括直到下一次出现分隔符。string插值#{ ... }
是允许的。返回shell命令的结果,就像反引号一样。
文档: http : //www.ruby-doc.org/docs/ProgrammingRuby/html/language.html
value = %x( echo 'hi' ) value = %x[ #{cmd} ]
-
Kernel#system
在子shell中执行给定的命令。
如果find并成功运行该命令,则返回
true
,否则返回false
。文档: http : //ruby-doc.org/core/Kernel.html#method-i-system
wasGood = system( "echo 'hi'" ) wasGood = system( cmd )
-
Kernel#exec
通过运行给定的外部命令来replace当前进程。
没有返回,当前进程被replace,永远不会继续。
文档: 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
这里有一些额外的build议: $?
,与$CHILD_STATUS
相同,如果使用反引号system()
或%x{}
,则访问上一个系统执行的命令的状态。 然后可以访问exitstatus
和pid
属性:
$?.exitstatus
更多阅读请参阅:
我喜欢这样做的方式是使用%x
文字,这使得在命令中使用引号变得容易(并且易读!),如下所示:
directorylist = %x[find . -name '*test.rb' | sort]
在这种情况下,它将在当前目录下的所有testing文件中填充文件列表,您可以按预期进行处理:
directorylist.each do |filename| filename.chomp! # work with file end
这是基于这个答案的stream程图。 另请参阅使用script
来模拟terminal 。
以下是关于在Ruby中运行shell脚本的最佳文章:“在Ruby 中运行Shell命令的6种方法 ”。
如果你只需要得到输出使用反引号。
我需要更先进的东西,如STDOUT和STDERR,所以我使用了Open4的gem。 你在那里解释了所有的方法。
我最喜欢的是Open3
require "open3" Open3.popen3('nroff -man') { |stdin, stdout, stderr| ... }
在这些机制之间进行select时需要考虑的一些事情是:
- 你只是想标准input还是你需要标准input? 甚至分离出来?
- 你的输出有多大? 你想把整个结果保存在内存中吗?
- 当subprocess仍在运行时,是否想要读取一些输出?
- 你需要结果代码吗?
- 你需要一个代表这个过程的ruby物体,并且让它可以根据需要杀死它吗?
你可能需要从简单的反引号(“),system()和IO.popen
到Kernel.fork
和IO.popen
的全面的Kernel.fork
/ IO.select
。
如果一个subprocess执行时间过长,你也可能想把超时放入混合中。
不幸的是,它非常依赖 。
还有一个select:
当你:
- 需要stderr以及stdout
- 不能/不会使用Open3 / Open4(他们在我的Mac上抛出NetBeans中的exception,不知道为什么)
你可以使用shellredirect:
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上运行。
我绝对不是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
– 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>]
您也可以使用类似于Perl的反引号操作符(`):
directoryListing = `ls /` puts directoryListing # prints the contents of the root directory
方便,如果你需要一些简单的东西。
您要使用哪种方法取决于您要完成的工作; 请查看文档以获取有关不同方法的更多详细信息。
用这里的答案和Mihai的答案相联系,我把一个满足这些要求的函数放在一起:
- 整洁地捕捉STDOUT和STDERR,所以当我的脚本从控制台运行时,它们不会“泄漏”。
- 允许将参数作为数组传递给shell,所以不需要担心转义。
- 捕获命令的退出状态,以便在发生错误时清除。
作为奖励,如果shell命令成功退出(0)并将任何内容放在STDOUT上,这个函数还会返回STDOUT。 以这种方式,它不同于system
,在这种情况下,它只是简单地返回true
。
代码如下。 具体function是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
命令来创build后台进程来执行指定的命令。 您甚至可以使用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
但不等待命令完成。
如果你真的需要Bash,请按照“最好”答案中的注释。
首先,请注意,当Ruby调用一个shell时,通常会调用
/bin/sh
而不是 Bash。 在所有系统上,某些Bash语法不受/bin/sh
支持。
如果您需要使用Bash, bash -c "your Bash-only command"
在所需的调用方法内插入bash -c "your Bash-only command"
。
quick_output = system("ls -la")
quick_bash = system("bash -c 'ls -la'")
去testing:
system("echo $SHELL") system('bash -c "echo $SHELL"')
或者如果你正在运行一个已经存在的脚本文件(例如script_output = system("./my_script.sh")
),Ruby 应该尊重shebang,但是你总是可以使用system("bash ./my_script.sh")
来确保尽pipe从/bin/sh
运行/bin/bash
可能会有一些小的开销,但你可能不会注意到。
如果你有比普通情况更复杂的情况(不能用``
来处理),那么在这里查看Kernel.spawn()
。 这似乎是股票Ruby提供的最通用/全function的执行外部命令。
例如,您可以使用它来:
- 创build进程组(Windows)
- redirect到,出错,文件/其他。
- 设置envvariables,umask
- 在执行命令之前更改dir
- 设置CPU /数据/资源限制…
- 在其他答案中尽可能使用其他选项来完成所有工作,但需要更多的代码。
官方的ruby文档有足够好的例子。
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}`
最简单的方法是,例如:
reboot = `init 6` puts reboot
这是一个很酷的,我在OS X的ruby脚本中使用(以便我可以启动一个脚本,并从窗口切换后得到一个更新):
cmd = %Q|osascript -e 'display notification "Server was reset" with title "Posted Update"'| system ( cmd )