【问题标题】:Threaded output not displayed when run from command (works from irb)从命令运行时不显示线程输出(从 irb 工作)
【发布时间】:2017-06-14 13:03:58
【问题描述】:

我决定用可执行组件重写我作为 gem/module 编写的 ruby​​ 脚本。原来的工作正常,但作为一个 gem,它更容易维护/安装所有依赖项。

该命令接受一个主机数组和一个命令,并通过线程在主机上运行它。我现在遇到的问题是命令的输出没有显示在终端上。然而,从 IRB 中执行模块代码会产生输出。

模块代码如下:

require "rcmd/version"
require 'net/ssh'
require 'thread'
module Rcmd
  @queue = Queue.new
  class << self
    attr_accessor :nthreads
    attr_accessor :user
    attr_accessor :quiet
    attr_accessor :command
    attr_accessor :host_list
    attr_accessor :threads
  end
  # Built in function called by each thread for executing the command on individual hosts
  def Rcmd.run_command_on_host(conn_options)
    begin
      # Create ssh session to host
      Net::SSH.start(conn_options[:host], conn_options[:user], :password => conn_options[:passwd]) do |session|
        # Open channel for input/output control
        session.open_channel do |channel|
          channel.on_data do |ch, data|
            # Print recieved data if quiet is not true
            puts "#{conn_options[:host]} :: #{data}" unless conn_options[:quiet]
          end
          channel.on_extended_data do |ch,type,data|
            # Always print stderr data
            puts "#{conn_options[:host]} :: ERROR :: #{data}"
          end
          # Execute command
          channel.exec @command
        end
        # Loop until command completes
        session.loop
    end
    rescue
      puts "#{conn_options[:host]} :: CONNECT ERROR :: Unable to connect to host!\n"
    end
  end
  # Main method of module for starting the execution of the specified command on provided hosts
  def Rcmd.run_command()
    if not @command
      raise ArgumentError.new("No command set for execution")
    end
    if not @host_list.count >= 1
      raise ArgumentError.new("host_list must contain at least one system")
    end
    @host_list.each do |host|
      @queue << host
    end
    until @queue.empty?
      # Don't start more threads then hosts.
      num_threads = @nthreads <= @host_list.count ? @nthreads : @host_list.count
      # Prepare threads
      @threads = { }
      num_threads.times do |i|
        @threads[i] = Thread.new {
          conn_options = { :user => @user, :host => @queue.pop, :password => nil, :quiet => @quiet}
          unless conn_options[:host].nil?
            self.run_command_on_host(conn_options)
          end
        }
      end
      # Execute threads
      @threads.each(&:join)
    end
  end
end

测试sn-p:

require 'rcmd'
Rcmd.host_list= ["localhost", "dummy-host"]
Rcmd.nthreads= 2
Rcmd.user= 'root'
Rcmd.command= "hostname -f"
Rcmd.run_command

在 IRB 中运行上述代码会产生:

dummy-host :: CONNECT ERROR :: Unable to connect to host!
localhost :: darkstar.lan

正如预期的那样。但是,从脚本文件(gem 命令)或在 ruby​​ 中直接运行相同的命令会导致没有输出:

ruby-newb@darkstar:~/repos/rcmd$ rcmd -n localhost,dummy-host -c 'hostname -f'
ruby-newb@darkstar:~/repos/rcmd$ 

之前我使用了 $stdout.puts 和 $stderr.puts 以及常量变体,但无奈之下只使用了 puts。我还尝试了 print 和其他各种方法,包括将流交给线程并在线程完成后打印所有输出,但无济于事。

在代码中添加大量“p”语句后,从命令运行时,输出在 run_command_on_host 方法中的 Net::SSH.start 调用之前停止。

我也尝试过在线程创建中使用整个方法,但它无法完全执行。因此有两种方法。一个用于创建线程,一个由线程用于执行 ssh 会话和命令。

在 ruby​​-2.3.3 和 ruby​​-2.0.0-p648 中都失败了,所以我认为我只是在做一些愚蠢的事情。

如果有人能在心里告诉这个 Ruby 新手他哪里出了问题,那将不胜感激。

【问题讨论】:

    标签: ruby multithreading net-ssh


    【解决方案1】:

    我不确定具体原因,但似乎方法调用正在退出(尽管线程已连接,但并未等待线程)并因此杀死了线程。因此,Net::SSH 模块没有时间获取/返回数据。

    我在代码末尾附近添加了以下除非语句,以强制该方法等待线程完成,然后根据需要显示输出。也减少了一点代码量。

    unless @threads.each.map {|t| t.alive?}.none?
        sleep 1
    end
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-07-07
      • 1970-01-01
      • 2011-05-21
      • 1970-01-01
      • 2019-12-15
      • 1970-01-01
      • 2013-06-14
      • 2011-01-22
      相关资源
      最近更新 更多