Отримання вихідних системних () викликів у Ruby


309

Якщо я викликаю команду за допомогою системи Kernel # в Ruby, як я отримаю її вихід?

system("ls")

1
Ви можете подивитися цю тему в comp.lang.ruby
Manrico Corazzi

Це дуже ручна нитка, дякую. Клас для запуску команд та отримання зворотного зв'язку чудовий у зразковому коді.
ylluminate

3
Для майбутніх гуглерів. Якщо ви хочете дізнатися про інші виклики команд системи та їх відмінності, дивіться цю відповідь ТА .
Узбекьон

Відповіді:


347

Я хотів би трохи розширити та уточнити відповідь хаосу .

Якщо ви оточуєте свою команду за допомогою зворотних посилань, вам взагалі не потрібно (явно) викликати систему (). Повернення виконують команду і повертають результат у вигляді рядка. Потім ви можете призначити значення такої змінної:

output = `ls`
p output

або

printf output # escapes newline chars

4
що робити, якщо мені потрібно надати змінну як частину своєї команди? Тобто, що б перетворити щось на зразок системи ("ls" + ім'я файлу), коли потрібно використовувати задні посилання?
Vijay Dev

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

36
Ця відповідь не доцільна: вона вводить нову проблему несанкціонованого введення користувача.
Dogweather

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

20
якщо ви хочете зафіксувати stderr, просто поставте 2> & 1 в кінці вашої команди. наприклад, вихід =command 2>&1
micred

243

Майте на увазі, що всі рішення, куди ви передаєте рядок, що містить значення, надані користувачем system,%x[] тощо, небезпечні! Небезпечно насправді означає: користувач може викликати запуск коду у контексті та з усіма дозволами програми.

Наскільки я можу сказати лише systemі Open3.popen3надаю захищений / втікаючий варіант у Ruby 1.8. У Рубі 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).
Грег Ціна


Хтось знає, чи щось змінилося в Ruby 2.0 або 2.1? Правки чи коментарі будуть вдячні ;-)
Саймон Хюрліманн

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

165

Тільки для запису, якщо ви хочете і те, і інше (результат і результат роботи), ви можете зробити:

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

4
Це саме те, що я шукав. Дякую.
jdl

12
Це лише фіксує stdout, і stderr йде до консолі. Щоб отримати складніше, використовуйте: output=`ls no_existing_file 2>&1`; result=$?.success?
peterept

8
Ця відповідь небезпечна, і її не слід використовувати - якщо команда не що інше, як константа, синтаксис backtick, ймовірно, спричинить помилку, можливо, вразливість безпеки. (І навіть якщо це константа, вона, ймовірно, змусить когось використовувати її для непостійної пізніше і спричинить помилку.) Дивіться відповідь Саймона Хюрліманна для правильного рішення.
Грег Ціна

23
kudos Greg Price для розуміння необхідності уникнути введення користувачем даних, але сказати цю відповідь, як написана, це не безпечно. Згаданий метод Open3 є складнішим та вводить більше залежностей, і аргумент того, що хтось "використовуватиме його для непостійного пізніше", є слабким. Правда, ви, ймовірно, не використовували б їх у додатку Rails, але для простого скрипта системної утиліти без можливості ненадійного введення користувачем зворотні посилання є прекрасними, і ніхто не повинен змушувати себе погано користуватися ними.
sbeam

69

Простий спосіб зробити це правильно і надійно, щоб використовувати Open3.capture2(), Open3.capture2e()або Open3.capture3().

Використання рубінів та інших його %xпсевдонімів НЕ БЕЗПЕЧНО під будь-якими обертаннями, якщо вони використовуються з недовіреними даними. Це НЕБЕЗПЕЧНО , просто і просто:

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 * s автоматично. (Принаймні, я ніколи не стикався з проблемою на своєму кінці.)
Дені де Бернарді,

без використання форми блоку немає можливості кодовій базі знати, коли щось потрібно закрити, тому я дуже сумніваюся, що вони закриваються. Ви, ймовірно, ніколи не стикалися з проблемою, оскільки їх закриття не спричинить проблем у короткочасному процесі, і якщо ви перезапустите тривалий процес досить часто, 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.close stderr.close `` `Я просто цитую документацію« # STDIN, STDOUT і STDERR повинні бути закриті явно в цій формі. »..
Peter H. Boling

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

У публікації блогу Джея Філдса докладно пояснюються відмінності між системою, exec та% x [..].


2
Дякуємо за пораду використання% x []. Це просто вирішило проблему, в якій я використовував зворотні тики в рубіновому скрипті в 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

Ви використовуєте зворотні посилання:

`ls`

5
Повернення не дає результату на терміналі.
Май

3
Він не створює stderr, але дає stdout.
Микола Кондратенко

1
Він не пише в stdout або stderr. Спробуємо цей приклад ruby -e '%x{ls}'- зауважте, жодного виводу. (fyi %x{}еквівалентно
backtikks

Це спрацювало чудово. Використання shвідлучить вихід консолі (тобто STDOUT), а також поверне її. Це не так.
Джошуа Пінтер

19

Ще один спосіб:

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

Зверніть увагу, що символ "труба" перед "ls" відкрито. Це також можна використовувати для подачі даних на стандартний вхід програм, а також для зчитування його стандартного виводу.


Щойно використав це для читання стандартного виводу з команди aws cli, щоб прочитати json, а не офіційне значення повернення 'true'
kraftydevil

14

Я виявив, що наступне корисне, якщо вам потрібно повернути значення:

result = %x[ls]
puts result

Я спеціально хотів перелічити підписи всіх процесів Java на моїй машині і використав це:

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

Це чудове рішення.
Ронан Луарн


9

Хоча часто використовувати те, що ви бажаєте використовувати backtikks або 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

Цей метод фіксує будь-який вихід у даному блоці за допомогою tempfile для зберігання фактичних даних. Приклад використання:

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.