具有并行线程和日志记录bash输出的Ruby脚本

时间:2017-01-03 16:31:30

标签: ruby bash chef

我有一个ruby脚本我正在上传一个角色目录到厨师服务器。使用.each循环一次执行此操作1很慢。所以我通过在单独的线程中运行每个命令来添加并行性。现在我试图弄清楚如何存储命令的输出,以便我可以按照创建的线程的顺序读回它们。 roles数组已按字母顺序排列。我们还使用bash_profile别名来运行带有dev和prod的不同配置文件的knife命令。

我尝试了许多不同的方法来运行bash命令并尝试将输出存储在数组或文件等中......目前这显示了每个线程在运行或完成时的输出所以输出难以阅读或判断一切是否正确完成,bash命令输出的文件应该重定向到创建但是内容为空。

很抱歉,如果这个脚本不是最容易阅读的。我现在只做了一年多一年的红宝石,当我们开始进入厨师时,我自学了。在此之前我没有编程背景。

#!/opt/chefdk/embedded/bin/ruby

def print_usage_and_exit
  puts 'Need to specify 1 or more role.json files or no arguments to upload all roles'
  puts "ruby #{__FILE__} or ruby #{__FILE__} [role1.json] [role2.json] [...]"
  exit(1)
end

def fetch_roles
  roles = []
  current_dir = File.dirname(__FILE__)
  Dir.foreach("#{current_dir}/roles") do |role|
    next if role == '.' || role == '..' || role == 'README.md'
    roles.push(role)
  end
  roles
end

upload = []
i = 0
roles = (ARGV.empty? ? fetch_roles : ARGV[0..-1])

# Probably redundant, but a cheap check to make sure we're only looking at json files
roles.keep_if { |b| b.end_with?('.json') }

print_usage_and_exit if roles.empty?

print "\nSpecify new knife command if you have seperate knife command for dev and prod created with .bash_profile function."
print "\nLeave blank to use default 'knife' command"
print "\nWhich knife command to use: "
knife = ($stdin.gets.chomp('') ? 'knife' : $stdin.gets.chomp)

print "\n**** Starting upload of roles to chef server ****\n"

roles.each do |role|
  upload[i] = Thread.new{
    system("bash", "-cl", "#{knife} role from file #{role} > /tmp/#{role}.log")
  }
  i += 1
end

upload.each {|t| t.join}
roles.each do |role|
  logfile = "/tmp/#{role}.log"
  logmsg = open(logfile)
  print "\n#{logmsg.read}\n"
  #FileUtils.rm("/tmp/#{role}.log")
end

print "\n**** Finished uploading roles to chef server ****\n"

2 个答案:

答案 0 :(得分:4)

正确的方法是knife upload roles/。这实际上并没有回答你的问题本身,但我认为你会发现它更简单。

答案 1 :(得分:0)

我更喜欢使用Open3' caputure3函数来执行子进程,因为它可以轻松处理所有各种细节(stdin,stdout,stderr,环境变量等)

使用线程本地数据,ruby线程的内置功能,并且您有一个非常简单的运行子进程的方法。我是使用线程进行这种并发的忠实粉丝。 GIL防止ruby同时运行所有线程,但capture3子进程无论如何都会同时运行,所以它并不重要。

require 'open3'

commands = [
  'true',
  'echo "a more complex command from `pwd`" 1>&2 && echo "and stdout"',
]

threads = []

commands.each_with_index do |cmd, i|
    threads[i] = Thread.new do
      stdout, stderr, status = Open3.capture3("bash", stdin_data: cmd)
      Thread.current['stdout'] = stdout
      Thread.current['stderr'] = stderr
      Thread.current['status'] = status
    end
end

threads.each_with_index do |th,i|
  th.join
  puts "Thread # #{i}:"
  %w( stdout stderr status ).each do |s|
    puts "\t#{s}: #{th[s]}"
  end
  puts
end

结果正是您所期望的:

$ ruby ./t.rb
Thread # 0:
    stdout:
    stderr:
    status: pid 34244 exit 0

Thread # 1:
    stdout: and stdout
    stderr: a more complex command from /Users/dfarrell/t
    status: pid 34243 exit 0

您可以使用退出状态给出失败或成功的命令数的最终摘要。