【问题标题】:Both get STDOUT in realtime and provide STDIN两者都实时获取 STDOUT 并提供 STDIN
【发布时间】:2019-07-04 10:07:28
【问题描述】:

我有一个命令需要我给它一些 STDIN 数据,如my-command <<< my-data。我无法控制命令;该信息旨在以交互方式提供,<<< 有效。

我想将此命令作为更大脚本的一部分自动执行,但由于它的操作需要一段时间——但会将进度输出到 STDOUT——我想实时打印 STDOUT。我还希望能够捕获命令的退出状态,以确定它是否失败。

如果我使用system,我会立即获得 STDOUT,但无法提供 STDIN 数据。

system('my-command')

如果我使用Open3,我可以提供 STDIN 数据,但 STDOUT 仅在最后打印(如果我完全捕获它)。

Open3.capture2('my-command', stdin_data: 'my-data')[1].success?

有什么方法可以两全其美,最好是Open3

【问题讨论】:

    标签: ruby stdout stdin popen3


    【解决方案1】:

    这是一个带有可选标准输入数据的实时标准输出的 sn-p。您需要使用IO.select 来查看流以查看它们是否可读。

    require 'open3'
    
    class Runner
      class Result
        attr_accessor :status
        attr_reader :stdout, :stderr
    
        def initialize
          @stdout = +''
          @stderr = +''
          @status = -127
        end
    
        def success?
          status.zero?
        end
      end
    
      attr_reader :result
    
      def initialize(cmd, stdin: nil, print_to: nil)
        @stdin = stdin
        @cmd = cmd
        @result = Result.new
        @print_to = print_to
      end
    
      def run
        Open3.popen3(@cmd) do |stdin, stdout, stderr, wait_thr|
          # Dump the stdin-data into the command's stdin:
          unless stdin.closed?
            stdin.write(@stdin) if @stdin
            stdin.close
          end
    
          until [stdout, stderr].all?(&:eof?)
            readable = IO.select([stdout, stderr])
            next unless readable&.first
    
            readable.first.each do |stream|
              data = +''
              begin
                stream.read_nonblock(1024, data)
              rescue EOFError
                # ignore, it's expected for read_nonblock to raise EOFError
                # when all is read
              end
    
              next if data.empty?
    
              if stream == stdout
                result.stdout << data
                @print_to << data if @print_to
              else
                result.stderr << data
                @print_to << data if @print_to
              end
            end
          end
    
          result.status = wait_thr.value.exitstatus
        end
        result
      end
    end
    
    
    result = Runner.new('ls -al').run
    puts "Exit status: %d" % result.status
    puts "Stdout:"
    puts result.stdout
    puts "Stderr:"
    puts result.stderr
    
    # Print as it goes:
    result = Runner.new('ls -al', print_to: $stdout).run
    

    如果您需要模拟实时标准输入(按键),那么您需要为标准输出数据流创建某种匹配器,并在预期提示通过流时将响应写入命令的标准输入。在这种情况下,您可能最好使用PTY.spawn 或使用第 3 方 gem。

    您还可以将标准输入数据写入临时文件并使用 shell 自己的重定向:

    require 'tempfile'
    tempfile = Tempfile.new
    tempfile.write(stdin_data)
    tempfile.close
    system(%(do_stuff < "#{tempfile.path}"))
    tempfile.unlink
    

    【讨论】:

    • 我希望有一些更简单的(和原生的?)。这是我的脚本大小的三倍。
    • 您可以将标准输入数据写入临时文件并让系统进行重定向,并将其添加到答案中。
    猜你喜欢
    • 1970-01-01
    • 2012-12-04
    • 1970-01-01
    • 1970-01-01
    • 2017-04-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多