Как я могу скопировать STDOUT в файл, не останавливая его показ на экране с помощью Ruby - PullRequest
11 голосов
/ 24 февраля 2012

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

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

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

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

Ответы [ 4 ]

9 голосов
/ 25 февраля 2012

Нечто подобное может вам помочь:

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

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

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


RSpec

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

Одним из решений является обезьяна-патч $stdout, например:

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

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

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

4 голосов
/ 24 февраля 2012

Если вы используете 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

выводит вывод в STDOUT и в файл. Catting файл дает мне:

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 закроет файл при выходе из скрипта. В общем, мы хотим закрыть файлы, как только закончим с ними, но в вашем коде более разумно открыть и оставить его открытым, чем повторно открывать и закрывать выходной файл, но ваш пробег может отличаться.


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')
1 голос
/ 18 октября 2017

Я знаю, что это старый вопрос, но я оказался в той же ситуации. Я написал Multi-IO Class, который расширяет File и переопределяет 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}
0 голосов
/ 12 апреля 2018

Крошечное улучшение от матов ответ:

class TeeIO < IO
  def initialize(ios)
    @ios = ios
  end

  def write(string)
    @ios.each { |io| io.write string }
  end
end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...