Як викликати команди оболонки від Ruby


1077

Як викликати команди оболонки зсередини програми Ruby? Як я можу отримати вихід з цих команд назад у Ruby?


3
Хоча це питання корисне, воно не задається добре. У Ruby є багато способів зателефонувати в суб-оболонки, які добре задокументовані та легко знайдені, читаючи документацію на Kernel та Open3 та шукаючи тут на SO.
Олов'яний чоловік

1
На жаль ця тема досить складна. Open3( docs ) - найкращий вибір для більшості ситуацій, IMO, але у старих версіях Ruby він не буде поважати модифіковані PATH( bugs.ruby-lang.org/isissue/8004 ) та залежно від способу передачі аргументів (зокрема , якщо ви використовуєте опт-хеш з не-ключовими словами), він може зламатися. Але якщо ви потрапили в ці ситуації, то ви робите щось досить вдосконалене, і ви можете зрозуміти, що робити, прочитавши реалізацію Open3.
Джошуа Щока

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

Відповіді:


1319

Це пояснення базується на коментованому сценарії 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

Детальніше читайте:


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
Я думаю, що ця відповідь буде дещо покращена, якби вона сказала, що backticks і% x повертають "вихід" даної команди, а не "результат". Останній може бути помилково прийнятий за вихідний статус. Або це тільки я?
skagedal

275

24
Вау ха-ха. Дуже корисно, хоча факт, що це має існувати, прикро
Джош Бода,

Як бічну зауваження, я знаходжу метод spawn (), який можна знайти в багатьох місцях (наприклад, Kernelі Processбути найбільш універсальним. Він більш-менш збігається з PTY.spawn(), але більш загальним.
Smar

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

% x [cmd] .split ("\ n") поверне список, хоча :)
Ian Ellis

65

Ось найкраща стаття на мій погляд про запуск скриптів оболонки в Ruby: " 6 способів запуску команд оболонки в Ruby ".

Якщо вам потрібно лише отримати вихід, використовуйте зворотній зв'язок.

Мені потрібні були більш досконалі речі, такі як STDOUT та STDERR, тому я використовував дорогоцінний камінь Open4. У вас є всі пояснені методи.


2
Опис, описаний тут, не обговорює %xваріант синтаксису.
Май

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

40

Мій улюблений - Open3

  require "open3"

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

2
Мені також подобається open3, особливо Open3.capture3: ruby-doc.org/stdlib-1.9.3/libdoc/open3/rdoc/… -> stdout, stderr, status = Open3.capture3('nroff -man', :stdin_data => stdin)
severin

Чи є документація про те, як провести тестування Spec і Unit з Open3 або іншими Open у std-lib Ruby? На моєму нинішньому рівні розуміння це важко перевірити вихідні снаряди.
FilBot3

29

Деякі речі, над якими варто задуматися, обираючи такі механізми, є:

  1. Ви просто хочете stdout або вам також потрібен stderr? Або навіть розлучилися?
  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

Я точно не експерт Рубі, але я спробую:

$ irb 
system "echo Hi"
Hi
=> true

Ви також повинні вміти робити такі речі:

cmd = 'ls'
system(cmd)

21

Відповіді вище вже досить чудові, але мені дуже хочеться поділитися такою короткою статтею: " 6 способів запуску команд оболонки в Рубі "

В основному, це говорить нам:

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

15

Якщо вам дійсно потрібен Баш, зауважте у відповіді "найкращий".

По-перше, зауважте, що коли 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`

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

Див. " Виконання команд в рубіні " та документацію Ruby's 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()всі вони спливуть це дратівливе вікно при використанні TK та rubyw.


-2

Ось класний, який я використовую в рубіновому скрипті на 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.