从Ruby调用shell命令

如何从Ruby程序内部调用shell命令? 那我如何从这些命令的输出回到Ruby?

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

首先,请注意,当Ruby调用一个shell时,通常会调用/bin/sh 而不是 Bash。 在所有系统上,某些Bash语法不受/bin/sh支持。

以下是执行shell脚本的方法:

 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. 内置语法, %x( cmd )

    x字符之后是一个分隔符,可以是任何字符。 如果分隔符是其中一个字符( [{ ,或< ,则文字由直到匹配的结束分隔符的字符组成,考虑到嵌套的分隔符对于所有其他分隔符,文字包括直到下一次出现分隔符。string插值#{ ... }是允许的。

    返回shell命令的结果,就像反引号一样。

    文档: http : //www.ruby-doc.org/docs/ProgrammingRuby/html/language.html

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

    在子shell中执行给定的命令。

    如果find并成功运行该命令,则返回true ,否则返回false

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

     wasGood = system( "echo 'hi'" ) wasGood = system( cmd ) 
  4. 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{} ,则访问上一个系统执行的命令的状态。 然后可以访问exitstatuspid属性:

 $?.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时需要考虑的一些事情是:

  1. 你只是想标准input还是你需要标准input? 甚至分离出来?
  2. 你的输出有多大? 你想把整个结果保存在内存中吗?
  3. 当subprocess仍在运行时,是否想要读取一些输出?
  4. 你需要结果代码吗?
  5. 你需要一个代表这个过程的ruby物体,并且让它可以根据需要杀死它吗?

你可能需要从简单的反引号(“),system()和IO.popenKernel.forkIO.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的答案相联系,我把一个满足这些要求的函数放在一起:

  1. 整洁地捕捉STDOUT和STDERR,所以当我的脚本从控制台运行时,它们不会“泄漏”。
  2. 允许将参数作为数组传递给shell,所以不需要担心转义。
  3. 捕获命令的退出状态,以便在发生错误时清除。

作为奖励,如果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 )