Search code examples
ruby

Prepend output while streaming like foreman


I have this ruby script that I use quite often:

$stdin.each_line do |it|
  it.strip!
  eval(ARGV.first)
end

For instance:

> dir | each "Dir.chdir(it) { puts it; system('bundle') }"
code
Bundle complete! 41 Gemfile dependencies, 165 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.
code-ruby
Bundle complete! 6 Gemfile dependencies, 44 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

I do this "puts it" quite often and would rather have my output be prepended by the "it".

e.g.

> dir | each "Dir.chdir(it) { system('bundle') }"
[code] Bundle complete! 41 Gemfile dependencies, 165 gems now installed.
[code] Use `bundle info [gemname]` to see where a bundled gem is installed.
[code-ruby] Bundle complete! 6 Gemfile dependencies, 44 gems now installed.
[code-ruby] Use `bundle info [gemname]` to see where a bundled gem is installed.

This is even more obvious with the parallel version:

Parallel.each($stdin.each_line.to_a) do |it|
  it.strip!
  eval(ARGV.first)
end

Solution

  • You can override system output and add prefix for every line of output:

    def system(cmd)
      rd, wr = IO.pipe
      Thread.new do
        Kernel.system(cmd, out: wr)
        wr.close
      end
    
      until rd.eof?
        line = rd.gets
        puts "[#{Thread.current[:prefix]}] #{line}"
      end
      rd.close
    end
    
    $stdin.each_line do |it|
      Thread.current[:prefix] = it.strip!
      eval(ARGV.first)
    end
    

    Test:

    $ ls | ./each "Dir.chdir(it) { system('bundle') }"
    
    [app1] Fetching gem metadata from https://rubygems.org/..........
    [app1] Resolving dependencies...
    [app1] Fetching minitest 5.25.1
    [app1] Fetching parallel 1.26.3
    [app1] Fetching brakeman 6.2.1
    [app1] Fetching rexml 3.3.6
    [app1] Installing parallel 1.26.3
    ...
    [app1] Fetching stimulus-rails 1.3.4
    [app1] Installing rails 7.2.1
    [app1] Installing stimulus-rails 1.3.4
    [app1] Bundle complete! 16 Gemfile dependencies, 99 gems now installed.
    [app1] Use `bundle info [gemname]` to see where a bundled gem is installed.
    [app2] Fetching gem metadata from https://rubygems.org/..........
    [app2] Resolving dependencies...
    [app2] Bundle complete! 16 Gemfile dependencies, 99 gems now installed.
    [app2] Use `bundle info [gemname]` to see where a bundled gem is installed.
    [app3] Fetching gem metadata from https://rubygems.org/..........
    [app3] Resolving dependencies...
    [app3] Bundle complete! 16 Gemfile dependencies, 99 gems now installed.
    [app3] Use `bundle info [gemname]` to see where a bundled gem is installed.
    

    https://docs.ruby-lang.org/en/master/Process.html#module-Process-label-File+Redirection+-28File+Descriptor-29


    Update

    You could run a separate process, like foreman does for each command, this way all output is redirected, and seems to work with parallel:

    require "parallel"
    
    Parallel.each($stdin.each_line.to_a) do |it|
      prefix = it.strip!
      rd, wr = IO.pipe
      pid = Process.spawn(RbConfig.ruby, "-e", %(it="#{it}"), "-e", ARGV.first, out: wr, err: wr)
      wr.close
      loop do
        if rd.eof?
          puts "[#{prefix}] process #{pid} closed"
          break
        else
          line = rd.gets
          puts "[#{prefix}] #{line}"
        end
      end
    end
    
    $ ls | ./each "Dir.chdir(it) { system('bundle'); puts 'hi' }" 
    
    [app2] Bundle complete! 16 Gemfile dependencies, 99 gems now installed.
    [app2] Use `bundle info [gemname]` to see where a bundled gem is installed.
    [app2] hi
    [app2] process 1808150 closed
    [app3] Bundle complete! 16 Gemfile dependencies, 99 gems now installed.
    [app3] Use `bundle info [gemname]` to see where a bundled gem is installed.
    [app3] hi
    [app3] process 1808151 closed
    [app1] Bundle complete! 16 Gemfile dependencies, 99 gems now installed.
    [app1] Use `bundle info [gemname]` to see where a bundled gem is installed.
    [app1] hi
    [app1] process 1808149 closed