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

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

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

the Tin Man 20.04.2017 02:21

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

Joshua Cheek 22.05.2018 21:47

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

Kelvin 06.12.2018 20:31
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Шаг 1: Создание приложения Slack Чтобы создать Slackbot, вам необходимо создать приложение Slack. Войдите в свою учетную запись Slack и перейдите на...
1 143
3
556 368
20
Перейти к ответу Данный вопрос помечен как решенный

Ответы 20

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

$ irb 
system "echo Hi"
Hi
=> true

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

cmd = 'ls'
system(cmd)

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

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

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

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

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

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

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

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

%x[ cmd ] возвращает вам массив?

x-yuri 02.06.2014 17:24

выше не работает для меня. ``

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

Nasser 22.06.2015 23:24

% x [cmd] .split ("\ n") вернет список :)

Ian Ellis 07.12.2016 23:28
Ответ принят как подходящий

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

Во-первых, обратите внимание, что когда 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

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

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

Omer Aslam 01.10.2012 21:37

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

hughdbrown 07.02.2013 03:20

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

sschuberth 15.02.2013 20:19

Кроме того, это сообщение в блоге дополнительно охватывает spawn и open3.

sschuberth 15.02.2013 20:29

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

Andrei Botalov 18.02.2013 02:19

exec хорош, потому что вы можете видеть вывод на консоли.

6ft Dan 28.04.2014 17:34

Если вы собираетесь обстреливать через exec и system и передавать некоторые параметры, я предлагаю вам проверить мою библиотеку отказаться.

sshaw 12.02.2015 19:29

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

skagedal 12.03.2015 18:05

Вот (хороший?) Пример того, как spawn может использоваться вместе со встроенным сценарием оболочки в программе ruby: github.com/baitisj/spark-ping/blob/master/spark-ping Посмотрите на строки 76–94.

baitisj 19.05.2015 02:09

Kernel#system также имеет режим без использования оболочки (начиная с Ruby 1.9). Это находится в документации, на которую вы ссылаетесь. AFAIK только варианты Kernel#system и popen имеют эту функцию. Это очень желательно для безопасности (нет необходимости экранировать метасимволы оболочки) и эффективности (без дополнительной вилки).

oals 19.05.2016 13:39

@skagedal - Я пошел дальше и внес это изменение, потому что оно меня тоже сбило с толку (как и всех остальных, кто поддержал ваш комментарий).

Grant Birchmeier 05.09.2019 18:46

В некоторых случаях вы можете использовать отказаться. Он может проверять хэши Ruby и преобразовывать их в аргументы в стиле оболочки для передачи в system и т. д.

sshaw 03.12.2020 23:12

Несколько замечаний по поводу $?. Как и некоторые другие переменные $, для каждого потока существует одна копия. Так что можно полагаться на то, что он не будет перезаписан другим потоком.

Keith Bennett 04.12.2020 00:18

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

  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. Здесь описаны все методы.

В сообщении, описанном здесь, не обсуждается вариант синтаксиса %x.

Mei 22.11.2011 21:58

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

Brandan 20.11.2012 02:02

Мой любимый - Открыть3

  require "open3"

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

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

severin 06.05.2013 11:39

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

FilBot3 29.11.2016 18:30

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

Когда ты:

  • нужен 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 и Окна с первых дней MS-DOS.

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

При использовании 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>]

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

Kashyap 11.12.2015 17:24

Если у вас более сложный случай, чем обычный, который не может быть обработан с помощью ``, проверьте 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". См. Также «Обмануть приложение, заставив его думать, что его стандартный вывод - это терминал, а не канал».

Вау, ха-ха. Очень полезно, хотя тот факт, что он должен существовать, прискорбно

Josh Bodah 19.12.2016 19:01

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

Smar 30.04.2020 09:19

IMO, обратные кавычки и %x хороши только для сценариев, потому что они не возвращают вам stderr, и вы не можете передавать им аргументы (обратите внимание, что вы можете использовать $? для получения статуса выхода). Поэтому я почти всегда использую Open3.capture3 вместо обратных кавычек или %x.

Joshua Cheek 06.12.2020 02:49

Кроме того, fork не работает в Windows.

Joshua Cheek 06.12.2020 02:51

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

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

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

reboot = `init 6`
puts reboot

Если вам действительно нужен Bash, согласно примечанию в «лучшем» ответе.

First, note that when Ruby calls out to a shell, it typically calls /bin/sh, not Bash. Some Bash syntax is not supported by /bin/sh on all systems.

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

Для такой команды, как 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.

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

При использовании 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.

Другие вопросы по теме