在Ruby中运行系统命令
Execute System Commands in Ruby
在Ruby中有若干种方法来调用系统命令:
- 反引号``或者
%x()
; -
system
方法; - File
- Open3
反引号和system方法
反引号及 %x()
与 system
的区别在于: system 只会返回 true/false;而%x
在命令执行成功时,会返回命令的stdout,当命令支持失败时,会返回空字符串 ""
举例来说:
r = `ls`
=> "Dockerfile\nGemfile\nGemfile.lock..."
r = `cat NoFile`
cat: NoFile: No such file or directory
=> ""
system("ls")
Dockerfile Jenkinsfile bin
=> true
system("cat NoFile")
cat: NoFile: No such file or directory
=> false
这样看来其实反引号或者 %x()
比较好用,但什么时候需要用到 system 呢?我的经验是当你需要执行一个interactive console的时候,system
会比较便利。
`rails console`
# get blocked
system 'rails console'
[Patch] Preventing bunny channel leak
Loading development environment (Rails 6.0.0)
[1] pry(main)> exit
=> true
Open3
无论是反引号还是system,都没有办法拿到命令的STDERR,这时候就可以用Ruby的 Open3
标准库,其中 Open3.capture3
是一个非常好用的方法。
require 'open3'
=> true
# When everything goes right
stdout, stderr, status = Open3.capture3('ls')
=> ["Dockerfile\nGemfile\nGemfile.lock...\n", "", #<Process::Status: pid 60368 exit 0>]
status
=> #<Process::Status: pid 60368 exit 0>
status.exitstatus
=> 0
# When something goes wrong
stdout, stderr, status = Open3.capture3('cat NOTHING')
=> ["", "cat: NOTHING: No such file or directory\n", #<Process::Status: pid 60385 exit 1>]
stderr
=> "cat: NOTHING: No such file or directory\n"
status
=> #<Process::Status: pid 60385 exit 1>
Exit Code与运行结果
在shell中有一个变量 $?
,标记了上一次命令的exit code:
$ ls -alt
total 328
-rw-r--r-- 1 xiao.wang staff 927B Sep 17 14:18 Dockerfile
...
$ echo $?
0
$ cat NOTHING
cat: NOTHING: No such file or directory
$ echo $?
1
就约定而言,当命令执行成功时,exit code为0;当执行失败时,exit 为0以外的数字。
这个数字并没有绝对的标准,不过还是有一些约定: FreeBSD约定了比较多的exit code: http://www.freebsd.org/cgi/man.cgi?query=sysexits&sektion=3 GNU就相对简单: http://www.gnu.org/software/libc/manual/html_node/Exit-Status.html
在Ruby中,有一个和shell中同名的全局变量 $?
system('echo Hello')
Hello
=> true
$?
=> #<Process::Status: pid 60633 exit 0>
$?.exitstatus
=> 0
也可以加载English标准库,让这个变量有更好的可读性:
require 'English'
=> true
2.6.3 :020 > system('echo 1')
1
$CHILD_STATUS
=> #<Process::Status: pid 60659 exit 0>
$CHILD_STATUS.exitstatus.zero?
=> true