So rufen Sie Shell-Befehle von Ruby aus auf

1077

Wie rufe ich Shell-Befehle innerhalb eines Ruby-Programms auf? Wie bekomme ich dann die Ausgabe dieser Befehle zurück in Ruby?

Codierung ohne Kommentare
quelle
3
Diese Frage ist zwar nützlich, wird aber nicht gut gestellt. Ruby hat viele Möglichkeiten, Sub-Shells aufzurufen, die gut dokumentiert und leicht zu finden sind, indem Sie die Kernel- und Open3- Dokumentation lesen und hier auf SO suchen.
der Blechmann
1
Leider ist dieses Thema ziemlich komplex. Open3( docs ) ist die beste Wahl für die meisten Situationen, IMO, aber in älteren Versionen von Ruby werden modifizierte PATH( bugs.ruby-lang.org/issues/8004 ) und je nachdem, wie Sie Argumente übergeben ( insbesondere ), nicht berücksichtigt Wenn Sie opts-Hash mit Nicht-Schlüsselwörtern verwenden, kann dies zu Problemen führen. Wenn Sie jedoch auf diese Situationen stoßen, tun Sie etwas ziemlich Fortgeschrittenes und können herausfinden, was zu tun ist, indem Sie die Implementierung von lesen Open3.
Joshua Cheek
3
Ich bin überrascht, dass niemand etwas erwähnt hat Shellwords.escape( doc ). Sie möchten keine Benutzereingaben direkt in Shell-Befehle einfügen - entkommen Sie zuerst! Siehe auch Befehlsinjektion .
Kelvin

Antworten:

1319

Diese Erklärung basiert auf einem kommentierten Ruby-Skript eines Freundes von mir. Wenn Sie das Skript verbessern möchten, können Sie es unter dem Link aktualisieren.

Beachten Sie zunächst, dass , wenn Ruby eine Shell ruft, ruft die Regel /bin/sh, nicht Bash. Einige Bash-Syntax wird nicht /bin/shauf allen Systemen unterstützt.

Hier sind Möglichkeiten, ein Shell-Skript auszuführen:

