Вызов команд оболочки из Ruby

941

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

  • 2
    Хотя этот вопрос полезен, он не очень хорошо задан. В Ruby есть много способов вызова вложенных оболочек, которые хорошо документированы и легко найдены, прочитав документацию по Kernel и Open3 и выполнив поиск здесь в SO.
  • 0
    К сожалению, эта тема довольно сложная. Open3 ( docs ) - лучший выбор для большинства ситуаций, IMO, но в более старых версиях Ruby он не будет учитывать измененный PATH ( bugs.ruby-lang.org/issues/8004 ), и в зависимости от того, как вы передаете аргументы (в частности, если вы используете хэш opts с не ключевыми словами), он может сломаться. Но если вы попадаете в такие ситуации, вы делаете что-то довольно продвинутое, и вы можете понять, что делать, прочитав реализацию Open3 .
Показать ещё 1 комментарий
Теги:
interop

20 ответов

1150
Лучший ответ

Это объяснение основано на комментируемом скрипте 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 стоит разделитель, который может быть любым символом. Если разделитель является одним из символов (, [, { или <, литерал состоит из символов до соответствующего закрывающего разделителя, с учетом вложенных пар разделителей. Для всех других разделителей литерал содержит символы до следующее вхождение символа-разделителя. Допускается интерполяция строки #{... }.

    Возвращает результат команды оболочки, как и обратные галочки.

    Документы: http://www.ruby-doc.org/docs/ProgrammingRuby/html/language.html

    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

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

  • 3
    Мне нужно записать результаты моего исполняемого файла на производственном сервере, но не нашел пути. Я использовал put #{cmd} и logger.info ( #{cmd} ). Есть ли способ зарегистрировать свои выходы на производстве?
  • 4
    И IO # popen () и Open3 # popen3 (). mentalized.net/journal/2010/03/08/...
Показать ещё 10 комментариев
198

Вот блок-схема, основанная на этом ответе. См. Также с помощью script для эмуляции терминала.

Изображение 4844

  • 14
    Ух ты хаха Очень полезно, хотя тот факт, что это должно существовать, вызывает сожаление
161

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

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

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

directorylist.each do |filename|
  filename.chomp!
  # work with file
end
  • 3
    %x[ cmd ] возвращает вам массив?
  • 1
    вышеупомянутое не работает для меня. `` <main> ': undefined method each' for :String (NoMethodError) как это работает для вас? Я использую ruby -v ruby 1.9.3p484 (2013-11-22 revision 43786) [i686-linux] Вы уверены, что массив возвращен из команды, чтобы цикл действительно работал?
Показать ещё 1 комментарий
61

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

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

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

  • 1
    Пост, описанный здесь, не обсуждает опцию синтаксиса %x .
  • 0
    +1 для Open4. Я уже начал пытаться реализовать свою собственную версию метода spawn когда нашел это.
Показать ещё 1 комментарий
33

Мой любимый Open3

  require "open3"

  Open3.popen3('nroff -man') { |stdin, stdout, stderr| ... }
  • 1
    Мне также нравится open3, особенно Open3.capture3: ruby-doc.org/stdlib-1.9.3/libdoc/open3/rdoc/… -> stdout, stderr, status = Open3.capture3('nroff -man', :stdin_data => stdin)
  • 0
    Есть ли в Ruby std-lib документация о том, как выполнять Spec и модульное тестирование с помощью Open3 или других Open? На моем нынешнем уровне понимания сложно протестировать оболочки.
24

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

  • Вы просто хотите stdout или вы нужен stderr? или даже отделены?
  • Насколько велика ваша продукция? Вы хотите сохранить весь результат в памяти?
  • Вы хотите прочитать некоторые из своих вывода, пока подпроцесс все еще работает?
  • Вам нужны коды результатов?
  • Вам нужен рубиновый объект, который представляет собой процесс и позволяет вам убить его по требованию?

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

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

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

19

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

Когда ты:

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

18

Я определенно не специалист по Ruby, но я сделаю это:

$ irb 
system "echo Hi"
Hi
=> true

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

cmd = 'ls'
system(cmd)
12

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

7

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

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

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

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

6

проще всего, например:

reboot = `init 6`
puts reboot
5

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

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

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

  • Аккуратно захватывает 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"
5

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

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

exec('ls ~')

Используя backticks or %x

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

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

system('ls ~')
=> true
3
  • backticks `метод является самым простым вызовом команд оболочки из ruby. Он возвращает результат команды оболочки.

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

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

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

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

Официальная рубиновая документация имеет достаточно хорошие примеры.

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)
1

Учитывая команду, например,

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

-1

На самом деле это не ответ, но, возможно, кто-то найдет это полезным, и это касается этого.

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

-2

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню