Calling shell commands from Ruby
如何从Ruby程序内部调用shell命令?然后如何将这些命令的输出返回Ruby?
这个解释是基于我朋友的一个评论过的Ruby脚本。如果您想改进脚本,请随时在链接中更新它。
首先,请注意,当Ruby调用shell时,它通常调用
以下是执行shell脚本的方法:
1 | cmd ="echo 'hi'" # Sample string that can be used |
这和许多其他语言一样,包括bash、php和perl。
返回shell命令的结果。
文档:http://ruby doc.org/core/kernel.html method-i-60
1 2 | value = `echo 'hi'` value = `#{cmd}` |
内置语法,
在
返回shell命令的结果,就像反勾号一样。
文档:http://www.ruby-doc.org/docs/programmingruby/html/language.html
1 2 | value = %x( echo 'hi' ) value = %x[ #{cmd} ] |
在子shell中执行给定的命令。
返回
文档:http://ruby doc.org/core/kernel.html method-i-system
1 2 | wasGood = system("echo 'hi'" ) wasGood = system( cmd ) |
通过运行给定的外部命令替换当前进程。
返回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 |
以下是一些额外的建议:
1 | $?.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
这是基于这个答案的流程图。另请参见,使用
我喜欢这样做的方式是使用
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| ... } |
在这些机制之间进行选择时需要考虑的一些事情是:
您可能需要任何东西,从简单的backticks(``)、system()和
如果子进程执行时间太长,您可能还希望将超时值抛出到组合中。
不幸的是,这要看情况而定。
还有一个选择:
当你:
- 需要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早期开始,
我绝对不是红宝石专家,但我会试一试:
1 2 3 4 | $ irb system"echo Hi" Hi => true |
您还应该能够执行以下操作:
1 2 | cmd = 'ls' system(cmd) |
上面的答案已经很好了,但是我真的想分享下面的摘要文章:"在Ruby中运行shell命令的6种方法"
基本上,它告诉我们:
1 | exec 'echo"hello $HOSTNAME"' |
1 2 | system 'false' puts $? |
Backticks(:):
1 | today = `date` |
1 | IO.popen("date") { |f| puts f.gets } |
1 2 | require"open3" stdin, stdout, stderr = Open3.popen3('dc') |
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,请将
测试:
system("echo $SHELL")
system('bash -c"echo $SHELL"')
或者,如果您正在运行一个现有的脚本文件(如
您还可以使用反勾号操作符(`),与Perl类似:
1 2 | directoryListing = `ls /` puts directoryListing # prints the contents of the root directory |
如果你需要一些简单的东西,很方便。
您希望使用哪种方法取决于您正试图完成的具体工作;请查看文档以了解有关不同方法的更多详细信息。
最简单的方法是,例如:
1 2 | reboot = `init 6` puts reboot |
使用这里的答案并链接到mihai的答案中,我构建了一个满足这些要求的函数:
另外,在shell命令成功退出(0)并将任何内容放入stdout的情况下,此命令还将返回stdout。在这种情况下,它不同于
代码如下。具体功能为
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" |
不要忘记
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说:这个方法类似于
我们可以通过多种方式实现。
使用
1 | exec('ls ~') |
使用
1 2 3 4 5 6 7 8 | `ls ~` =>"Applications\ Desktop\ Documents" %x(ls ~) =>"Applications\ Desktop\ Documents" |
使用
1 2 | system('ls ~') => true |
如果您的案件比普通案件更复杂(不能用
例如,您可以使用它来:
- 创建进程组(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) |
backticks`方法是从ruby调用shell命令最简单的方法。它返回shell命令的结果。
1
2url_request = 'http://google.com'
result_of_shell_command = `curl #{url_request}`
给定一个橡木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。
你将需要你的
这是一个很酷的脚本,我在OSX上的Ruby脚本中使用(这样我就可以启动一个脚本并获取更新,即使在切换到远离窗口的位置之后):
1 2 | cmd = %Q|osascript -e 'display notification"Server was reset" with title"Posted Update"'| system ( cmd ) |