Получение вывода системных вызовов () в 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?
FernandoFabreti
источник
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"
LRI
источник
Спасибо! Это потрясающе.
Грег Прайс
21

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

`ls`
хаос
источник
5
Обратные пометки не производят вывод на терминале.
Мэй
3
Он не производит stderr, но дает стандартный вывод.
Николай Кондратенко
1
Он не пишет в стандартный вывод или стандартный вывод. Давайте попробуем этот пример ruby -e '%x{ls}'- обратите внимание, нет вывода. (К вашему сведению %x{}эквивалентны кавычкам .)
ocodo
Это сработало отлично. Использование shбудет выводить вывод на консоль (т.е. STDOUT), а также возвращать его. Это не
Джошуа Пинтер
19

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

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

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

DWC
источник
Просто использовал это, чтобы прочитать стандартный вывод команды 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 #.

Ashrith
источник
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

dac2009
источник
-1
puts `date`
puts $?


Mon Mar  7 19:01:15 PST 2016
pid 13093 exit 0
Рон Хинчли
источник