Как я могу скопировать 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}