Как вызывать команды оболочки из Ruby


1077

Как мне вызвать команды оболочки изнутри программы на Ruby? Как я могу получить вывод этих команд обратно в Ruby?


3
Хотя этот вопрос полезен, он не очень хорошо задан. В Ruby есть много способов вызова вложенных оболочек, которые хорошо документированы и легко найдены, прочитав документацию по Kernel и Open3 и выполнив поиск здесь в SO.
Оловянный Человек

1
К сожалению, эта тема довольно сложная. Open3( docs ) - лучший выбор для большинства ситуаций, IMO, но в старых версиях Ruby он не учитывает измененные PATH( bugs.ruby-lang.org/issues/8004 ) и зависит от того, как вы передаете аргументы (в частности, , если вы используете хэш opts с не ключевыми словами), он может сломаться. Но если вы попадаете в такие ситуации, вы делаете что-то довольно продвинутое, и вы можете понять, что делать, прочитав реализацию Open3.
Джошуа Чик

3
Я удивлен, что никто не упомянул Shellwords.escape( док ). Вы не хотите вставлять пользовательский ввод непосредственно в команды оболочки - сначала удалите его! Смотрите также командный впрыск .
Кельвин

Ответы:


1319

Это объяснение основано на комментируемом сценарии Ruby от моего друга. Если вы хотите улучшить скрипт, обновите его по ссылке.

Во-первых, обратите внимание, что когда Ruby вызывает оболочку, он обычно вызывает /bin/sh, а не Bash. Некоторый синтаксис Bash поддерживается не /bin/shво всех системах.

Вот способы выполнить скрипт оболочки:

