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

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

Ответов (20)

Решение

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

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

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

cmd = "echo 'hi'" # Sample string that can be used
  1. Kernel#` , обычно называемые обратными кавычками - `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

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

Мой любимый - Open3

  require "open3"

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

Мы можем добиться этого разными способами.

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

exec('ls ~')

С использованием backticks or %x

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

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

system('ls ~')
=> true

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

  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"

Вышеупомянутые ответы уже довольно хороши, но я действительно хочу поделиться следующей итоговой статьей: « 6 способов запуска команд оболочки в Ruby »

По сути, это говорит нам:

Kernel#exec :

exec 'echo "hello $HOSTNAME"'

system и $? :

system 'false' 
puts $?

Обратные кавычки (`):

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>]

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

cmd = %Q|osascript -e 'display notification "Server was reset" with title "Posted Update"'|
system ( cmd )

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

Если у вас более сложный случай, чем обычный, с которым невозможно справиться ``, тогда проверьте 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)

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

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

Если вам действительно нужен 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")

Ruby должен уважать shebang, но вы всегда можете использовать

system("bash ./my_script.sh")

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

Это не совсем ответ, но, возможно, кому-то это пригодится:

Когда вы используете 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() все это будет вызывать это раздражающее окно при использовании TK и rubyw.

Самый простой способ, например:

reboot = `init 6`
puts reboot

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

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

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

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

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

$ irb 
system "echo Hi"
Hi
=> true

Вы также должны уметь делать такие вещи, как:

cmd = 'ls'
system(cmd)

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

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

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

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

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

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

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

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

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

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

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

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

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

Когда ты:

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

Учитывая такую ​​команду, как 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 .

Вот блок-схема, основанная на том, « Когда использовать каждый метод запуска подпроцесса в Ruby ». См. Также « Обмануть приложение, заставив его думать, что его стандартный вывод - это терминал, а не канал ».

введите описание изображения здесь