Если я вызываю команду, использующую систему Kernel # в Ruby, как мне получить ее вывод?
system("ls")
Если я вызываю команду, использующую систему Kernel # в Ruby, как мне получить ее вывод?
system("ls")
Ответы:
Я хотел бы немного расширить и уточнить ответ хаоса .
Если вы окружаете свою команду обратными галочками, то вам вообще не нужно (явно) вызывать system (). Обратные галочки выполняют команду и возвращают вывод в виде строки. Затем вы можете присвоить значение переменной следующим образом:
output = `ls`
p output
или
printf output # escapes newline chars
ls #{filename}
.
command 2>&1
Помните, что все решения, в которых вы передаете строку, содержащую предоставленные пользователем значения system
,%x[]
и т.д. небезопасны! На самом деле небезопасный означает: пользователь может запустить код для запуска в контексте и со всеми разрешениями программы.
Насколько я могу сказать, system
и Open3.popen3
предоставить безопасный / экранирующий вариант в Ruby 1.8. В Ruby 1.9IO::popen
также принимает массив.
Просто передайте каждый параметр и аргумент в виде массива одному из этих вызовов.
Если вам нужен не только статус выхода, но и результат, который вы, вероятно, хотите использовать Open3.popen3
:
require 'open3'
stdin, stdout, stderr, wait_thr = Open3.popen3('usermod', '-p', @options['shadow'], @options['username'])
stdout.gets(nil)
stdout.close
stderr.gets(nil)
stderr.close
exit_code = wait_thr.value
Обратите внимание, что блочная форма автоматически закроет stdin, stdout и stderr, иначе они должны быть закрыты явно .
Более подробная информация здесь: Формирование команд санитарной оболочки или системных вызовов в Ruby.
gets
вызовы должны передавать аргумент nil
, иначе мы просто получим первую строку вывода. Так, например stdout.gets(nil)
.
Open3.popen3
отсутствует главная проблема: если у вас есть подпроцесс, который записывает в стандартный вывод больше данных, чем может выдержать канал, подпроцесс приостанавливается stderr.write
, и ваша программа застревает stdout.gets(nil)
.
Просто для записи, если вы хотите оба (вывод и результат операции), вы можете сделать:
output=`ls no_existing_file` ; result=$?.success?
output=`ls no_existing_file 2>&1`; result=$?.success?
Простой способ сделать это правильно и надежно является использование Open3.capture2()
, Open3.capture2e()
илиOpen3.capture3()
.
Использование обратных ссылок и %x
псевдонима ruby НЕ БЕЗОПАСНО ПОД ЛЮБЫМИ ОБСТОЯТЕЛЬСТВАМИ, если используется с ненадежными данными. Это ОПАСНО , просто и понятно:
untrusted = "; date; echo"
out = `echo #{untrusted}` # BAD
untrusted = '"; date; echo"'
out = `echo "#{untrusted}"` # BAD
untrusted = "'; date; echo'"
out = `echo '#{untrusted}'` # BAD
system
Функция, в отличие от этого , сбегает аргументы должным образом , если они используются правильно :
ret = system "echo #{untrusted}" # BAD
ret = system 'echo', untrusted # good
Проблема в том, что он возвращает код выхода вместо вывода, а захват последнего является запутанным и грязным.
Лучший ответ в этой теме на данный момент упоминает Open3, но не функции, которые лучше всего подходят для этой задачи. Open3.capture2
, capture2e
И capture3
работа , как system
, но возвращает два или три аргумента:
out, err, st = Open3.capture3("echo #{untrusted}") # BAD
out, err, st = Open3.capture3('echo', untrusted) # good
out_err, st = Open3.capture2e('echo', untrusted) # good
out, st = Open3.capture2('echo', untrusted) # good
p st.exitstatus
Другой упоминает IO.popen()
. Синтаксис может быть неуклюжим в том смысле, что ему нужен массив в качестве входных данных, но он тоже работает:
out = IO.popen(['echo', untrusted]).read # good
Для удобства вы можете заключить Open3.capture3()
в функцию, например:
#
# Returns stdout on success, false on failure, nil on error
#
def syscall(*cmd)
begin
stdout, stderr, status = Open3.capture3(*cmd)
status.success? && stdout.slice!(0..-(1 + $/.size)) # strip trailing eol
rescue
end
end
Пример:
p system('foo')
p syscall('foo')
p system('which', 'foo')
p syscall('which', 'foo')
p system('which', 'which')
p syscall('which', 'which')
Получает следующее:
nil
nil
false
false
/usr/bin/which <— stdout from system('which', 'which')
true <- p system('which', 'which')
"/usr/bin/which" <- p syscall('which', 'which')
require 'open3'; output = Open3.popen3("ls") { |stdin, stdout, stderr, wait_thr| stdout.read }
Обратите внимание, что блочная форма автоматически закрывает stdin, stdout и stderr, иначе они должны быть закрыты явно .
capture2
, capture2e
и capture3
также близко их StD * с автоматически. (По крайней мере, я никогда не сталкивался с проблемой с моей стороны.)
Open3#popen2
, popen2e
и popen3
с предопределенным блока: ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/...
Вы можете использовать system () или% x [] в зависимости от того, какой результат вам нужен.
system () возвращает true, если команда была найдена и успешно выполнена, в противном случае - false.
>> s = system 'uptime'
10:56 up 3 days, 23:10, 2 users, load averages: 0.17 0.17 0.14
=> true
>> s.class
=> TrueClass
>> $?.class
=> Process::Status
% x [..] с другой стороны сохраняет результаты команды в виде строки:
>> result = %x[uptime]
=> "13:16 up 4 days, 1:30, 2 users, load averages: 0.39 0.29 0.23\n"
>> p result
"13:16 up 4 days, 1:30, 2 users, load averages: 0.39 0.29 0.23\n"
>> result.class
=> String
В блоге Джея Филдса подробно объясняются различия между использованием system, exec и% x [..].
Если вам нужно экранировать аргументы, в Ruby 1.9 IO.popen также принимает массив:
p IO.popen(["echo", "it's escaped"]).read
В более ранних версиях вы можете использовать Open3.popen3 :
require "open3"
Open3.popen3("echo", "it's escaped") { |i, o| p o.read }
Если вам также нужно передать stdin, это должно работать как в 1.9, так и в 1.8:
out = IO.popen("xxd -p", "r+") { |io|
io.print "xyz"
io.close_write
io.read.chomp
}
p out # "78797a"
Вы используете backticks:
`ls`
ruby -e '%x{ls}'
- обратите внимание, нет вывода. (К вашему сведению %x{}
эквивалентны кавычкам .)
sh
будет выводить вывод на консоль (т.е. STDOUT), а также возвращать его. Это не
Другой способ это:
f = open("|ls")
foo = f.read()
Обратите внимание, что это символ "pipe" перед открытием "ls". Это также может быть использовано для подачи данных в стандартный ввод программы, а также для считывания стандартного вывода.
Я обнаружил, что следующее полезно, если вам нужно возвращаемое значение:
result = %x[ls]
puts result
Я специально хотел перечислить pids всех процессов Java на моем компьютере и использовал это:
ids = %x[ps ax | grep java | awk '{ print $1 }' | xargs]
Как уже объяснил Саймон Хюрлиманн , Open3 безопаснее, чем обратные трюки и т. Д.
require 'open3'
output = Open3.popen3("ls") { |stdin, stdout, stderr, wait_thr| stdout.read }
Обратите внимание, что блочная форма автоматически закроет stdin, stdout и stderr, иначе они должны быть закрыты явно .
Хотя использование обратных кавычек или popen часто является тем, что вам действительно нужно, на самом деле оно не отвечает на заданный вопрос. Могут быть веские причины для получения system
результатов (возможно, для автоматического тестирования). Немного погуглил ответ я решил опубликовать здесь для блага других.
Так как мне нужно было это для тестирования, мой пример использует настройку блока для захвата стандартного вывода, так как фактический system
вызов скрыт в тестируемом коде:
require 'tempfile'
def capture_stdout
stdout = $stdout.dup
Tempfile.open 'stdout-redirect' do |temp|
$stdout.reopen temp.path, 'w+'
yield if block_given?
$stdout.reopen stdout
temp.read
end
end
Этот метод захватывает любой вывод в данном блоке, используя временный файл для хранения фактических данных. Пример использования:
captured_content = capture_stdout do
system 'echo foo'
end
puts captured_content
Вы можете заменить system
звонок на все, что звонит внутри system
. Вы также можете использовать аналогичный метод для захвата, stderr
если хотите.
Если вы хотите, чтобы вывод перенаправлялся в файл с помощью Kernel#system
, вы можете изменить дескрипторы следующим образом:
перенаправить stdout и stderr в файл (/ tmp / log) в режиме добавления:
system('ls -al', :out => ['/tmp/log', 'a'], :err => ['/tmp/log', 'a'])
Для длительной команды это сохранит выходные данные в реальном времени. Вы также можете сохранить вывод, используя IO.pipe и перенаправить его из системы Kernel #.
В качестве прямой замены системы (...) вы можете использовать Open3.popen3 (...)
Дальнейшее обсуждение: http://tech.natemurray.com/2007/03/ruby-shell-commands.html
Я не нашел этого здесь, поэтому добавляя его, у меня были некоторые проблемы с получением полного вывода.
Вы можете перенаправить STDERR в STDOUT, если хотите захватить STDERR с помощью backtick.
output = `grep hosts / private / etc / * 2> & 1`
источник: http://blog.bigbinary.com/2012/10/18/backtick-system-exec-in-ruby.html
puts `date`
puts $?
Mon Mar 7 19:01:15 PST 2016
pid 13093 exit 0