代码之家  ›  专栏  ›  技术社区  ›  CodingWithoutComments

从Ruby调用shell命令

  •  948
  • CodingWithoutComments  · 技术社区  · 16 年前

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

    20 回复  |  直到 5 年前
        1
  •  1424
  •   rnevius    5 年前

    这一解释是基于一个被评论的例子 Ruby script

    首先,请注意,当Ruby调用shell时,它通常会调用 /bin/sh 猛击不支持某些Bash语法 /垃圾箱/垃圾箱

    以下是执行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 字符是分隔符,可以是任何字符。 如果分隔符是其中一个字符 ( , [ { < 文字由字符组成,直到匹配的结束分隔符, 考虑嵌套分隔符对。对于所有其他分隔符 文字包含直到下一次出现的字符 #{ ... } 是允许的。

      返回shell命令的结果(即标准输出),就像backticks一样。

      https://docs.ruby-lang.org/en/master/syntax/literals_rdoc.html#label-Percent+Strings

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

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

      退换商品 true 如果找到该命令并成功运行, false 否则

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

      wasGood = system( "echo 'hi'" )
      wasGood = system( cmd )
      
    4. Kernel#exec

      返回none,当前进程将被替换,并且永远不会继续。

      文件: 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
      

    这里有一些额外的建议: $? $CHILD_STATUS ,如果使用反勾号,则访问上次系统执行命令的状态, system() %x{} . exitstatus pid 特性:

    $?.exitstatus
    

    欲了解更多信息,请参阅:

        3
  •  164
  •   Andrew Marshall    12 年前

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

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

    directorylist.each do |filename|
      filename.chomp!
      # work with file
    end
    
        4
  •  66
  •   the Tin Man    11 年前

    以下是我认为最好的一篇关于在Ruby中运行shell脚本的文章: 6 Ways to Run Shell Commands in Ruby ".

        5
  •  44
  •   Dominic K    12 年前

    我最喜欢的是 Open3

      require "open3"
    
      Open3.popen3('nroff -man') { |stdin, stdout, stderr| ... }
    
        6
  •  30
  •   the Tin Man    5 年前

    在选择这些机制时,需要考虑以下几点:

    1. 你是想要史都特还是你 分开了?
    2. 你们的产量有多大?你想要什么 将整个结果保存在内存中?
    3. 你想读一些你的书吗 跑步
    4. 你需要一个Ruby对象吗 按要求杀了它?

    system() IO.popen Kernel.fork Kernel.exec 具有 IO.pipe IO.select .

    如果子进程执行时间过长,您可能还希望在混合中抛出超时。

    不幸的是,这是非常重要的 .

        7
  •  27
  •   the Tin Man    11 年前

    我肯定不是Ruby专家,但我会尝试一下:

    $ irb 
    system "echo Hi"
    Hi
    => true
    

    您还应该能够做到以下几点:

    cmd = 'ls'
    system(cmd)
    
        8
  •  26
  •   the Tin Man    11 年前

    还有一个选择:

    当你:

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

    您可以使用shell重定向:

    puts %x[cat bogus.txt].inspect
      => ""
    
    puts %x[cat bogus.txt 2>&1].inspect
      => "cat: bogus.txt: No such file or directory\n"
    

    2>&1 语法适用于不同的领域 Linux Windows 从MS-DOS的早期开始。

        9
  •  23
  •   Samuel Liew cicero lopes    5 年前

    上面的答案已经非常好了,但我真的想分享以下摘要文章: 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>]
    
        10
  •  16
  •   the Tin Man    5 年前

    如果你真的需要Bash,按照“最佳”答案中的注释。

    /bin/sh , /垃圾箱/垃圾箱 在所有系统上。

    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")
    

    应该

    system("bash ./my_script.sh")
    

    为了确保这一点,尽管可能会有轻微的开销 /垃圾箱/垃圾箱 /bin/bash

        11
  •  12
  •   Rufo Sanchez    16 年前

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

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

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

    你想用哪种方法取决于你想完成什么;有关不同方法的更多详细信息,请查看文档。

        12
  •  11
  •   the Tin Man    11 年前

    使用此处的答案和Mihai的答案中的链接,我构建了一个满足以下要求的函数:

    1. 允许将参数作为数组传递给shell,因此无需担心转义。
    2. 捕获命令的退出状态,以便在发生错误时清除。

    另外,如果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"
    
        13
  •  11
  •   the Tin Man    11 年前

    我们可以通过多种方式实现这一目标。

    Kernel#exec ,执行此命令后不执行任何操作:

    exec('ls ~')
    

    使用 backticks or %x

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

    Kernel#system 命令,返回 true 如果成功, false 如果不成功,则返回 nil

    system('ls ~')
    => true
    
        14
  •  11
  •   MonsieurDart    9 年前

    别忘了这道菜 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

        15
  •  11
  •   the Tin Man    5 年前

    例如,最简单的方法是:

    reboot = `init 6`
    puts reboot
    
        16
  •  8
  •   the Tin Man    5 年前

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

         url_request = 'http://google.com'
         result_of_shell_command = `curl #{url_request}`
    
        17
  •  7
  •   the Tin Man    5 年前

    如果你有一个比普通案件更复杂的案件无法处理 `` ,然后退房 Kernel.spawn() . 这似乎是StockRuby为执行外部命令提供的最通用/功能最全的功能。

    您可以使用它来:

    • 创建进程组(窗口)。
    • 设置环境变量,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)
    
        18
  •  7
  •   the Tin Man    5 年前

    发出这样的命令 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

        19
  •  4
  •   the Tin Man    5 年前

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

    当在Windows上使用TKGUI时,您需要从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' 在你的脚本里面。

    system() , exec() spawn()

        20
  •  -1
  •   JayCrossler    10 年前

    下面是我在OS X上的ruby脚本中使用的一个很酷的脚本(这样我就可以启动一个脚本并在从窗口切换出去后获得更新):

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