Jak wywoływać polecenia powłoki z Ruby


1077

Jak wywoływać polecenia powłoki z wnętrza programu Ruby? Jak mogę uzyskać dane wyjściowe z tych poleceń z powrotem do Ruby?


3
Chociaż to pytanie jest przydatne, nie jest zadawane dobrze. Ruby ma wiele sposobów wywoływania podpowłok, które są dobrze udokumentowane i łatwe do znalezienia poprzez czytanie dokumentacji jądra i Open3 i wyszukiwanie tutaj na SO.
Tin Man

1
Niestety ten temat jest dość złożony. Open3( docs ) jest najlepszym wyborem dla większości sytuacji, IMO, ale w starszych wersjach Ruby nie będzie respektował zmodyfikowanej PATH( bugs.ruby-lang.org/issues/8004 ), i w zależności od tego, jak przekazujesz argumenty (w szczególności , jeśli użyjesz skrótu opcji bez słów kluczowych), może się zepsuć. Ale jeśli trafisz w te sytuacje, robisz coś dość zaawansowanego i możesz dowiedzieć się, co zrobić, czytając implementację Open3.
Joshua Cheek

3
Dziwi mnie, że nikt nie wspomniał Shellwords.escape( doc ). Nie chcesz wstawiać danych wprowadzanych przez użytkownika bezpośrednio do poleceń powłoki - najpierw ucieknij! Zobacz także polecenie wtrysku .
Kelvin,

Odpowiedzi:


1319

To wyjaśnienie opiera się na skomentowanym skrypcie Ruby od mojego znajomego. Jeśli chcesz ulepszyć skrypt, możesz go zaktualizować pod linkiem.

Po pierwsze, zauważ, że gdy Ruby woła powłokę, zwykle wywołuje /bin/sh, a nie Bash. Niektóre składnie Bash nie są obsługiwane przez /bin/shwszystkie systemy.

Oto sposoby wykonania skryptu powłoki:

