Получение вывода системных вызовов () в Ruby


309

Если я вызываю команду, использующую систему Kernel # в Ruby, как мне получить ее вывод?

system("ls")

1
Возможно, вы захотите взглянуть на эту тему в comp.lang.ruby
Манрико Корацци,

Это очень ручная тема, спасибо. Класс для запуска команд и получения обратной связи великолепен в примере кода.
Иллюминат

3
Для будущих гуглеров. Если вы хотите узнать о других вызовах системных команд и их различиях, посмотрите этот SO-ответ .
Узбекджон

Ответы:


347

Я хотел бы немного расширить и уточнить ответ хаоса .

Если вы окружаете свою команду обратными галочками, то вам вообще не нужно (явно) вызывать system (). Обратные галочки выполняют команду и возвращают вывод в виде строки. Затем вы можете присвоить значение переменной следующим образом:

output = `ls`
p output

или

printf output # escapes newline chars

4
Что делать, если мне нужно дать переменную как часть моей команды? То есть, на что будет похожа система ("ls" + имя файла), когда следует использовать обратные метки?
Виджей Дев

47
Вы можете сделать оценку выражения так же , как вы бы с регулярными строками: ls #{filename}.
Крейг Уокер,

36
Этот ответ не рекомендуется: он вводит новую проблему неанизированного пользовательского ввода.
Dogweather

4
@ Dogweather: это может быть правдой, но отличается ли это от любых других методов?
Крейг Уокер,

20
если вы хотите захватить stderr, просто поставьте 2> & 1 в конце вашей команды. например, выход =command 2>&1
micred

243

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


26
Это единственный ответ, который фактически отвечает на вопрос и решает проблему, не вводя новые (неантифицированный ввод).
Dogweather

2
Спасибо! Это такой ответ, на который я надеялся. Одно исправление: getsвызовы должны передавать аргумент nil, иначе мы просто получим первую строку вывода. Так, например stdout.gets(nil).
Грег Прайс

3
stdin, stdout и stderr должны быть явно закрыты в неблокированной форме .
Ярин

Кто-нибудь знает, что-то изменилось в Ruby 2.0 или 2.1? Будем благодарны за правки и комментарии ;-)
Simon Hürlimann

1
Я думаю, что в обсуждении Open3.popen3отсутствует главная проблема: если у вас есть подпроцесс, который записывает в стандартный вывод больше данных, чем может выдержать канал, подпроцесс приостанавливается stderr.write, и ваша программа застревает stdout.gets(nil).
Гагелло

165

Просто для записи, если вы хотите оба (вывод и результат операции), вы можете сделать:

output=`ls no_existing_file` ;  result=$?.success?

4
Это именно то, что я искал. Спасибо.
JDL

12
Это только захватывает стандартный вывод, и стандартный вывод идет на консоль. Чтобы получить stderr, используйте: output=`ls no_existing_file 2>&1`; result=$?.success?
peterept

8
Этот ответ небезопасен и не должен использоваться - если команда не является константой, то синтаксис обратного удара может вызвать ошибку, возможно, уязвимость системы безопасности. (И даже если это константа, это, вероятно, заставит кого-то позже использовать ее для непостоянной и вызвать ошибку.) См . Ответ Саймона Хюрлиманна для правильного решения.
Грег Прайс

23
Слава Грегу Прайсу за понимание необходимости избегать пользовательского ввода, но неправильно писать этот ответ небезопасно. Упомянутый метод Open3 является более сложным и вводит больше зависимостей, и аргумент, что кто-то «будет использовать его для неконстантного позже», является бессмысленным. Правда, вы, вероятно, не использовали бы их в приложении на Rails, но для простого сценария системной утилиты без возможности ненадежного пользовательского ввода обратные помехи совершенно хороши, и никто не должен расстраиваться из-за их использования.
sbeam

69

Простой способ сделать это правильно и надежно является использование 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')

2
Это правильный ответ. Это также наиболее информативно. Единственное, чего не хватает, это предупреждения о закрытии std * s. Смотрите этот другой комментарий : require 'open3'; output = Open3.popen3("ls") { |stdin, stdout, stderr, wait_thr| stdout.read } Обратите внимание, что блочная форма автоматически закрывает stdin, stdout и stderr, иначе они должны быть закрыты явно .
Питер Х. Болинг