cmd = "echo 'hi'" # Sample string that can be used
  1. Kernel#` обычно называют backticks - `cmd`

    Это похоже на многие другие языки, включая Bash, PHP и Perl.

    Возвращает результат (т.е. стандартный вывод) команды оболочки.

    Документы: http://ruby-doc.org/core/Kernel.html#method-i-60

    value = `echo 'hi'`
    value = `#{cmd}`
  2. Встроенный синтаксис, %x( cmd )

    За xсимволом следует разделитель, которым может быть любой символ. Если разделитель является одним из символов (, [, {или <, в буквальном смысле состоит из символов , до закрытия соответствия разделителя, с учетом вложенных пар разделителей. Для всех других разделителей литерал содержит символы до следующего вхождения символа разделителя. Строковая интерполяция #{ ... }разрешена.

    Возвращает результат (то есть стандартный вывод) команды оболочки, так же как обратные пометки.

    Документы: https://docs.ruby-lang.org/en/master/syntax/literals_rdoc.html#label-Percent+Strings

    value = %x( echo 'hi' )
    value = %x[ #{cmd} ]
  3. Kernel#system

    Выполняет данную команду в подоболочке.

    Возвращает, trueесли команда была найдена и успешно выполнена, в falseпротивном случае.

    Документы: http://ruby-doc.org/core/Kernel.html#method-i-system

    wasGood = system( "echo 'hi'" )
    wasGood = system( cmd )
  4. 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

Для получения дополнительной информации см .:


4
Мне нужно записать результаты моего исполняемого файла на производственном сервере, но не нашел пути. Я использовал #{cmd}put и logger.info ( #{cmd}). Есть ли способ зарегистрировать свои выходы на производстве?
Омер Аслам

5
И IO # popen () и Open3 # popen3 (). mentalized.net/journal/2010/03/08/...
hughdbrown

6
Для полноты (как я сначала подумал, это также будет команда Ruby): Rake имеет sh, который выполняет команду «Выполнить системную команду cmd. Если задано несколько аргументов, команда не запускается с оболочкой (семантика та же, что и у Kernel ::». exec и Kernel :: system) ".
sschuberth

40
Обратные пометки не захватывают STDERR по умолчанию. Добавьте `2> & 1` в команду, если хотите захватить
Андрей Боталов

14
Я думаю, что этот ответ был бы немного улучшен, если бы он сказал, что обратные пометки и% x вернули «результат», а не «результат» данной команды. Последний может быть ошибочно принят за статус выхода. Или это только я?
skagedal

275

24
Вау хаха Очень полезно, хотя тот факт, что это должно существовать, вызывает сожаление
Джош Бода

В качестве примечания, я нахожу метод spawn (), найденный во многих разных местах (например, Kernelи Processнаиболее универсальным. Он более или менее такой же PTY.spawn(), но более общий)
Смарт,

160

Мне нравится делать это, используя %xлитерал, который позволяет легко (и удобно читать) использовать кавычки в команде, например так:

directorylist = %x[find . -name '*test.rb' | sort]

Который, в этом случае, заполнит список файлов всеми тестовыми файлами в текущем каталоге, который вы можете обработать, как ожидается:

directorylist.each do |filename|
  filename.chomp!
  # work with file
end

4
Имеет ли %x[ cmd ]возвращает массив для вас?
x-yuri

2
вышеупомянутое не работает для меня. `` <main> ': неопределенный метод, each' for :String (NoMethodError) как он работал для вас? Я использую ruby -v ruby 1.9.3p484 (2013-11-22 revision 43786) [i686-linux]Вы уверены, что массив возвращается из команды, чтобы цикл действительно работал?
Насер

% x [cmd] .split ("\ n") вернет список, хотя :)
Ян Эллис

65

Вот лучшая на мой взгляд статья о запуске сценариев оболочки в Ruby: « 6 способов запуска команд оболочки в Ruby ».

Если вам нужно только получить выходные данные, используйте обратные метки.

Мне нужны были более продвинутые вещи, такие как STDOUT и STDERR, поэтому я использовал камень Open4. У вас есть все методы, объясненные там.


2
Пост, описанный здесь, не обсуждает %xопцию синтаксиса.
Мей

+1 для Open4. Я уже начал пытаться реализовать собственную версию его spawnметода, когда нашел это.
Брендан

40

Мой любимый это Open3

  require "open3"

  Open3.popen3('nroff -man') { |stdin, stdout, stderr| ... }

3
Мне также нравится open3, особенно Open3.capture3: ruby-doc.org/stdlib-1.9.3/libdoc/open3/rdoc/… -> stdout, stderr, status = Open3.capture3('nroff -man', :stdin_data => stdin)
Северин

Есть ли в Ruby std-lib документация о том, как выполнять Spec и модульное тестирование с помощью Open3 или других Open? На моем нынешнем уровне понимания сложно протестировать оболочки.
FilBot3

29

При выборе между этими механизмами необходимо учитывать следующие факторы:

  1. Вы просто хотите использовать стандартный вывод или вам тоже нужен стандартный вывод? Или даже выделены?
  2. Насколько велика ваша продукция? Вы хотите сохранить весь результат в памяти?
  3. Вы хотите прочитать некоторые из ваших выводов, пока подпроцесс еще работает?
  4. Вам нужны коды результатов?
  5. Вам нужен объект Ruby, который представляет процесс и позволяет вам убивать его по требованию?

Возможно , вам что - нибудь из простых обратных кавычек ( ``), system()и IO.popenв полномасштабный Kernel.fork/ Kernel.execс IO.pipeи IO.select.

Вы также можете добавить тайм-ауты в микс, если подпроцесс занимает слишком много времени для выполнения.

К сожалению, это очень многое зависит .


25

Еще один вариант:

Когда ты:

  • нужен как 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.


25

Я определенно не эксперт по Ruby, но я попробую:

$ irb 
system "echo Hi"
Hi
=> true

Вы также должны быть в состоянии сделать такие вещи, как:

cmd = 'ls'
system(cmd)

21

Ответы выше уже довольно хороши, но я действительно хочу поделиться следующей сводной статьей: « 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>]

15

Если вам действительно нужен 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_output = system("./my_script.sh")

Руби должен соблюдать шебанг, но вы всегда можете использовать

system("bash ./my_script.sh")

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


11

Вы также можете использовать операторы backtick (`), аналогичные Perl:

directoryListing = `ls /`
puts directoryListing # prints the contents of the root directory

Удобно, если вам нужно что-то простое.

Какой метод вы хотите использовать, зависит от того, что именно вы пытаетесь достичь; проверьте документы для более подробной информации о различных методах.


10

Мы можем достичь этого несколькими способами.

Используя Kernel#exec, ничего после этой команды не выполняется:

exec('ls ~')

С помощью backticks or %x

`ls ~`
=> "Applications\nDesktop\nDocuments"
%x(ls ~)
=> "Applications\nDesktop\nDocuments"

Использование Kernel#systemкоманды возвращает в trueслучае успеха, falseесли не удалось, и возвращает в nilслучае сбоя выполнения команды:

system('ls ~')
=> true


9

Используя ответы здесь и связанные в ответе Михая, я собрал функцию, которая отвечает этим требованиям:

  1. Аккуратно фиксирует STDOUT и STDERR, чтобы они не «просачивались», когда мой скрипт запускается из консоли.
  2. Позволяет передавать аргументы в оболочку в виде массива, поэтому вам не нужно беспокоиться об экранировании.
  3. Записывает состояние завершения команды, чтобы было ясно, когда произошла ошибка.

В качестве бонуса этот также будет возвращать 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"

9

Не забудьте 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но он не ждет завершения команды.


2
Kernel.spawn()кажется гораздо более универсальным, чем все другие варианты.
Кашьяп

6

Если у вас есть более сложный случай, чем общий случай, с которым невозможно справиться ``, то ознакомьтесь с Kernel.spawn() . Похоже, это наиболее универсальный / полнофункциональный инструмент, предоставляемый стандартным Ruby для выполнения внешних команд.

Вы можете использовать его для:

  • создать группы процессов (Windows).
  • перенаправлять ошибки, файлы / друг друга.
  • установить env vars, umask.
  • измените каталог перед выполнением команды.
  • установить ограничения ресурсов для процессора / данных / и т. д.
  • Делайте все, что можно сделать с другими вариантами в других ответах, но с большим количеством кода.

Документация Ruby имеет достаточно хороших примеров:

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)

6

Метод backticks (`) является самым простым для вызова команд оболочки из Ruby. Возвращает результат команды оболочки:

     url_request = 'http://google.com'
     result_of_shell_command = `curl #{url_request}`

5

Дана команда типа attrib:

require 'open3'

a="attrib"
Open3.popen3(a) do |stdin, stdout, stderr|
  puts stdout.read
end

Я обнаружил, что хотя этот метод не так запоминающийся, как

system("thecommand")

или

`thecommand`

в обратном кавычках хорошая вещь в этом методе по сравнению с другими методами в том, что обратные кавычки не позволяют мне putsвводить / сохранять команду, которую я хочу запустить в переменной, иsystem("thecommand") , похоже, не позволяют мне получить вывод, тогда как этот метод позволяет мне делать обе эти вещи, и он позволяет мне получать доступ к stdin, stdout и stderr независимо.

См. « Выполнение команд в ruby » и документацию по Ruby для Open3 .


3

Это не совсем ответ, но, возможно, кто-то найдет его полезным:

При использовании TK GUI в Windows, и вам нужно вызывать команды оболочки из rubyw, у вас всегда будет раздражающее окно CMD, появляющееся менее чем за секунду.

Чтобы избежать этого, вы можете использовать:

WIN32OLE.new('Shell.Application').ShellExecute('ipconfig > log.txt','','','open',0)

или

WIN32OLE.new('WScript.Shell').Run('ipconfig > log.txt',0,0)

Оба будут хранить ipconfigвывод внутриlog.txt , но окна не появятся.

Вам нужно будет require 'win32ole' внутри вашего сценария.

system(), exec()иspawn() будет все всплывающие окна , что раздражает при использовании ТЗ и rubyw.


-2

Вот классный пример, который я использую в скрипте ruby ​​на OS X (чтобы я мог запустить скрипт и получить обновление даже после переключения из окна):

cmd = %Q|osascript -e 'display notification "Server was reset" with title "Posted Update"'|
system ( cmd )
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.