cmd = "echo 'hi'" # Sample string that can be used
  1. Kernel#` , powszechnie nazywane backticks - `cmd`

    To jest jak wiele innych języków, w tym Bash, PHP i Perl.

    Zwraca wynik (tj. Standardowe wyjście) polecenia powłoki.

    Dokumenty: http://ruby-doc.org/core/Kernel.html#method-i-60

    value = `echo 'hi'`
    value = `#{cmd}`
    
  2. Wbudowana składnia, %x( cmd )

    Po xznaku znajduje się separator, którym może być dowolny znak. Jeśli separator jest jednym z bohaterów (, [, {, lub <, dosłowne składa się ze znaków aż do ogranicznika dopasowanie zamknięcia, z uwzględnieniem zagnieżdżonych par ogranicznik. Dla wszystkich innych ograniczników literał obejmuje znaki aż do następnego wystąpienia znaku ogranicznika. Interpolacja ciągów #{ ... }jest dozwolona.

    Zwraca wynik (tj. Standardowe wyjście) polecenia powłoki, podobnie jak backsticks.

    Dokumenty: https://docs.ruby-lang.org/en/master/syntax/literals_rdoc.html#label-Percent+Strings

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

    Wykonuje podane polecenie w podpowłoce.

    Zwraca, truejeśli polecenie zostało znalezione i uruchomione pomyślnie, w falseprzeciwnym razie.

    Dokumenty: http://ruby-doc.org/core/Kernel.html#method-i-system

    wasGood = system( "echo 'hi'" )
    wasGood = system( cmd )
  4. Kernel#exec

    Zastępuje bieżący proces, uruchamiając dane polecenie zewnętrzne.

    Nie zwraca żadnych, aktualny proces jest zastępowany i nigdy nie jest kontynuowany.

    Dokumenty: 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

Oto kilka dodatkowych porad: $?które są takie same jak $CHILD_STATUS, uzyskują dostęp do statusu ostatniego wykonanego polecenia systemowego, jeśli używasz backsticks, system()lub %x{}. Następnie możesz uzyskać dostęp do właściwości exitstatusi pid:

$?.exitstatus

Więcej informacji można znaleźć w:


4
Muszę zarejestrować wyniki mojego pliku wykonywalnego na serwerze produkcyjnym, ale nie znalazłem sposobu. Użyłem puts #{cmd}i logger.info ( #{cmd}). Czy jest jakiś sposób na rejestrowanie produkcji w produkcji?
Omer Aslam

5
I IO # popen () i Open3 # popen3 (). mentalized.net/journal/2010/03/08/…
hughdbrown

6
Dla kompletności (jak początkowo myślałem, będzie to również polecenie Ruby): Rake ma sh, który wykonuje polecenie „Uruchom polecenie systemowe cmd. Jeśli podano wiele argumentów, polecenie nie jest uruchamiane z powłoką (taka sama semantyka jak jądro :: exec i Kernel :: system) ".
sschuberth

40
Backticks domyślnie nie przechwytują STDERR. Dołącz `2> i 1` do dowodzenia, jeśli chcesz schwytać
Andrei Botalov

14
Myślę, że ta odpowiedź byłaby nieco ulepszona, gdyby powiedziała, że ​​backsticks i% x zwróciły „wynik”, a nie „wynik” danego polecenia. Ten ostatni można pomylić ze statusem wyjścia. Czy to tylko ja?
skagedal

275

24
Łał haha. Bardzo przydatne, chociaż fakt, że to musi istnieć, jest niefortunne
Josh Bodah,

Na marginesie, uważam, że metoda spawn () znajduje się w wielu różnych miejscach (np. KernelI Processjest najbardziej wszechstronna. Jest mniej więcej taka sama PTY.spawn(), ale bardziej ogólna.
Smar

160

Lubię to robić, używając %xliterału, co ułatwia (i czyta!) Używanie cudzysłowów w poleceniu, tak jak poniżej:

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

Które w tym przypadku zapełni listę plików wszystkimi plikami testowymi w bieżącym katalogu, które można przetwarzać zgodnie z oczekiwaniami:

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

4
Czy %x[ cmd ]zwraca tablicę?
x-yuri

2
powyższe nie działa dla mnie. `` <main> ': niezdefiniowana metoda, each' for :String (NoMethodError) jak to dla ciebie działało? Korzystam z ruby -v ruby 1.9.3p484 (2013-11-22 revision 43786) [i686-linux]Czy na pewno zwracana jest tablica z polecenia, aby pętla faktycznie działała?
Nasser

% x [cmd] .split ("\ n") zwróci jednak listę :)
Ian Ellis,

65

Oto najlepszy artykuł, moim zdaniem, na temat uruchamiania skryptów powłoki w Rubim: „ 6 sposobów uruchamiania poleceń powłoki w Rubim ”.

Jeśli potrzebujesz tylko wyjścia, użyj backsticksa.

Potrzebowałem bardziej zaawansowanych rzeczy, takich jak STDOUT i STDERR, więc użyłem klejnotu Open4. Wszystkie metody zostały tam wyjaśnione.


2
Opisany tutaj post nie omawia %xopcji składni.
Mei

+1 dla Open4. Kiedy już to znalazłem, zacząłem próbować wdrożyć własną wersję tej spawnmetody.
Brandan,

40

Moim ulubionym jest Open3

  require "open3"

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

2
Lubię też open3, szczególnie Open3.capture3: ruby-doc.org/stdlib-1.9.3/libdoc/open3/rdoc/… -> stdout, stderr, status = Open3.capture3('nroff -man', :stdin_data => stdin)
severin

Czy jest jakaś dokumentacja na temat przeprowadzania testów Spec i testów jednostkowych z Open3 lub innymi Open'ami w Ruby std-lib? Na obecnym poziomie zrozumienia trudno mi przetestować powłoki zewnętrzne.
FilBot3,

29

Wybierając między tymi mechanizmami, należy rozważyć następujące rzeczy:

  1. Czy chcesz po prostu stdout, czy też potrzebujesz stderr? A nawet wyodrębniony?
  2. Jak duży jest twój wynik? Czy chcesz zachować cały wynik w pamięci?
  3. Czy chcesz przeczytać niektóre dane wyjściowe, gdy podproces jest nadal uruchomiony?
  4. Potrzebujesz kodów wynikowych?
  5. Czy potrzebujesz obiektu Ruby, który reprezentuje proces i pozwala zabijać go na żądanie?

Może trzeba coś od prostych backticks ( ``) system(), a IO.popendo pełnowymiarową Kernel.fork/ Kernel.execz IO.pipei IO.select.

Możesz także wrzucić limity czasu do miksu, jeśli podproces trwa zbyt długo.

Niestety, to bardzo zależy .


25

Jeszcze jedna opcja:

Kiedy ty:

  • potrzebujesz stderr i stdout
  • nie mogę / nie użyję Open3 / Open4 (generują wyjątki w NetBeans na moim Macu, nie wiem dlaczego)

Możesz użyć przekierowania powłoki:

puts %x[cat bogus.txt].inspect
  => ""

puts %x[cat bogus.txt 2>&1].inspect
  => "cat: bogus.txt: No such file or directory\n"

2>&1Składnia działa na Linux , Mac i systemu Windows od wczesnych dni od MS-DOS.


25

Zdecydowanie nie jestem ekspertem od Ruby, ale spróbuję:

$ irb 
system "echo Hi"
Hi
=> true

Powinieneś być także w stanie robić takie rzeczy jak:

cmd = 'ls'
system(cmd)

21

Powyższe odpowiedzi są już dość świetne, ale naprawdę chcę udostępnić następujący artykuł podsumowujący: „ 6 sposobów uruchamiania poleceń powłoki w Rubim

Zasadniczo mówi nam:

Kernel#exec:

exec 'echo "hello $HOSTNAME"'

systemi $?:

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 -- klejnot:

require "open4" 
pid, stdin, stdout, stderr = Open4::popen4 "false" # => [26327, #<IO:0x6dff24>, #<IO:0x6dfee8>, #<IO:0x6dfe84>]

15

Jeśli naprawdę potrzebujesz Bash, zgodnie z notatką w „najlepszej” odpowiedzi.

Po pierwsze, zauważ, że gdy Ruby woła powłokę, zwykle wywołuje /bin/sh, a nie Bash. Niektóre składnie Bash nie są obsługiwane przez /bin/shwszystkie systemy.

Jeśli chcesz użyć Bash, wstaw bash -c "your Bash-only command"żądaną metodę wywoływania:

quick_output = system("ls -la")
quick_bash = system("bash -c 'ls -la'")

Testować:

system("echo $SHELL")
system('bash -c "echo $SHELL"')

Lub jeśli działasz istniejący plik skryptu, taki jak

script_output = system("./my_script.sh")

Ruby powinna uszanować shebang, ale zawsze możesz go użyć

system("bash ./my_script.sh")

dla pewności, chociaż /bin/shbieg może być niewielki /bin/bash, prawdopodobnie tego nie zauważysz.


11

Możesz także użyć operatorów wstecznych (`), podobnych do Perla:

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

Przydatne, jeśli potrzebujesz czegoś prostego.

Wybór metody zależy dokładnie od tego, co próbujesz osiągnąć; sprawdź dokumentację, aby uzyskać więcej informacji na temat różnych metod.


10

Możemy to osiągnąć na wiele sposobów.

Używanie Kernel#exec, nic po wykonaniu tego polecenia:

exec('ls ~')

Za pomocą backticks or %x

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

Za pomocą Kernel#systempolecenia zwraca, truejeśli się powiedzie, falsejeśli się nie powiedzie i zwraca, niljeśli wykonanie polecenia się nie powiedzie:

system('ls ~')
=> true


9

Korzystając z odpowiedzi tutaj i powiązanych w odpowiedzi Mihai, stworzyłem funkcję, która spełnia następujące wymagania:

  1. Starannie rejestruje STDOUT i STDERR, aby nie „wyciekły”, gdy mój skrypt jest uruchamiany z konsoli.
  2. Umożliwia przekazywanie argumentów do powłoki jako tablicy, więc nie musisz się martwić o ucieczkę.
  3. Przechwytuje status wyjścia polecenia, aby było jasne, kiedy wystąpił błąd.

Jako bonus, ten zwróci również STDOUT w przypadkach, gdy polecenie powłoki zakończy się pomyślnie (0) i umieści cokolwiek w STDOUT. W ten sposób różni się od system, co truew takich przypadkach po prostu zwraca .

Kod następuje. Szczególną funkcją jest 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

Nie zapomnij spawnpolecenia utworzenia procesu w tle w celu wykonania określonego polecenia. Możesz nawet poczekać na jej zakończenie, korzystając z Processklasy i zwróconych 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

Dokument mówi: Ta metoda jest podobna do tej, #systemale nie czeka na zakończenie polecenia.


2
Kernel.spawn()wydaje się być znacznie bardziej wszechstronny niż wszystkie inne opcje.
Kashyap

6

Jeśli masz bardziej złożony przypadek niż zwykły przypadek, z którym nie można sobie poradzić ``, sprawdź Kernel.spawn(). Wydaje się, że jest to najbardziej ogólny / w pełni funkcjonalny program Ruby do wykonywania poleceń zewnętrznych.

Możesz go użyć do:

  • utwórz grupy procesów (Windows).
  • przekierowanie do, wyjście, błąd do plików / siebie.
  • ustaw env vars, umask.
  • zmień katalog przed wykonaniem polecenia.
  • ustawić limity zasobów dla procesora / danych / itp.
  • Zrób wszystko, co można zrobić z innymi opcjami w innych odpowiedziach, ale z większym kodem.

Dokumentacja Ruby ma wystarczająco dobrych przykładów:

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

Metoda backticks (`) jest najłatwiejszą do wywoływania poleceń powłoki z Ruby. Zwraca wynik polecenia powłoki:

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

5

Biorąc pod uwagę polecenie attrib:

require 'open3'

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

Przekonałem się, że chociaż ta metoda nie jest tak niezapomniana jak

system("thecommand")

lub

`thecommand`

w backticks, dobrą rzeczą w tej metodzie w porównaniu z innymi metodami jest to, że backticks nie pozwalają mi putsna polecenie, które wykonuję / przechowuję polecenie, które chcę uruchomić w zmiennej, i system("thecommand")nie wydaje mi się, aby uzyskać wynik, podczas gdy ta metoda pozwala mi robić obie te rzeczy i umożliwia mi niezależny dostęp do stdin, stdout i stderr.

Zobacz „ Wykonywanie poleceń w Rubim ” i dokumentację Open3 Ruby .


3

To nie jest tak naprawdę odpowiedź, ale może ktoś uzna ją za przydatną:

Kiedy używasz TK GUI w systemie Windows i musisz wywoływać polecenia powłoki z rubyw, zawsze pojawi się denerwujące okno CMD na mniej niż sekundę.

Aby tego uniknąć, możesz użyć:

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

lub

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

Oba będą przechowywać dane ipconfigwyjściowe w środku log.txt, ale nie pojawią się żadne okna.

Musisz zajrzeć do require 'win32ole'skryptu.

system(), exec()i spawn()wszystkie pojawią się w tym irytującym oknie podczas korzystania z TK i rubyw.


-2

Oto fajny, którego używam w skrypcie ruby ​​w systemie OS X (dzięki czemu mogę uruchomić skrypt i uzyskać aktualizację nawet po przełączeniu się z okna):

cmd = %Q|osascript -e 'display notification "Server was reset" with title "Posted Update"'|
system ( cmd )
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.