Якщо я викликаю команду за допомогою системи Kernel # в Ruby, як я отримаю її вихід?
system("ls")
Якщо я викликаю команду за допомогою системи Kernel # в Ruby, як я отримаю її вихід?
system("ls")
Відповіді:
Я хотів би трохи розширити та уточнити відповідь хаосу .
Якщо ви оточуєте свою команду за допомогою зворотних посилань, вам взагалі не потрібно (явно) викликати систему (). Повернення виконують команду і повертають результат у вигляді рядка. Потім ви можете призначити значення такої змінної:
output = `ls`
p output
або
printf output # escapes newline chars
ls #{filename}
.
command 2>&1
Майте на увазі, що всі рішення, куди ви передаєте рядок, що містить значення, надані користувачем 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
gets
виклики повинні передавати аргумент nil
, оскільки в іншому випадку ми просто отримуємо перший рядок виводу. Так, наприклад stdout.gets(nil)
.
Open3.popen3
не вистачає головної проблеми: Якщо у вас є підпроцес, який записує більше даних для stdout, ніж може тримати труба, підпроцес призупиняється 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
псевдонімів НЕ БЕЗПЕЧНО під будь-якими обертаннями, якщо вони використовуються з недовіреними даними. Це НЕБЕЗПЕЧНО , просто і просто:
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 * s автоматично. (Принаймні, я ніколи не стикався з проблемою на своєму кінці.)
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
У публікації блогу Джея Філдса докладно пояснюються відмінності між системою, 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"
Ви використовуєте зворотні посилання:
`ls`
ruby -e '%x{ls}'
- зауважте, жодного виводу. (fyi %x{}
еквівалентно
sh
відлучить вихід консолі (тобто STDOUT), а також поверне її. Це не так.
Ще один спосіб:
f = open("|ls")
foo = f.read()
Зверніть увагу, що символ "труба" перед "ls" відкрито. Це також можна використовувати для подачі даних на стандартний вхід програм, а також для зчитування його стандартного виводу.
Я виявив, що наступне корисне, якщо вам потрібно повернути значення:
result = %x[ls]
puts result
Я спеціально хотів перелічити підписи всіх процесів 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 - інакше їх доведеться явно закрити .
Хоча часто використовувати те, що ви бажаєте використовувати 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
якщо хочете.
Якщо ви хочете, щоб вихід переспрямований у файл за допомогою 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