Как я могу скопировать STDOUT в файл, не останавливая его, показывая на экране с помощью Ruby

Я пытаюсь скопировать stdout в файл для ведения журнала. Я также хочу, чтобы он отображался в консоли Ruby IDE, которую я использую.

Я вставил этот код в свой script и перенаправляет $stdout в файл my.log:

$stdout.reopen("my.log", "w")

Кто-нибудь знает о камне или технике для копирования содержимого $stdout в файл и не перенаправляет его в файл? Кроме того, я не использую Rails только Ruby.

Ответ 1

Что-то вроде этого может помочь вам:

class TeeIO < IO

  def initialize orig, file
    @orig = orig
    @file = file
  end

  def write string
    @file.write string
    @orig.write string
  end

end

Большинство методов из IO, которые в конечном итоге выполняют вывод, используют write, поэтому вам нужно только переопределить этот метод. Вы можете использовать его следующим образом:

#setup
tee = TeeIO.new $stdout, File.new('out.txt', 'w')
$stdout = tee

# Now lots of example uses:
puts "Hello"
$stdout.puts "Extending IO allows us to expicitly use $stdout"
print "heres", :an, :example, "using", 'print', "\n"
48.upto(57) do |i|
  putc i
end
putc 10 #newline
printf "%s works as well - %d\n", "printf", 42
$stdout.write "Goodbye\n"

После этого примера одно и то же имя записывается как в консоль, так и в файл:

Hello
Extending IO allows us to expicitly use $stdout
heresanexampleusingprint
0123456789
printf works as well - 42
Goodbye

Я не буду утверждать, что этот метод не подходит, но он должен работать для простого использования stdout. Протестируйте его для использования.

Обратите внимание, что вам не нужно использовать reopen on $stdout, если вы не хотите перенаправлять вывод из дочернего процесса или неконвертируемого расширения. Простое назначение другого объекта IO для него будет работать для большинства целей.


RSpec

Командная строка RSpec ссылается на $stdout, прежде чем вы сможете запустить любой код для переназначения, поэтому это не сработает. reopen все еще работает в этом случае, когда вы меняете фактический объект, на который указывают как $stdout, так и ссылка, которую имеет RSpec, но это не дает вам выход для обоих.

Одним из решений является monkey-patch $stdout следующим образом:

$out_file = File.new('out.txt', 'w')

def $stdout.write string
  $out_file.write string
  super
end

Это работает, но, как и во всех исправлениях обезьян, будьте осторожны. Было бы безопаснее использовать команду OS tee.

Ответ 2

Если вы используете Linux или Mac OS, команда tee, доступная в ОС, упрощает это. На странице своего руководства:

NAME
     tee -- pipe fitting

SYNOPSIS
     tee [-ai] [file ...]

DESCRIPTION
     The tee utility copies standard input to standard output, making a copy in zero or more files.  The output is unbuffered.

Так что-то вроде:

echo '>foo bar' | tee tmp.out
>foo bar

echos вывод в STDOUT и в файл. Котировка файла дает мне:

cat tmp.out
>foo bar

В противном случае, если вы хотите сделать это внутри своего кода, это простая задача:

def tee_output(logfile)
  log_output = File.open(logfile, 'w+')
  ->(o) {
    log_output.puts o
    puts o
  }
end

tee = tee_output('tmp.out')
tee.call('foo bar')

Запуск:

>ruby test.rb 
foo bar

И проверка выходного файла:

>cat tmp.out
foo bar

Я бы использовал "w+" для доступа к файлу для добавления к выходному файлу, а не для его перезаписи.

CAVEAT: открывает файл и оставляет его открытым в течение срока действия кода после вызова метода tee_output. Это беспокоит некоторых людей, но, лично, меня это не беспокоит, потому что Ruby закроет файл, когда script завершает работу. В общем, мы хотим закрыть файлы, как только с ними закончим, но в вашем коде имеет смысл открыть его и оставить открытым, чем повторно открывать и закрывать выходной файл, но ваш пробег может отличаться.


EDIT:

Для Ruby 1.8.7 используйте lambda вместо нового синтаксиса ->:

def tee_output(logfile)
  log_output = File.open(logfile, 'w+')
  lambda { |o|
    log_output.puts o
    puts o
  }
end

tee = tee_output('tmp.out')
tee.call('foo bar')

Ответ 3

Я знаю, это старый вопрос, но я оказался в той же ситуации. Я написал Multi-IO Class, который расширяет методы File и overrode write puts и close, я также убедился, что его поток безопасен:

require 'singleton'

class MultiIO < File
  include Singleton 
  @@targets = []
  @@mutex = Mutex.new

  def self.instance
    self.open('/dev/null','w+')
  end

  def puts(str)
    write "#{str}\n"
  end

  def write(str)
    @@mutex.synchronize do 
      @@targets.each { |t| t.write str; flush }
    end
  end

  def setTargets(targets)
    raise 'setTargets is a one-off operation' unless @@targets.length < 1
    targets.each do |t|
       @@targets.push STDOUT.clone if t == STDOUT 
       @@targets.push STDERR.clone if t == STDERR
       break if t == STDOUT or t == STDERR  
       @@targets.push(File.open(t,'w+'))
    end
    self
  end

  def close
    @@targets.each {|t| f.close}
  end
end

STDOUT.reopen MultiIO.instance.setTargets(['/tmp/1.log',STDOUT,STDERR])
STDERR.reopen STDOUT 


threads = []

5.times.each do |i| 
  threads.push( 
    Thread.new do
      10000.times.each do |j|
        STDOUT.puts "out#{i}:#{j}"
      end
    end
  )
end

5.times.each do |i| 
  threads.push( 
    Thread.new do
      10000.times.each do |j|
        STDERR.puts "err#{i}:#{j}"
      end
    end
  )
end

threads.each {|t| t.join}