【问题标题】:Ruby, run linux commands one by one, by SSH and LOG everythingRuby,一个一个地运行linux命令,通过SSH和LOG一切
【发布时间】:2011-07-24 10:07:17
【问题描述】:

我想用 Ruby 女巫 net::ssh 编写代码,在远程 linux 机器上一一运行命令并记录所有内容(在 linux 机器上称为命令、stdout 和 stderr)。

所以我写函数:

  def rs(ssh,cmds)
    cmds.each do |cmd|
      log.debug "[SSH>] #{cmd}"
      ssh.exec!(cmd) do |ch, stream, data|    
        log.debug "[SSH:#{stream}>] #{data}"            
      end  
    end
  end

例如,如果我想在远程 linux 上创建新文件夹和文件:“./verylongdirname/anotherlongdirname/a.txt”,并列出该目录中的文件,然后在那里找到 firefox(这有点愚蠢:P)所以我像这样调用上面的程序:

Net::SSH.start(host, user, :password => pass) do |ssh|  

  cmds=["mkdir verylongdirname", \                                 #1
        "cd verylongdirname; mkdir anotherlongdirname, \           #2
        "cd verylongdirname/anotherlongdirname; touch a.txt", \    #3
        "cd verylongdirname/anotherlongdirname; ls -la", \         #4
        "cd verylongdirname/anotherlongdirname; find ./ firefox"   #5 that command send error to stderr.
        ]

  rs(ssh,cmds)   # HERE we call our function

  ssh.loop
end

运行上面的代码后,我将在#1、#2、#3、#4、#5 行中获得有关执行命令的完整 LOG 女巫信息。问题是 linux 上的状态,在从 cmds 数组执行命令之间,没有保存(所以我必须在运行正确的命令之前重复“cd”语句)。我对此并不满意。

我的目的是有这样的 cmds 表:

  cmds=["mkdir verylongdirname", \     #1
        "cd verylongdirname", \        
        "mkdir anotherlongdirname", \  #2
        "cd anotherlongdirname", \
        "touch a.txt", \               #3
        "ls -la", \                    #4
        "find ./ firefox"]             #5

如您所见,运行每个命令之间的状态都保存在 linux 机器上(在运行正确的命令之前我们不需要重复适当的“cd”语句)。如何更改“rs(ssh,cmds)”程序来执行此操作并像以前一样记录所有内容(comand、stdout、stdin)?

【问题讨论】:

    标签: ruby ssh command logging sequential


    【解决方案1】:

    也许可以尝试使用 ssh 通道来打开远程 shell。这应该保留您的命令之间的状态,因为连接将保持打开状态:

    http://net-ssh.github.com/ssh/v1/chapter-5.html

    这里还有一篇文章用稍微不同的方法做类似的事情:

    http://drnicwilliams.com/2006/09/22/remote-shell-with-ruby/

    编辑 1

    好的。我明白你在说什么。 SyncShell 已从 Net::SSH 2.0 中删除。但是我发现了这个,它看起来和SyncShell 所做的差不多:

    http://net-ssh-telnet.rubyforge.org/

    例子:

    s = Net::SSH.start(host, user)
    t = Net::SSH::Telnet.new("Session" => s, "Prompt" => %r{^myprompt :})
    puts t.cmd("cd /tmp")  
    puts t.cmd("ls")       # <- Lists contents of /tmp
    

    Net::SSH::Telnet 是同步的,并保留状态,因为它在您的远程 shell 环境的 pty 中运行。记得设置正确的提示检测,否则Net::SSH::Telnet一旦调用它就会出现挂起(它正在尝试查找提示)。

    【讨论】:

    • 1.在drnicwilliams.com/2006/09/22/remote-shell-with-ruby 我们看到:`shell = session.shell.sync` Thad web 是从 2006 年开始的。今天在 ruby​​ 中 shell.sync 不起作用。 Shell 可能已从 develop net::ssh 中删除(我不知道为什么)。有一些替代包 net-ssh-shell 但使用它还很年轻(那里没有实现太多功能)。
    • 2.我之前读过net-ssh.github.com/ssh/v1/chapter-5.html。当我尝试将通道与它的 on_data(用于 stdout)、on_extended_data(用于 stderr)和 send_data(用于在循环中一一发送命令)功能一起使用时 - 但我无法强制顺序日志(以记录顺序 command1、stdout1、stderr1 ,command2,stdout2,stderr2,command3,stdout3,sterr3...)。原因是在 send_data 中 - 所有命令都是在第一个命令在远程机器上执行之前发送的(所以在日志中我们看到:command1,command2,command3,...,stdout1,stderr1,stdout2,stderr2,stdout3,stderr3.. .)
    • 3.好的 - 非常感谢你 - 这是我不想要的。 :) 顺便说一句:我的 linux 提示符是“/[$%#>] \z/n”——它是 net/ssh/telnet 包的默认值——所以我不需要使用“提示”选项。谢谢
    【解决方案2】:

    您可以改用管道:

    require "open3"
    
    SERVER = "..."
    BASH_PATH = "/bin/bash"
    
    BASH_REMOTE = lambda do |command|
      Open3.popen3("ssh #{SERVER} #{BASH_PATH}") do |stdin, stdout, stderr|
        stdin.puts command
        stdin.close_write
        puts "STDOUT:", stdout.read
        puts "STDERR:", stderr.read
      end
    end
    
    BASH_REMOTE["ls /"]
    BASH_REMOTE["ls /no_such_file"]
    

    【讨论】:

    • 该解决方案在 Windows 中不起作用 - 所以它不是可移植的。我不在linux中检查它,但感谢您的时间。也许将来有人会使用它。
    【解决方案3】:

    好的,最后在@Casper 的帮助下,我得到了程序(可能有人使用它):

      # Remote command execution
      # t=net::ssh:telnet, c="command_string"
      def cmd(t,c)    
        first=true
        d=''    
        # We send command via SSH and read output piece by piece (in 'cm' variable)
        t.cmd(c) do |cm|       
          # below we cleaning up output piece (becouse it have strange chars)     
          d << cm.gsub(/\e\].*?\a/,"").gsub(/\e\[.*?m/,"").gsub(/\r/,"")     
          # when we read entire line(composed of many pieces) we write it to log
          if d =~ /(^.*?)\n(.*)$/m
            if first ; 
              # instead of the first line (which has repeated commands) we log commands 'c'
              @log.info "[SSH]>"+c; 
              first=false
            else
              @log.info "[SSH] "+$1; 
            end
            d=$2        
          end      
        end
    
        # We print lines that were at the end (in last piece)
        d.each_line do |l|
          @log.info "[SSH] "+l.chomp      
        end
      end
    

    我们在代码中调用它:

    #!/usr/bin/env ruby
    
    require 'rubygems'
    
    require 'net/ssh'
    
    require 'net/ssh/telnet'
    
    require 'log4r'
    ...
    ...
    ...
    Net::SSH.start(host, user, :password => pass) do |ssh|  
      t = Net::SSH::Telnet.new("Session" => ssh)
      cmd(t,"cd /")
      cmd(t,"ls -la")
      cmd(t,"find ./ firefox")  
    end
    

    谢谢,再见。

    【讨论】:

      【解决方案4】:

      这里是 Net/ssh 的包装,这里是文章 http://ruby-lang.info/blog/virtual-file-system-b3g

      来源https://github.com/alexeypetrushin/vfs

      要记录所有命令,只需覆盖 Box.bash 方法并在那里添加日志记录

      【讨论】:

        猜你喜欢
        • 2014-06-30
        • 1970-01-01
        • 1970-01-01
        • 2019-09-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多