ChatGPT解决这个技术问题 Extra ChatGPT

如何从 Ruby 调用 shell 命令

如何从 Ruby 程序内部调用 shell 命令?然后如何将这些命令的输出返回到 Ruby?

虽然这个问题很有用,但问得不好。 Ruby 有许多调用子shell 的方法,这些子shell 有很好的文档,通过阅读KernelOpen3 文档并在此处搜索SO 很容易找到。
可悲的是,这个话题相当复杂。 Open3 (docs) 是大多数情况下的最佳选择,IMO,但在旧版本的 Ruby 上,它不会尊重修改后的 PATH (bugs.ruby-lang.org/issues/8004),这取决于您传递参数的方式(特别是,如果您使用带有非关键字的 opts 哈希),它可能会中断。但是,如果您遇到这些情况,那么您正在做一些非常高级的事情,您可以通过阅读 Open3 的实现来弄清楚要做什么。
我很惊讶没有人提到 Shellwords.escape (doc)。您不想将用户输入直接插入到 shell 命令中 - 首先将其转义!另见command injection

r
rnevius

此解释基于我朋友的评论 Ruby script。如果您想改进脚本,请随时在链接上更新它。

首先,请注意,当 Ruby 调用 shell 时,它通常调用 /bin/shnot Bash。 /bin/sh 并非在所有系统上都支持某些 Bash 语法。