cmd = "echo 'hi'" # Sample string that can be used
  1. Kernel#` , allgemein als Backticks bezeichnet - `cmd`

    Dies ist wie bei vielen anderen Sprachen, einschließlich Bash, PHP und Perl.

    Gibt das Ergebnis (dh die Standardausgabe) des Shell-Befehls zurück.

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

    value = `echo 'hi'`
    value = `#{cmd}`
    
  2. Eingebaute Syntax, %x( cmd )

    Dem xZeichen folgt ein Trennzeichen, das ein beliebiges Zeichen sein kann. Wenn das Trennzeichen eines der Zeichen (ist [, {oder <, besteht das Literal aus den Zeichen bis zum übereinstimmenden schließenden Trennzeichen unter Berücksichtigung verschachtelter Trennzeichenpaare. Bei allen anderen Trennzeichen umfasst das Literal die Zeichen bis zum nächsten Auftreten des Trennzeichens. String-Interpolation #{ ... }ist erlaubt.

    Gibt das Ergebnis (dh die Standardausgabe) des Shell-Befehls zurück, genau wie die Backticks.

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

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

    Führt den angegebenen Befehl in einer Subshell aus.

    Gibt zurück, truewenn der Befehl gefunden und erfolgreich ausgeführt wurde, falseandernfalls.

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

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

    Ersetzt den aktuellen Prozess durch Ausführen des angegebenen externen Befehls.

    Gibt keine zurück, der aktuelle Prozess wird ersetzt und wird nie fortgesetzt.

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

Hier einige zusätzliche Beratung: $?, das ist die gleiche wie $CHILD_STATUS, greift auf den Status des letzten System ausgeführten Befehls , wenn Sie die Backticks verwenden, system()oder %x{}. Sie können dann auf die Eigenschaften exitstatusund zugreifen pid:

$?.exitstatus

Weitere Informationen finden Sie unter:

Steve Willard
quelle
4
Ich muss die Ausgaben meiner ausführbaren Datei auf dem Produktionsserver protokollieren, habe aber keine Möglichkeit gefunden. Ich habe Puts #{cmd}und logger.info ( #{cmd}) verwendet. Gibt es eine Möglichkeit, ihre Ausgaben in der Produktion zu protokollieren?
Omer Aslam
5
Und IO # popen () und Open3 # popen3 (). mentalized.net/journal/2010/03/08/...
hughdbrown
6
Der Vollständigkeit halber (wie ich zuerst dachte, wäre dies auch ein Ruby-Befehl): Rake hat sh, was "Systembefehl cmdausführen " ausführt . Wenn mehrere Argumente angegeben werden, wird der Befehl nicht mit der Shell ausgeführt (gleiche Semantik wie Kernel :: exec und Kernel :: system) ".
Sschuberth
40
Backticks erfassen STDERR standardmäßig nicht. Fügen Sie `2> & 1` zum Befehl hinzu, wenn Sie erfassen möchten
Andrei Botalov
14
Ich denke, diese Antwort würde sich leicht verbessern, wenn sie besagt, dass Backticks und% x die "Ausgabe" und nicht das "Ergebnis" des angegebenen Befehls zurückgeben. Letzteres könnte mit dem Exit-Status verwechselt werden. Oder bin das nur ich?
Skagedal
275

Hier ist ein Flussdiagramm, das auf " Wann soll jede Methode zum Starten eines Unterprozesses in Ruby verwendet werden " basiert . Siehe auch " Trick eine Anwendung in den Gedanken, dass ihr Standard ein Terminal ist, keine Pipe ".

Geben Sie hier die Bildbeschreibung ein

Ian
quelle
24
Wow haha. Sehr nützlich, obwohl die Tatsache, dass dies existieren muss, unglücklich ist
Josh Bodah
Als Randnotiz finde ich die spawn () -Methode an vielen verschiedenen Stellen (z. B. Kernelund Processam vielseitigsten). Sie ist mehr oder weniger dieselbe PTY.spawn(), aber allgemeiner.
Smar
160

Die Art und Weise, wie ich das mache, ist die Verwendung des %xLiteral, was es einfach (und lesbar!) Macht, Anführungszeichen in einem Befehl zu verwenden, wie folgt:

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

In diesem Fall wird die Dateiliste mit allen Testdateien im aktuellen Verzeichnis gefüllt, die Sie wie erwartet verarbeiten können:

directorylist.each do |filename|
  filename.chomp!
  # work with file
end
Zyniker
quelle
4
Gibt %x[ cmd ]Ihnen ein Array zurück?
X-Yuri
2
das obige funktioniert bei mir nicht. `` <main> ': undefinierte Methode each' for :String (NoMethodError) Wie hat es bei Ihnen funktioniert? Ich verwende ruby -v ruby 1.9.3p484 (2013-11-22 revision 43786) [i686-linux]Sind Sie sicher, dass ein Array vom Befehl zurückgegeben wird, damit die Schleife tatsächlich funktioniert?
Nasser
% x [cmd] .split ("\ n") gibt jedoch eine Liste zurück :)
Ian Ellis
65

Hier ist meiner Meinung nach der beste Artikel zum Ausführen von Shell-Skripten in Ruby: " 6 Möglichkeiten zum Ausführen von Shell-Befehlen in Ruby ".

Wenn Sie nur die Ausgabe benötigen, verwenden Sie Backticks.

Ich brauchte fortgeschrittenere Sachen wie STDOUT und STDERR, also habe ich das Open4-Juwel verwendet. Sie haben dort alle Methoden erklärt.

Mihai A.
quelle
2
In dem hier beschriebenen Beitrag wird die Syntaxoption nicht behandelt %x.
Mei
+1 für Open4. Ich hatte bereits versucht, meine eigene Version der spawnMethode zu implementieren , als ich diese fand.
Brandan
40

Mein Favorit ist Open3

  require "open3"

  Open3.popen3('nroff -man') { |stdin, stdout, stderr| ... }
anshul
quelle
2
Ich mag auch open3, besonders Open3.capture3: ruby-doc.org/stdlib-1.9.3/libdoc/open3/rdoc/… -> stdout, stderr, status = Open3.capture3('nroff -man', :stdin_data => stdin)
Severin
Gibt es eine Dokumentation zur Durchführung von Spezifikations- und Komponententests mit Open3 oder anderen Open in der Ruby std-lib? Nach meinem derzeitigen Verständnis ist es schwierig, Shell-Outs zu testen.
FilBot3
29

Einige Dinge, über die Sie nachdenken sollten, wenn Sie zwischen diesen Mechanismen wählen, sind:

  1. Willst du nur stdout oder brauchst du auch stderr? Oder sogar getrennt?
  2. Wie groß ist Ihre Leistung? Möchten Sie das gesamte Ergebnis im Speicher behalten?
  3. Möchten Sie einen Teil Ihrer Ausgabe lesen, während der Unterprozess noch ausgeführt wird?
  4. Benötigen Sie Ergebniscodes?
  5. Benötigen Sie ein Ruby-Objekt, das den Prozess darstellt und das Sie bei Bedarf beenden können?

Sie können alles von einfachen Backticks benötigen ( ``), system()und IO.popenzu ausgewachsenen Kernel.fork/ Kernel.execmit IO.pipeund IO.select.

Möglicherweise möchten Sie auch Timeouts in den Mix einfügen, wenn die Ausführung eines Unterprozesses zu lange dauert.

Leider kommt es sehr darauf an .

Nick Brosnahan
quelle
25

Noch eine Option:

Wenn du:

  • brauche sowohl stderr als auch stdout
  • kann / wird Open3 / Open4 nicht verwenden (sie werfen Ausnahmen in NetBeans auf meinem Mac aus, keine Ahnung warum)

Sie können die Shell-Umleitung verwenden:

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

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

Die 2>&1Syntax funktioniert seit den Anfängen von MS-DOS unter Linux , Mac und Windows .

jg-faustus
quelle
25

Ich bin definitiv kein Ruby-Experte, aber ich werde es versuchen:

$ irb 
system "echo Hi"
Hi
=> true

Sie sollten auch in der Lage sein, Dinge zu tun wie:

cmd = 'ls'
system(cmd)
Steve Willard
quelle
21

Die obigen Antworten sind bereits ziemlich gut, aber ich möchte wirklich den folgenden zusammenfassenden Artikel teilen: " 6 Möglichkeiten, Shell-Befehle in Ruby auszuführen "

Grundsätzlich sagt es uns:

Kernel#exec::

exec 'echo "hello $HOSTNAME"'

systemund $?:

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 -- ein Edelstein:

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

Wenn Sie Bash wirklich brauchen, lesen Sie den Hinweis in der "besten" Antwort.

Beachten Sie zunächst, dass , wenn Ruby eine Shell ruft, ruft die Regel /bin/sh, nicht Bash. Einige Bash-Syntax wird nicht /bin/shauf allen Systemen unterstützt.

Wenn Sie Bash verwenden müssen, fügen Sie es bash -c "your Bash-only command"in die gewünschte Aufrufmethode ein:

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

Zu testen:

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

Oder wenn Sie eine vorhandene Skriptdatei wie ausführen

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

Ruby sollte den Schebang ehren, aber du könntest ihn immer gebrauchen

system("bash ./my_script.sh")

Um sicherzugehen, dass das /bin/shLaufen zwar einen leichten Overhead verursacht /bin/bash, werden Sie es wahrscheinlich nicht bemerken.

dragon788
quelle
11

Sie können auch die Backtick-Operatoren (`) verwenden, ähnlich wie bei Perl:

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

Praktisch, wenn Sie etwas Einfaches brauchen.

Welche Methode Sie verwenden möchten, hängt genau davon ab, was Sie erreichen möchten. Weitere Informationen zu den verschiedenen Methoden finden Sie in den Dokumenten.

Rufo Sanchez
quelle
10

Wir können es auf verschiedene Arten erreichen.

Verwenden Sie Kernel#execnichts, nachdem dieser Befehl ausgeführt wurde:

exec('ls ~')

Verwenden von backticks or %x

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

Wenn der Kernel#systemBefehl verwendet wird, wird zurückgegeben, truewenn er erfolgreich ist, falsewenn er nicht erfolgreich ist, und wird zurückgegeben, nilwenn die Befehlsausführung fehlschlägt:

system('ls ~')
=> true
nkm
quelle
10

Der einfachste Weg ist zum Beispiel:

reboot = `init 6`
puts reboot
Alex Lorsung
quelle
9

Mit den Antworten hier und in Mihais Antwort verknüpft, habe ich eine Funktion zusammengestellt, die diese Anforderungen erfüllt:

  1. Erfasst STDOUT und STDERR ordentlich, damit sie nicht "auslaufen", wenn mein Skript von der Konsole ausgeführt wird.
  2. Ermöglicht die Übergabe von Argumenten an die Shell als Array, sodass Sie sich keine Gedanken über das Escape machen müssen.
  3. Erfasst den Beendigungsstatus des Befehls, sodass klar ist, wann ein Fehler aufgetreten ist.

Als Bonus gibt dieser auch STDOUT zurück, wenn der Shell-Befehl erfolgreich beendet wird (0) und STDOUT aktiviert. Auf diese Weise unterscheidet es sich von system, was truein solchen Fällen einfach zurückkehrt .

Code folgt. Die spezifische Funktion ist 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"
Ryan Tate
quelle
9

Vergessen Sie nicht den spawnBefehl zum Erstellen eines Hintergrundprozesses zum Ausführen des angegebenen Befehls. Sie können sogar mit der ProcessKlasse und der zurückgegebenen Klasse auf den Abschluss warten 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

Das Dokument sagt: Diese Methode ähnelt, #systemwartet jedoch nicht auf den Abschluss des Befehls.

MonsieurDart
quelle
2
Kernel.spawn()scheint weitaus vielseitiger zu sein als alle anderen Optionen.
Kashyap
6

Wenn Sie einen komplexeren Fall als den allgemeinen haben, der nicht behandelt werden kann ``, überprüfen Sie ihn Kernel.spawn(). Dies scheint die allgemeinste / umfassendste Version zu sein, die von Stock Ruby zur Ausführung externer Befehle bereitgestellt wird.

Sie können es verwenden, um:

  • Prozessgruppen erstellen (Windows).
  • Fehler in Dateien / untereinander umleiten.
  • setze env vars, umask.
  • Ändern Sie das Verzeichnis, bevor Sie einen Befehl ausführen.
  • Ressourcenlimits für CPU / Daten / etc. festlegen.
  • Machen Sie alles, was mit anderen Optionen in anderen Antworten möglich ist, aber mit mehr Code.

Die Ruby-Dokumentation enthält genügend Beispiele:

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)
Kashyap
quelle
6

Die Methode backticks (`) ist die einfachste Methode, um Shell-Befehle von Ruby aus aufzurufen. Es gibt das Ergebnis des Shell-Befehls zurück:

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

Gegeben ein Befehl wie attrib:

require 'open3'

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

Ich habe festgestellt, dass diese Methode zwar nicht so einprägsam ist wie

system("thecommand")

oder

`thecommand`

In Backticks ist eine gute Sache bei dieser Methode im Vergleich zu anderen Methoden, dass Backticks mir putsden Befehl, den ich ausführe / speichere, nicht in einer Variablen zulassen / speichern und system("thecommand")mir nicht die Ausgabe zu ermöglichen scheinen Mit dieser Methode kann ich beides tun und unabhängig voneinander auf stdin, stdout und stderr zugreifen.

Siehe " Ausführen von Befehlen in Ruby " und Rubys Open3-Dokumentation .

Barlop
quelle
3

Dies ist keine wirkliche Antwort, aber vielleicht findet es jemand nützlich:

Wenn Sie die TK-Benutzeroberfläche unter Windows verwenden und Shell-Befehle von rubyw aus aufrufen müssen, wird immer weniger als eine Sekunde lang ein nerviges CMD-Fenster angezeigt.

Um dies zu vermeiden, können Sie Folgendes verwenden:

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

oder

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

In beiden Fällen wird die ipconfigAusgabe gespeichert log.txt, es werden jedoch keine Fenster geöffnet.

Sie müssen sich require 'win32ole'in Ihrem Skript befinden.

system(), exec()Und spawn()öffnet sich alles das ärgerliche Fenster , wenn TK und rubyw verwenden.

lucaortis
quelle
-2

Hier ist eine coole Version, die ich in einem Ruby-Skript unter OS X verwende (damit ich ein Skript starten und ein Update erhalten kann, auch nachdem ich vom Fenster weggeschaltet habe):

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