Jak wywoływać polecenia powłoki z Ruby

Jak wywoływać polecenia powłoki z poziomu programu Ruby? Jak następnie uzyskać wyjście z tych poleceń z powrotem do Ruby?

Author: the Tin Man, 2008-08-05

20 answers

To wyjaśnienie jest oparte na komentarzu skryptu Ruby od mojego przyjaciela. Jeśli chcesz ulepszyć skrypt, możesz go zaktualizować pod linkiem.

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

Oto sposoby wykonania skryptu powłoki:

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

    To jest jak wiele inne języki, w tym Bash, PHP i Perl.

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

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

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

    Po znaku x znajduje się ogranicznik, który może być dowolnym znakiem. Jeśli ogranicznik jest jednym ze znaków (, [, {, lub <, literal składa się ze znaków aż do dopasowanego zamknięcia delimiter, uwzględniając zagnieżdżone pary ograniczników. Dla wszystkich innych ograniczników, literalny zawiera znaki aż do następnego wystąpienia znak ogranicznika. Dozwolone jest interpolowanie ciągu znaków #{ ... }.

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

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

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

    Wykonuje / align = "left" /

    Zwraca true Jeśli polecenie zostało znalezione i uruchomione pomyślnie, false w przeciwnym razie.

    Docs: 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 poprzez uruchomienie podanego zewnętrznego polecenia.

    Zwraca none, bieżący proces jest zastępowany i nigdy nie jest kontynuowany.

    Docs: 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 dodatkowa Rada: $?, który jest taki sam jak $CHILD_STATUS, uzyskuje dostęp do statusu ostatniej wykonanej komendy systemu, jeśli użyjesz backsticków, system() lub %x{}. Następnie możesz uzyskać dostęp do właściwości exitstatus i pid:

$?.exitstatus

Aby przeczytać więcej zobacz:

 1383
Author: Steve Willard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-05-07 13:11:15
 323
Author: Ian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-11-18 21:28:57

Sposób, w jaki lubię to robić, to używanie %x dosłownego, co sprawia, że jest to łatwe (i czytelne! aby użyć cudzysłowów w poleceniu, w ten sposób:

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

, który w tym przypadku wypełni listę plików wszystkimi plikami testowymi w bieżącym katalogu, które możesz przetworzyć zgodnie z oczekiwaniami:

directorylist.each do |filename|
  filename.chomp!
  # work with file
end
 163
Author: cynicalman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-10 05:42:55

Oto najlepszy moim zdaniem artykuł o uruchamianiu skryptów powłoki w Rubim: " 6 sposobów uruchamiania poleceń powłoki w Rubim ".

Jeśli potrzebujesz tylko uzyskać wyjście, użyj backticks.

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

 66
Author: Mihai A,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-11 01:11:06

Moim ulubionym jest Open3

  require "open3"

  Open3.popen3('nroff -man') { |stdin, stdout, stderr| ... }
 43
Author: anshul,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-13 02:34:07

Niektóre rzeczy do przemyślenia przy wyborze pomiędzy tymi mechanizmami to:

  1. czy po prostu chcesz stdout czy chcesz potrzebujesz również stderr? Lub nawet rozdzieleni?
  2. jak duża jest twoja wydajność? Czy chcesz aby zachować cały wynik w pamięci?
  3. czy chcesz przeczytać niektóre z Twoich wyjście, gdy podproces jest nadal biegać?
  4. czy potrzebujesz kodów wyników?
  5. czy potrzebujesz obiektu Ruby, który reprezentuje proces i pozwala kill it on żądać?

Możesz potrzebować wszystkiego od prostych backticks (`), system(), i IO.popen do pełnego Kernel.fork/Kernel.exec z IO.pipe i IO.select.

Możesz też wrzucić timeouty do miksu, jeśli wykonanie pod-procesu trwa zbyt długo.

Niestety, to bardzo zależy.

 30
Author: Nick Brosnahan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-10 01:47:38

Na pewno nie jestem ekspertem od Ruby, ale dam mu szansę:

$ irb 
system "echo Hi"
Hi
=> true

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

cmd = 'ls'
system(cmd)
 27
Author: Steve Willard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-11 01:12:02

Jeszcze jedna opcja:

Kiedy:

  • potrzebujesz stderr jak i stdout
  • nie można/nie chce używać Open3/Open4 (rzucają 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"

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

 26
Author: j-g-faustus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-11 01:10:31

Powyższe odpowiedzi są już całkiem świetne, ale naprawdę chcę podzielić się następującym artykułem podsumowującym: "6 sposobów uruchamiania poleceń powłoki w Rubim "

W zasadzie mówi nam:

Kernel#exec:

exec 'echo "hello $HOSTNAME"'

system i $?:

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>]
 23
Author: Utensil,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-10-28 12:21:25

Jeśli naprawdę potrzebujesz Basha, zgodnie z notatką w" najlepszej " odpowiedzi.

Po pierwsze, zauważ, że gdy Ruby wywołuje powłokę, zwykle wywołuje /bin/sh, Nie Bash. Niektóre składnie Bash nie są obsługiwane przez /bin/sh we wszystkich systemach.

Jeśli chcesz użyć Bash, Wstaw bash -c "your Bash-only command" wewnątrz żądanej metody wywołania:

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

Do testu:

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

Lub jeśli uruchamiasz istniejący plik skryptu, taki jak

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

Ruby powinien szanuj szebang, ale zawsze możesz użyć

system("bash ./my_script.sh")

Aby się upewnić, chociaż może być trochę nad głową z /bin/sh działa /bin/bash, prawdopodobnie nie zauważysz.

 16
Author: dragon788,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-10 01:50:51

Możesz również użyć operatorów backtick ( ` ), podobnych do Perla:

directoryListing = `ls /`
puts directoryListing # prints the contents of the root directory
Przydatne, jeśli potrzebujesz czegoś prostego.

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

 12
Author: Rufo Sanchez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-05 13:57:47

Korzystając z odpowiedzi tutaj i linkowanych w odpowiedzi Mihai, ułożyłem funkcję, która spełnia te wymagania:

  1. starannie przechwytuje STDOUT i STDERR, aby nie "wyciekały", gdy mój skrypt jest uruchamiany z konsoli.
  2. pozwala na przekazywanie argumentów do powłoki jako tablicy, więc nie ma potrzeby martwić się o ucieczkę.
  3. rejestruje status zakończenia polecenia, dzięki czemu jest jasne, kiedy wystąpił błąd.

Jako bonus, ten zwróci również STDOUT w przypadki, w których polecenie powłoki kończy się pomyślnie (0) i umieszcza cokolwiek na STDOUT. W ten sposób różni się ona od system, która w takich przypadkach po prostu zwraca true.

Kod następuje. Funkcja specyficzna to 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"
 11
Author: Ryan Tate,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-11 01:09:02

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

Używając Kernel#exec, Po wykonaniu tej komendy nic się nie dzieje:

exec('ls ~')

Using backticks or %x

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

Użycie polecenia Kernel#system, zwraca true jeśli się powiedzie, false jeśli się nie powiedzie i zwraca nil jeśli wykonanie polecenia nie powiedzie się:

system('ls ~')
=> true
 11
Author: nkm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-11 01:10:04

Najprostszym sposobem jest, na przykład:

reboot = `init 6`
puts reboot
 11
Author: Alex Lorsung,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-10 01:51:16

Nie zapomnij polecenia spawn, aby utworzyć proces w tle, aby wykonać określone polecenie. Możesz nawet poczekać na jego zakończenie używając klasy Process i zwracanej 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

Doc mówi: ta metoda jest podobna do #system, ale nie czeka na zakończenie polecenia.

 10
Author: MonsieurDart,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-04 15:04:26

Jeśli masz bardziej skomplikowaną sprawę niż zwykła sprawa, której nie można obsłużyć ``, Sprawdź Kernel.spawn(). Wydaje się to być najbardziej ogólnym/w pełni funkcjonalnym dostarczonym przez stock Ruby do wykonywania zewnętrznych poleceń.

Możesz go użyć do:

  • tworzenie grup procesów (Windows).
  • przekierowanie in, out, error do plików / each-other.
  • set ENV vars, umask.
  • zmień katalog przed wykonaniem polecenia.
  • Ustaw limity zasobów dla CPU / data / etc.
  • zrób wszystko, co można zrobić z innymi opcjami w innych odpowiedziach, ale z większą ilością kodu.

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)
 7
Author: Kashyap,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-10 01:54:20

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

     url_request = 'http://google.com'
     result_of_shell_command = `curl #{url_request}`
 7
Author: ysk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-10 01:55:02

Podano polecenie podobne do attrib:

require 'open3'

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

Odkryłem, że chociaż ta metoda nie jest tak pamiętna jak

system("thecommand")

Lub

`thecommand`

W backticks, dobrą rzeczą w tej metodzie w porównaniu do innych metod jest backticks wydaje się nie pozwalać Mi puts poleceniu, które uruchamiam / przechowuję polecenie, które chcę uruchomić w zmiennej, i system("thecommand") wydaje się nie pozwalać mi uzyskać wyjścia, podczas gdy ta metoda pozwala mi wykonywać obie te rzeczy i pozwala mi uzyskać dostęp do stdin, stdout i stderr niezależnie.

Zobacz " wykonywanie poleceń w Rubim " i dokumentacja Rubiego Open3.

 6
Author: barlop,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-10 01:59:27

To nie jest odpowiedź, ale może komuś się przyda:

Gdy używasz TK GUI w systemie Windows i musisz wywoływać polecenia powłoki z rubyw, zawsze będziesz miał irytujące okno CMD wyskakujące przez 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ć wyjście ipconfig wewnątrz log.txt, ale nie pojawią się żadne okna.

Będziesz musiał require 'win32ole' wewnątrz skryptu.

system(), exec() and spawn() will all pop up to irytujące okno podczas korzystania z TK i rubyw.

 4
Author: lucaortis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-10 02:28:19

Oto fajny skrypt, którego używam w skrypcie ruby na OS X (tak, że mogę uruchomić skrypt i uzyskać aktualizację nawet po przełączeniu się z dala od okna):

cmd = %Q|osascript -e 'display notification "Server was reset" with title "Posted Update"'|
system ( cmd )
 -1
Author: JayCrossler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-14 20:12:11