以下是执行 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 字符是分隔符,可以是任何字符。如果分隔符是字符 (、[、{ 或下一次出现的分隔符。允许字符串插值 #{ ... }。返回 shell 命令的结果(即标准输出),就像反引号一样。文档:https://docs.ruby-lang.org/ en/master/syntax/literals_rdoc.html#label-Percent+Strings value = %x( echo 'hi' ) value = %x[ #{cmd} ] Kernel#system 在子 shell 中执行给定的命令。如果命令找到并运行成功,否则为假。文档:http://ruby-doc.org/core/Kernel.html#method-i-system wasGood = system("echo 'hi'") wasGood = system(cmd) Kernel#exec 通过运行给定的外部命令替换当前进程。返回无,当前进程被替换并且永远不会继续。文档:http://ruby-doc.org/core/Kernel.html#meth od-i-exec exec( "echo 'hi'" ) exec( cmd ) # 注意:由于上面的行,这永远不会到达

这里有一些额外的建议:$?$CHILD_STATUS 相同,如果您使用反引号 system()%x{},则访问最后一个系统执行命令的状态。然后您可以访问 exitstatuspid 属性:

$?.exitstatus

更多阅读请参见:

http://www.elctech.com/blog/im-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


我需要在生产服务器上记录我的可执行文件的输出,但找不到办法。我使用 puts #{cmd} 和 logger.info(#{cmd})。有没有办法在生产中记录他们的输出?
还有 IO#popen() 和 Open3#popen3()。 mentalized.net/journal/2010/03/08/…
为了完整起见(因为我最初认为这也是一个 Ruby 命令):Rakesh,它执行“运行系统命令 cmd。如果给出了多个参数,则该命令不与 shell 一起运行(与 Kernel::exec 和 Kernel::system 的语义相同)”。
默认情况下,反引号不会捕获 STDERR。如果要捕获,请将 `2>&1` 添加到命令
如果它说反引号和 %x 返回给定命令的“输出”而不是“结果”,我认为这个答案会略有改进。后者可能被误认为是退出状态。还是只有我一个人?
t
the Tin Man

这是一个基于“When to use each method of launching a subprocess in Ruby”的流程图。另请参见“Trick an application into thinking its stdout is a terminal, not a pipe”。

https://i.stack.imgur.com/1Vuvp.png


哇哈哈。非常有用,即使它必须存在的事实是不幸的
附带说明一下,我发现 spawn() 方法在许多不同的地方(例如 KernelProcess 是最通用的。它或多或少与 PTY.spawn() 相同,但更通用。
IMO、反引号和 %x 仅适用于脚本,因为它们不会将 stderr 返回给您,并且您不能向它们传递参数(请注意,您可以使用 $? 来获取退出状态)。所以我几乎总是使用 Open3.capture3 而不是反引号或 %x
A
Andrew Marshall

我喜欢这样做的方式是使用 %x 文字,这使得在命令中使用引号变得容易(并且可读!),如下所示:

directorylist = %x[find . -name '*test.rb' | sort]

在这种情况下,它将使用当前目录下的所有测试文件填充文件列表,您可以按预期进行处理:

directorylist.each do |filename|
  filename.chomp!
  # work with file
end

%x[ cmd ] 是否向您返回一个数组?
以上对我不起作用。 ``<main>': 未定义的方法 each' for :String (NoMethodError) 它是如何为您工作的?我正在使用 ruby -v ruby 1.9.3p484 (2013-11-22 revision 43786) [i686-linux] 您确定从命令返回了一个数组,以便循环实际工作吗?
%x[ cmd ].split("\n") 将返回一个列表:)
t
the Tin Man

这是我认为关于在 Ruby 中运行 shell 脚本的最佳文章:“6 Ways to Run Shell Commands in Ruby”。

如果您只需要获取输出,请使用反引号。

我需要更高级的东西,比如 STDOUT 和 STDERR,所以我使用了 Open4 gem。你有那里解释的所有方法。


此处描述的帖子未讨论 %x 语法选项。
+1 为 Open4。当我发现这个时,我已经开始尝试实现我自己版本的 spawn 方法。
D
Dominic K

我最喜欢的是Open3

  require "open3"

  Open3.popen3('nroff -man') { |stdin, stdout, stderr| ... }

我也喜欢 open3,尤其是 Open3.capture3: ruby-doc.org/stdlib-1.9.3/libdoc/open3/rdoc/… -> stdout, stderr, status = Open3.capture3('nroff -man', :stdin_data => stdin)
是否有关于如何使用 Open3 或 Ruby 标准库中的其他 Open 执行规范和单元测试的文档?以我目前的理解水平很难测试外壳。
open3 和 open4 有什么区别?
t
the Tin Man

在这些机制之间进行选择时需要考虑的一些事项是:

你只想要标准输出还是也需要标准错误?甚至分开?你的输出有多大?您想将整个结果保存在内存中吗?您想在子进程仍在运行时读取一些输出吗?您需要结果代码吗?您是否需要一个 Ruby 对象来表示该进程并让您按需终止它?

您可能需要从简单的反引号 (``)、system()IO.popen 到带有 IO.pipeIO.select 的完整 Kernel.fork/Kernel.exec 的任何内容。

如果子流程执行时间过长,您可能还想在混合中加入超时。

不幸的是,这在很大程度上取决于。


t
the Tin Man

另一种选择:

当你:

需要标准错误和标准输出

不能/不会使用 Open3/Open4(他们在我的 Mac 上的 NetBeans 中抛出异常,不知道为什么)

您可以使用外壳重定向:

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


t
the Tin Man

我绝对不是 Ruby 专家,但我会试一试:

$ irb 
system "echo Hi"
Hi
=> true

您还应该能够执行以下操作:

cmd = 'ls'
system(cmd)

S
Samuel Liew

上面的答案已经很不错了,但是我真的很想分享以下总结文章:“6 Ways to Run Shell Commands in Ruby

基本上,它告诉我们:

Kernel#exec

exec 'echo "hello $HOSTNAME"'

system$?

system 'false' 
puts $?

反引号 (`):

today = `date`

IO#popen

IO.popen("date") { |f| puts f.gets }

Open3#popen3——标准库:

require "open3"
stdin, stdout, stderr = Open3.popen3('dc') 

Open4#popen4——宝石:

require "open4" 
pid, stdin, stdout, stderr = Open4::popen4 "false" # => [26327, #<IO:0x6dff24>, #<IO:0x6dfee8>, #<IO:0x6dfe84>]

t
the Tin Man

如果您真的需要 Bash,请按照“最佳”答案中的说明。

首先,请注意当 Ruby 调用 shell 时,它通常调用 /bin/sh,而不是 Bash。 /bin/sh 并非所有系统都支持某些 Bash 语法。

如果您需要使用 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 可能会产生轻微的开销,但您可能不会注意到。


R
Rufo Sanchez

您还可以使用反引号运算符 (`),类似于 Perl:

directoryListing = `ls /`
puts directoryListing # prints the contents of the root directory

如果你需要一些简单的东西很方便。

您要使用哪种方法取决于您要完成的工作。检查文档以获取有关不同方法的更多详细信息。


t
the Tin Man

使用这里的答案和 Mihai 的答案中的链接,我整理了一个满足这些要求的函数:

整齐地捕获 STDOUT 和 STDERR,因此当我的脚本从控制台运行时它们不会“泄漏”。允许将参数作为数组传递给 shell,因此无需担心转义。捕获命令的退出状态,以便在发生错误时一目了然。

作为奖励,如果 shell 命令成功退出 (0) 并将任何内容放在 STDOUT 上,此命令也将返回 STDOUT。通过这种方式,它与 system 不同,后者在这种情况下仅返回 true

代码如下。具体函数为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"

t
the Tin Man

我们可以通过多种方式实现它。

使用 Kernel#exec,执行此命令后没有任何内容:

exec('ls ~')

使用 backticks or %x

`ls ~`
=> "Applications\nDesktop\nDocuments"
%x(ls ~)
=> "Applications\nDesktop\nDocuments"

使用 Kernel#system 命令,如果成功则返回 true,如果不成功则返回 false,如果命令执行失败则返回 nil

system('ls ~')
=> true

M
MonsieurDart

不要忘记 spawn 命令创建一个后台进程来执行指定的命令。您甚至可以使用 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 但它不等待命令完成。


Kernel.spawn() 似乎比所有其他选项更通用。
t
the Tin Man

最简单的方法是,例如:

reboot = `init 6`
puts reboot

t
the Tin Man

反引号 (`) 方法是从 Ruby 调用 shell 命令的最简单方法。它返回 shell 命令的结果:

     url_request = 'http://google.com'
     result_of_shell_command = `curl #{url_request}`

t
the Tin Man

给定一个类似 attrib 的命令:

require 'open3'

a="attrib"
Open3.popen3(a) do |stdin, stdout, stderr|
  puts stdout.read
end

我发现虽然这种方法不像

system("thecommand")

或者

`thecommand`

在反引号中,与其他方法相比,这种方法的一个好处是反引号似乎不允许我puts我运行的命令/将我想要运行的命令存储在一个变量中,而 system("thecommand") 似乎没有让我得到输出,而这个方法让我做这两件事,它让我可以独立访问标准输入、标准输出和标准错误。

请参阅“Executing commands in ruby”和Ruby's Open3 documentation


t
the Tin Man

如果您有一个比普通情况更复杂且无法使用 `` 处理的情况,请查看 Kernel.spawn()。这似乎是 Ruby 提供的用于执行外部命令的最通用/功能最全面的。

您可以使用它来:

创建进程组 (Windows)。

将输入、输出、错误重定向到文件/彼此。

设置环境变量,umask。

在执行命令之前更改目录。

为 CPU/数据/等设置资源限制。

做所有可以在其他答案中使用其他选项完成的事情,但需要更多代码。

Ruby documentation 有足够好的示例:

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)

t
the Tin Man

这不是一个真正的答案,但也许有人会发现它很有用:

在 Windows 上使用 TK GUI 时,你需要从 rubyw 调用 shell 命令,你总会有一个烦人的 CMD 窗口弹出不到一秒钟。

为避免这种情况,您可以使用:

WIN32OLE.new('Shell.Application').ShellExecute('ipconfig > log.txt','','','open',0)

或者

WIN32OLE.new('WScript.Shell').Run('ipconfig > log.txt',0,0)

两者都将 ipconfig 输出存储在 log.txt 中,但不会出现任何窗口。

您需要在脚本中require 'win32ole'

使用 TK 和 rubyw 时,system()exec()spawn() 都会弹出那个烦人的窗口。


V
Vasanth Saminathan

不确定shell命令。我使用以下方法将系统命令的输出捕获到变量 val 中:

val = capture(:stdout) do
  system("pwd")
end

puts val

缩短版:

val = capture(:stdout) { system("pwd") }

捕获方法由 active_support/core_ext/kernel/reporting.rb 提供

同样,我们也可以使用 :stderr 捕获标准错误


T
Teoman shipahi

您可以使用如下 format 方法打印一些信息:

puts format('%s', `ps`)
puts format('%d MB', (`ps -o rss= -p #{Process.pid}`.to_i / 1024))

J
JayCrossler

这是我在 OS X 上的 ruby 脚本中使用的一个很酷的脚本(这样即使在离开窗口后我也可以启动脚本并获取更新):

cmd = %Q|osascript -e 'display notification "Server was reset" with title "Posted Update"'|
system ( cmd )