@ PeterH.Boling: Лучше я знаю, то capture2, capture2eи capture3также близко их StD * с автоматически. (По крайней мере, я никогда не сталкивался с проблемой с моей стороны.)
Дени де Бернарди

без использования блочной формы кодовая база не сможет узнать, когда что-то должно быть закрыто, поэтому я очень сомневаюсь, что они закрываются. Вы, вероятно, никогда не сталкивались с проблемой, потому что ее закрытие не вызовет проблем в недолговечном процессе, и если вы достаточно часто перезапускаете длительный процесс, то otto не будет отображаться там, если вы не открываете std * s в цикл. В Linux существует высокий предел дескриптора файла, который вы можете использовать, но пока вы его не достигнете, вы не увидите «ошибки».
Питер Х. Болинг

2
@ PeterH.Boling: Нет, нет, см. Исходный код. Функции просто обертки вокруг Open3#popen2, popen2eи popen3с предопределенным блока: ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/...
Дени де Бернарди

1
@Dennis de Barnardy Возможно, вы пропустили, что я ссылался на одну и ту же документацию по классу (хотя и для Ruby 2.0.0 и с другим методом. Ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/… из примера : `` `stdin, stdout, stderr, wait_thr = Open3.popen3 ([env,] cmd ... [, opts]) pid = wait_thr [: pid] # pid запущенного процесса ... stdin.close # stdin , stdout и stderr должны быть явно закрыты в этой форме. stdout.close stderr.close `` `Я просто цитирую документацию." # stdin, stdout и stderr должны быть явно закрыты в этой форме. "
Питер Х. Болинг

61

Вы можете использовать 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 [..].


2
Спасибо за подсказку использования% x []. Это просто решило проблему, с которой я столкнулся, когда я использовал обратные тики в скрипте ruby ​​в Mac OS X. При запуске того же скрипта на компьютере Windows с Cygwin он не работал из-за обратных тиков, но работал с% x [].
Хенрик Варн

22

Если вам нужно экранировать аргументы, в 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"

Спасибо! Это потрясающе.
Грег Прайс

21

Вы используете backticks:

`ls`

5
Обратные пометки не производят вывод на терминале.
Мэй

3
Он не производит stderr, но дает стандартный вывод.
Николай Кондратенко

1
Он не пишет в стандартный вывод или стандартный вывод. Давайте попробуем этот пример ruby -e '%x{ls}'- обратите внимание, нет вывода. (К вашему сведению %x{}эквивалентны кавычкам .)
ocodo

Это сработало отлично. Использование shбудет выводить вывод на консоль (т.е. STDOUT), а также возвращать его. Это не
Джошуа Пинтер

19

Другой способ это:

f = open("|ls")
foo = f.read()

Обратите внимание, что это символ "pipe" перед открытием "ls". Это также может быть использовано для подачи данных в стандартный ввод программы, а также для считывания стандартного вывода.


Просто использовал это, чтобы прочитать стандартный вывод команды aws cli, чтобы прочитать json, а не официальное возвращаемое значение 'true'
kraftydevil

14

Я обнаружил, что следующее полезно, если вам нужно возвращаемое значение:

result = %x[ls]
puts result

Я специально хотел перечислить pids всех процессов Java на моем компьютере и использовал это:

ids = %x[ps ax | grep java | awk '{ print $1 }' | xargs]

Это отличное решение.
Ронан Луарн


9

Хотя использование обратных кавычек или 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если хотите.


8

Если вы хотите, чтобы вывод перенаправлялся в файл с помощью Kernel#system, вы можете изменить дескрипторы следующим образом:

перенаправить stdout и stderr в файл (/ tmp / log) в режиме добавления:

system('ls -al', :out => ['/tmp/log', 'a'], :err => ['/tmp/log', 'a'])

Для длительной команды это сохранит выходные данные в реальном времени. Вы также можете сохранить вывод, используя IO.pipe и перенаправить его из системы Kernel #.



0

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

Вы можете перенаправить STDERR в STDOUT, если хотите захватить STDERR с помощью backtick.

output = `grep hosts / private / etc / * 2> & 1`

источник: http://blog.bigbinary.com/2012/10/18/backtick-system-exec-in-ruby.html


Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.