Вызов команд оболочки из Ruby
Как вызвать команды оболочки из программы Ruby? Как я могу получить выход из этих команд обратно в Ruby?
Ответы
Ответ 1
Это объяснение основано на прокомментированном сценарии Ruby моего друга. Если вы хотите улучшить скрипт, обновите его по ссылке.
Во-первых, обратите внимание, что когда Ruby вызывает оболочку, он обычно вызывает /bin/sh
, а не Bash. Некоторый синтаксис Bash не поддерживается /bin/sh
во всех системах.
Вот способы выполнения сценария оболочки:
cmd = "echo 'hi'" # Sample string that can be used
Kernel#'
, обычно называемый backticks - 'cmd'
Это похоже на многие другие языки, включая Bash, PHP и Perl.
Возвращает результат (то есть стандартный вывод) команды оболочки.
Документы: http://ruby-doc.org/core/Kernel.html#method-i-60
value = 'echo 'hi''
value = '#{cmd}'
Встроенный синтаксис, %x( cmd )
После символа x
стоит разделитель, который может быть любым символом.
Если разделителем является один из символов (
, [
, {
или <
,
литерал состоит из символов до соответствующего закрывающего разделителя,
с учетом вложенных пар разделителей. Для всех других разделителей
литерал включает в себя символы до следующего появления
разделитель символа. Строковая интерполяция #{ ... }
разрешена.
Возвращает результат (т.е. стандартный вывод) команды оболочки, так же как обратные пометки.
Документы: http://www.ruby-doc.org/docs/ProgrammingRuby/html/language.html
value = %x( echo 'hi' )
value = %x[ #{cmd} ]
Kernel#system
Выполняет данную команду в подоболочке.
Возвращает true
, если команда была найдена и успешно выполнена, в противном случае false
.
Документы: http://ruby-doc.org/core/Kernel.html#method-i-system
wasGood = system( "echo 'hi'" )
wasGood = system( cmd )
Kernel#exec
Заменяет текущий процесс, выполнив данную внешнюю команду.
Не возвращает ничего, текущий процесс заменяется и никогда не продолжается.
Документы: http://ruby-doc.org/core/Kernel.html#method-i-exec
exec( "echo 'hi'" )
exec( cmd ) # Note: this will never be reached because of the line above
Вот несколько дополнительных советов:
$?
, который совпадает с $CHILD_STATUS
, получает доступ к состоянию последней выполненной системой команды, если вы используете обратные метки, system()
или %x{}
.
Затем вы можете получить доступ к свойствам exitstatus
и pid
:
$?.exitstatus
Для получения дополнительной информации см.
Ответ 2
Вот блок-схема, основанная на этом ответе. См. Также с помощью script
для эмуляции терминала.
Ответ 3
Как мне это нравится, используйте литерал %x
, который упрощает (и читает!) использование кавычек в команде, например:
directorylist = %x[find . -name '*test.rb' | sort]
В этом случае будет заполнен список файлов всеми тестовыми файлами в текущем каталоге, которые вы можете обработать так, как ожидалось:
directorylist.each do |filename|
filename.chomp!
# work with file
end
Ответ 4
Вот лучшая статья на мой взгляд о запуске сценариев оболочки в Ruby: " 6 способов запуска команд консоли в Ruby.
Если вам нужно только получить выходные данные, используйте обратные ссылки.
Мне нужны были более продвинутые вещи, такие как STDOUT и STDERR, поэтому я использовал камень Open4. Здесь есть все методы.
Ответ 5
Мой любимый Open3
require "open3"
Open3.popen3('nroff -man') { |stdin, stdout, stderr| ... }
Ответ 6
Некоторые вещи, которые следует учитывать при выборе между этими механизмами:
- Вы просто хотите stdout или вы
нужен stderr? или даже
отделены?
- Насколько велика ваша продукция? Вы хотите
сохранить весь результат в памяти?
- Вы хотите прочитать некоторые из своих
вывода, пока подпроцесс все еще
работает?
- Вам нужны коды результатов?
- Вам нужен рубиновый объект, который
представляет собой процесс и позволяет вам
убить его по требованию?
Вам может понадобиться что угодно: от простых обратных ссылок (``), system() и IO.popen
до полноразмерных Kernel.fork
/Kernel.exec
с помощью IO.pipe
и IO.select
.
Вы также можете запрограммировать тайм-ауты в миксе, если подпроцесс занимает слишком много времени.
К сожалению, это очень сильно зависит.
Ответ 7
Я определенно не специалист по Ruby, но я сделаю это:
$ irb
system "echo Hi"
Hi
=> true
Вы также должны иметь возможность делать такие вещи, как:
cmd = 'ls'
system(cmd)
Ответ 8
Еще один вариант:
Когда ты:
- нужен stderr, а также stdout
- не может/не будет использовать Open3/Open4 (они бросают исключения в NetBeans на моем Mac, не знаю, почему)
Вы можете использовать перенаправление оболочки:
puts %x[cat bogus.txt].inspect
=> ""
puts %x[cat bogus.txt 2>&1].inspect
=> "cat: bogus.txt: No such file or directory\n"
Синтаксис 2>&1
работает в Linux, Mac и Windows с первых дней MS-DOS.
Ответ 9
Ответы выше уже достаточно велики, но я действительно хочу поделиться следующей сводной статьей: " 6 способов запуска команд консоли в Ruby"
В основном, это говорит нам:
Kernel#exec
:
exec 'echo "hello $HOSTNAME"'
system
и $?
:
system 'false'
puts $?
Backticks (`):
today = `date`
IO#popen
:
IO.popen("date") { |f| puts f.gets }
Open3#popen3
- stdlib:
require "open3"
stdin, stdout, stderr = Open3.popen3('dc')
Open4#popen4
- драгоценный камень:
require "open4"
pid, stdin, stdout, stderr = Open4::popen4 "false" # => [26327, #<IO:0x6dff24>, #<IO:0x6dfee8>, #<IO:0x6dfe84>]
Ответ 10
Если вам действительно нужно Bash, за заметку в "лучшем" ответе.
Во-первых, обратите внимание, что когда Ruby вызывает оболочку, она обычно вызывает /bin/sh
, а не Bash. Синтаксис Bash не поддерживается /bin/sh
для всех систем.
Если вам нужно использовать Bash, вставьте bash -c "your Bash-only command"
внутри желаемого метода вызова.
quick_output = system("ls -la")
quick_bash = system("bash -c 'ls -la'")
Чтобы проверить:
system("echo $SHELL")
system('bash -c "echo $SHELL"')
Или, если вы используете существующий файл script (например, script_output = system("./my_script.sh")
), Ruby должен соблюдать shebang, но вы всегда можете использовать system("bash ./my_script.sh")
, чтобы убедиться (хотя могут быть небольшие накладные расходы от /bin/sh
/bin/bash
, вы, вероятно, не заметите.
Ответ 11
Вы также можете использовать операторы backtick (`), похожие на Perl:
directoryListing = `ls /`
puts directoryListing # prints the contents of the root directory
Удобно, если вам нужно что-то простое.
Какой метод вы хотите использовать, зависит от того, что вы пытаетесь выполнить; проверьте документы для получения более подробной информации о различных методах.
Ответ 12
проще всего, например:
reboot = `init 6`
puts reboot
Ответ 13
Мы можем достичь этого несколькими способами.
Используя Kernel#exec
, после выполнения этой команды ничего не происходит:
exec('ls ~')
Используя backticks or %x
`ls ~`
=> "Applications\nDesktop\nDocuments"
%x(ls ~)
=> "Applications\nDesktop\nDocuments"
Используя команду Kernel#system
, при успешном завершении true
false
, если она не удалась, и возвращает nil
, если выполнение команды завершается с ошибкой:
system('ls ~')
=> true
Ответ 14
Используя ответы здесь и связавшись в ответе Михай, я собрал функцию, отвечающую этим требованиям:
- Аккуратно захватывает STDOUT и STDERR, поэтому они не "утечки", когда мой script запускается с консоли.
- Позволяет передавать аргументы в оболочку как массив, поэтому нет необходимости беспокоиться об экранировании.
- Захват статуса выхода команды, поэтому становится ясно, когда произошла ошибка.
В качестве бонуса этот будет также возвращать STDOUT в случаях, когда команда оболочки успешно завершается (0) и помещает что-либо в STDOUT. Таким образом, он отличается от system
, который просто возвращает true
в таких случаях.
Далее следует код. Конкретная функция system_quietly
:
require 'open3'
class ShellError < StandardError; end
#actual function:
def system_quietly(*cmd)
exit_status=nil
err=nil
out=nil
Open3.popen3(*cmd) do |stdin, stdout, stderr, wait_thread|
err = stderr.gets(nil)
out = stdout.gets(nil)
[stdin, stdout, stderr].each{|stream| stream.send('close')}
exit_status = wait_thread.value
end
if exit_status.to_i > 0
err = err.chomp if err
raise ShellError, err
elsif out
return out.chomp
else
return true
end
end
#calling it:
begin
puts system_quietly('which', 'ruby')
rescue ShellError
abort "Looks like you don't have the `ruby` command. Odd."
end
#output: => "/Users/me/.rvm/rubies/ruby-1.9.2-p136/bin/ruby"
Ответ 15
Не забудьте команду spawn
создать фоновый процесс для выполнения указанной команды. Вы даже можете дождаться его завершения, используя класс Process
и возвращенный pid
:
pid = spawn("tar xf ruby-2.0.0-p195.tar.bz2")
Process.wait pid
pid = spawn(RbConfig.ruby, "-eputs'Hello, world!'")
Process.wait pid
Док говорит: этот метод похож на #system
, но он не ждет завершения команды.
Ответ 16
Если у вас более сложный случай, чем общий случай (который не может быть обработан с помощью ''
), тогда посмотрите Kernel.spawn()
здесь. Похоже, это наиболее общий/полнофункциональный инструмент, предоставляемый фондовым Ruby для выполнения внешних команд.
Например. Вы можете использовать его для:
- создавать группы процессов (Windows)
- перенаправлять ошибки, файлы/друг друга.
- установить env vars, umask
- изменить каталог перед выполнением команды
- установить ограничения ресурсов для процессора/данных/...
- Делайте все, что можно, используя другие варианты в других ответах, но с большим количеством кода.
Официальная рубиновая документация содержит достаточно хороших примеров.
env: hash
name => val : set the environment variable
name => nil : unset the environment variable
command...:
commandline : command line string which is passed to the standard shell
cmdname, arg1, ... : command name and one or more arguments (no shell)
[cmdname, argv0], arg1, ... : command name, argv[0] and zero or more arguments (no shell)
options: hash
clearing environment variables:
:unsetenv_others => true : clear environment variables except specified by env
:unsetenv_others => false : dont clear (default)
process group:
:pgroup => true or 0 : make a new process group
:pgroup => pgid : join to specified process group
:pgroup => nil : dont change the process group (default)
create new process group: Windows only
:new_pgroup => true : the new process is the root process of a new process group
:new_pgroup => false : dont create a new process group (default)
resource limit: resourcename is core, cpu, data, etc. See Process.setrlimit.
:rlimit_resourcename => limit
:rlimit_resourcename => [cur_limit, max_limit]
current directory:
:chdir => str
umask:
:umask => int
redirection:
key:
FD : single file descriptor in child process
[FD, FD, ...] : multiple file descriptor in child process
value:
FD : redirect to the file descriptor in parent process
string : redirect to file with open(string, "r" or "w")
[string] : redirect to file with open(string, File::RDONLY)
[string, open_mode] : redirect to file with open(string, open_mode, 0644)
[string, open_mode, perm] : redirect to file with open(string, open_mode, perm)
[:child, FD] : redirect to the redirected file descriptor
:close : close the file descriptor in child process
FD is one of follows
:in : the file descriptor 0 which is the standard input
:out : the file descriptor 1 which is the standard output
:err : the file descriptor 2 which is the standard error
integer : the file descriptor of specified the integer
io : the file descriptor specified as io.fileno
file descriptor inheritance: close non-redirected non-standard fds (3, 4, 5, ...) or not
:close_others => false : inherit fds (default for system and exec)
:close_others => true : dont inherit (default for spawn and IO.popen)
Ответ 17
Ответ 18
Учитывая команду, например,
require 'open3'
a="attrib"
Open3.popen3(a) do |stdin, stdout, stderr|
puts stdout.read
end
Я обнаружил, что, хотя этот метод не так запоминаем, как, например, система ("команда") или команда в backticks, хорошая вещь об этом методе по сравнению с другими методами.. например, обратные сигналы, похоже, не позволяют мне помещать 'команда, в которой я запускаю/сохраняю команду, которую я хочу запустить в переменной, и система ("thecommand"), похоже, не позволяет мне получить результат. В то время как этот метод позволяет мне делать обе эти вещи, и он позволяет мне самостоятельно обращаться к stdin, stdout и stderr.
https://blog.bigbinary.com/2012/10/18/backtick-system-exec-in-ruby.html
http://ruby-doc.org/stdlib-2.4.1/libdoc/open3/rdoc/Open3.html
Ответ 19
На самом деле это не ответ, но, возможно, кто-то найдет это полезным, и это касается этого.
При использовании TK GUI в Windows, и вам нужно вызвать команды оболочки из rubyw, u всегда будет раздражать CMD-окно, появляющееся менее чем за секунду.
Чтобы избежать этого, вы можете использовать
WIN32OLE.new('Shell.Application').ShellExecute('ipconfig > log.txt','','','open',0)
или
WIN32OLE.new('WScript.Shell').Run('ipconfig > log.txt',0,0)
Оба будут хранить вывод ipconfig внутри 'log.txt', но окна не появятся.
U нужно будет require 'win32ole'
внутри вашего скрипта.
system()
, exec()
и spawn()
будут всплывать в этом раздражающем окне при использовании TK и rubyw.
Ответ 20
Здесь классный, который я использую в ruby script для OS X (так что я могу запустить script и получить обновление даже после перехода от окна):
cmd = %Q|osascript -e 'display notification "Server was reset" with title "Posted Update"'|
system ( cmd )