Abrufen von system () -Aufrufen in Ruby

309

Wie erhalte ich die Ausgabe, wenn ich einen Befehl mit dem Kernel # -System in Ruby aufrufe?

system("ls")
Macha
quelle
1
Vielleicht möchten Sie einen Blick auf diesen Thread in comp.lang.ruby
Manrico Corazzi
Dies ist ein sehr Handfaden, danke. Die Klasse zum Ausführen von Befehlen und zum Abrufen von Feedback ist im Beispielcode großartig.
ylluminate
3
Für zukünftige Googler. Wenn Sie mehr über andere Systembefehlsaufrufe und deren Unterschiede erfahren möchten, lesen Sie diese SO-Antwort .
Usbekjon

Antworten:

347

Ich möchte die Antwort des Chaos ein wenig erweitern und klären .

Wenn Sie Ihren Befehl mit Backticks umgeben, müssen Sie system () überhaupt nicht (explizit) aufrufen. Die Backticks führen den Befehl aus und geben die Ausgabe als Zeichenfolge zurück. Sie können den Wert dann einer Variablen wie folgt zuweisen:

output = `ls`
p output

oder

printf output # escapes newline chars
Craig Walker
quelle
4
Was ist, wenn ich als Teil meines Befehls eine Variable angeben muss? Das heißt, was würde so etwas wie ein System ("ls" + Dateiname) bedeuten, wenn Backticks verwendet werden sollen?
Vijay Dev
47
Sie können die Ausdrucksauswertung wie bei normalen Zeichenfolgen durchführen : ls #{filename}.
Craig Walker
36
Diese Antwort ist nicht ratsam: Sie führt das neue Problem der nicht bereinigten Benutzereingaben ein.
Dogweather
4
@ Dogweather: Das mag wahr sein, aber unterscheidet es sich von den anderen Methoden?
Craig Walker
20
Wenn Sie stderr erfassen möchten, setzen Sie einfach 2> & 1 am Ende Ihres Befehls. zB Ausgabe =command 2>&1
mikred
243

Seien Sie sich bewusst , dass alle Lösungen , bei denen Sie einen String mit Benutzer angegebenen Werte weitergeben system, %x[]usw. unsicher sind! Unsicher bedeutet eigentlich: Der Benutzer kann Code auslösen, der im Kontext und mit allen Berechtigungen des Programms ausgeführt wird.

Soweit ich nur sagen kann systemund Open3.popen3eine sichere / flüchtende Variante in Ruby 1.8 bereitstelle. In Ruby 1.9 IO::popenakzeptiert auch ein Array.

Übergeben Sie einfach jede Option und jedes Argument als Array an einen dieser Aufrufe.

Wenn Sie nicht nur den Exit-Status, sondern auch das Ergebnis benötigen, das Sie wahrscheinlich verwenden möchten Open3.popen3:

require 'open3'
stdin, stdout, stderr, wait_thr = Open3.popen3('usermod', '-p', @options['shadow'], @options['username'])
stdout.gets(nil)
stdout.close
stderr.gets(nil)
stderr.close
exit_code = wait_thr.value

Beachten Sie, dass das Blockformular stdin, stdout und stderr automatisch schließt - andernfalls müssten sie explizit geschlossen werden .

Weitere Informationen finden Sie hier: Erstellen von Sanitär-Shell-Befehlen oder Systemaufrufen in Ruby

Simon Hürlimann
quelle
26
Dies ist die einzige Antwort, die die Frage tatsächlich beantwortet und das Problem löst, ohne neue einzuführen (nicht bereinigte Eingabe).
Dogweather
2
Vielen Dank! Dies ist die Art von Antwort, auf die ich gehofft hatte. Eine Korrektur: Die getsAufrufe sollten das Argument übergeben nil, da wir sonst nur die erste Zeile der Ausgabe erhalten. Also zB stdout.gets(nil).
Greg Price
3
stdin, stdout und stderr sollten explizit in nicht blockierter Form geschlossen werden .
Yarin
Weiß jemand, ob sich in Ruby 2.0 oder 2.1 etwas geändert hat? Änderungen oder Kommentare sind willkommen ;-)
Simon Hürlimann
1
Ich denke, dass in der Diskussion Open3.popen3ein großes Problem fehlt: Wenn Sie einen Unterprozess haben, der mehr Daten in stdout schreibt, als eine Pipe aufnehmen kann, wird der Unterprozess angehalten stderr.writeund Ihr Programm bleibt hängen stdout.gets(nil).
Hagello
165

Nur für die Aufzeichnung, wenn Sie beide (Ausgabe und Operationsergebnis) möchten, können Sie Folgendes tun:

output=`ls no_existing_file` ;  result=$?.success?
FernandoFabreti
quelle
4
Genau das habe ich gesucht. Vielen Dank.
JDL
12
Das erfasst nur stdout und stderr geht zur Konsole. Um stderr zu bekommen, verwenden Sie: output=`ls no_existing_file 2>&1`; result=$?.success?
Peterept
8
Diese Antwort ist unsicher und sollte nicht verwendet werden. Wenn der Befehl alles andere als eine Konstante ist, verursacht die Backtick-Syntax wahrscheinlich einen Fehler, möglicherweise eine Sicherheitslücke. (Und selbst wenn es sich um eine Konstante handelt, wird sie wahrscheinlich dazu führen, dass jemand sie später für eine Nichtkonstante verwendet und einen Fehler verursacht.) Eine korrekte Lösung finden Sie in der Antwort von Simon Hürlimann .
Greg Price
23
Ein großes Lob an Greg Price für das Verständnis der Notwendigkeit, Benutzereingaben zu entgehen, aber es ist nicht richtig zu sagen, dass diese Antwort in der geschriebenen Form unsicher ist. Die erwähnte Open3-Methode ist komplizierter und führt zu mehr Abhängigkeiten, und das Argument, dass jemand "sie später für eine Nichtkonstante verwenden wird", ist ein Strohmann. Zwar würden Sie sie wahrscheinlich nicht in einer Rails-App verwenden, aber für ein einfaches Systemdienstprogramm-Skript ohne die Möglichkeit nicht vertrauenswürdiger Benutzereingaben sind Backticks vollkommen in Ordnung, und niemand sollte sich schlecht fühlen, wenn er sie verwendet.
Sbeam
69

Die einfache Möglichkeit , dies richtig zu tun und sicher zu bedienen Open3.capture2(), Open3.capture2e()oder Open3.capture3().

Die Verwendung von Rubys Backticks und seines %xAlias ​​ist unter keinen Umständen sicher, wenn sie mit nicht vertrauenswürdigen Daten verwendet werden. Es ist schlicht und einfach GEFÄHRLICH :

untrusted = "; date; echo"
out = `echo #{untrusted}`                              # BAD

untrusted = '"; date; echo"'
out = `echo "#{untrusted}"`                            # BAD

untrusted = "'; date; echo'"
out = `echo '#{untrusted}'`                            # BAD

Im systemGegensatz dazu entgeht die Funktion Argumenten bei korrekter Verwendung ordnungsgemäß :

ret = system "echo #{untrusted}"                       # BAD
ret = system 'echo', untrusted                         # good

Das Problem ist, dass der Exit-Code anstelle der Ausgabe zurückgegeben wird und die Erfassung des letzteren kompliziert und chaotisch ist.

Die beste Antwort in diesem Thread erwähnt bisher Open3, aber nicht die Funktionen, die für die Aufgabe am besten geeignet sind. Open3.capture2, capture2eUnd capture3Arbeit wie system, kehrt aber zwei oder drei Argumente:

out, err, st = Open3.capture3("echo #{untrusted}")     # BAD
out, err, st = Open3.capture3('echo', untrusted)       # good
out_err, st  = Open3.capture2e('echo', untrusted)      # good
out, st      = Open3.capture2('echo', untrusted)       # good
p st.exitstatus

Ein anderer erwähnt IO.popen(). Die Syntax kann in dem Sinne ungeschickt sein, dass ein Array als Eingabe gewünscht wird, aber es funktioniert auch:

out = IO.popen(['echo', untrusted]).read               # good

Der Einfachheit halber können Sie Open3.capture3()eine Funktion einschließen , z.

#
# Returns stdout on success, false on failure, nil on error
#
def syscall(*cmd)
  begin
    stdout, stderr, status = Open3.capture3(*cmd)
    status.success? && stdout.slice!(0..-(1 + $/.size)) # strip trailing eol
  rescue
  end
end

Beispiel:

p system('foo')
p syscall('foo')
p system('which', 'foo')
p syscall('which', 'foo')
p system('which', 'which')
p syscall('which', 'which')

Ergibt folgendes:

nil
nil
false
false
/usr/bin/which         <— stdout from system('which', 'which')
true                   <- p system('which', 'which')
"/usr/bin/which"       <- p syscall('which', 'which')
Denis de Bernardy
quelle
2
Dies ist die richtige Antwort. Es ist auch das informativste. Das einzige, was fehlt, ist eine Warnung vor dem Schließen der Standards. Siehe diesen anderen Kommentar : require 'open3'; output = Open3.popen3("ls") { |stdin, stdout, stderr, wait_thr| stdout.read } Beachten Sie, dass das Blockformular stdin, stdout und stderr automatisch schließt - andernfalls müssten sie explizit geschlossen werden .
Peter H. Boling
@ PeterH.Boling: Am besten ist mir bewusst, dass capture2, capture2eund capture3auch sie std * s automatisch schließen. (Zumindest bin ich nie auf das Problem gestoßen.)
Denis de Bernardy
Ohne die Verwendung des Blockformulars kann eine Codebasis nicht wissen, wann etwas geschlossen werden sollte, daher bezweifle ich sehr, dass sie geschlossen werden. Sie sind wahrscheinlich nie auf ein Problem gestoßen, weil das Nicht-Schließen nicht zu Problemen in einem kurzlebigen Prozess führt. Wenn Sie einen lang laufenden Prozess häufig genug neu starten, wird otto dort auch nicht angezeigt, es sei denn, Sie öffnen std * s in eine Schleife. Linux hat ein hohes Dateideskriptor-Limit, das Sie treffen können, aber bis Sie es treffen, werden Sie den "Fehler" nicht sehen.
Peter H. Boling
2
@ PeterH.Boling: Nein, nein, siehe Quellcode. Die Funktionen sind nur Wrapper um Open3#popen2, popen2eund popen3mit einem vorgegebenen Block: ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/...
Denis de Bernardy
1
@Dennis de Barnardy Vielleicht haben Sie verpasst, dass ich auf dieselbe Klassendokumentation verlinkt habe (allerdings für Ruby 2.0.0 und eine andere Methode. Ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/… Aus dem Beispiel : `` `stdin, stdout, stderr, wait_thr = Open3.popen3 ([env,] cmd ... [, opts]) pid = wait_thr [: pid] # pid des gestarteten Prozesses ... stdin.close # stdin , stdout und stderr sollten in dieser Form explizit geschlossen werden. stdout.close stderr.close `` `Ich habe nur die Dokumentation zitiert." # stdin, stdout und stderr sollten in dieser Form explizit geschlossen werden. "
Peter H. Boling
61

Sie können system () oder% x [] verwenden, je nachdem, welche Art von Ergebnis Sie benötigen.

system () gibt true zurück, wenn der Befehl gefunden und erfolgreich ausgeführt wurde, andernfalls false.

>> s = system 'uptime'
10:56  up 3 days, 23:10, 2 users, load averages: 0.17 0.17 0.14
=> true
>> s.class
=> TrueClass
>> $?.class
=> Process::Status

% x [..] speichert andererseits die Ergebnisse des Befehls als Zeichenfolge:

>> result = %x[uptime]
=> "13:16  up 4 days,  1:30, 2 users, load averages: 0.39 0.29 0.23\n"
>> p result 
"13:16  up 4 days,  1:30, 2 users, load averages: 0.39 0.29 0.23\n"
>> result.class
=> String

Der Blog-Beitrag von Jay Fields erklärt ausführlich die Unterschiede zwischen der Verwendung von system, exec und% x [..].

Martin Gross
quelle
2
Vielen Dank für den Tipp,% x [] zu verwenden. Es hat gerade ein Problem gelöst, bei dem ich in einem Ruby-Skript unter Mac OS X Back-Ticks verwendet habe. Wenn dasselbe Skript auf einem Windows-Computer mit Cygwin ausgeführt wurde, schlug es aufgrund der Back-Ticks fehl, funktionierte jedoch mit% x [].
Henrik Warne
22

Wenn Sie den Argumenten entkommen müssen, akzeptiert IO.popen in Ruby 1.9 auch ein Array:

p IO.popen(["echo", "it's escaped"]).read

In früheren Versionen können Sie Open3.popen3 verwenden :

require "open3"

Open3.popen3("echo", "it's escaped") { |i, o| p o.read }

Wenn Sie auch stdin bestehen müssen, sollte dies sowohl in 1.9 als auch in 1.8 funktionieren:

out = IO.popen("xxd -p", "r+") { |io|
    io.print "xyz"
    io.close_write
    io.read.chomp
}
p out # "78797a"
Lri
quelle
Vielen Dank! Dies ist perfekt.
Greg Price
21

Sie verwenden Backticks:

`ls`
Chaos
quelle
5
Backticks erzeugen keine Ausgabe am Terminal.
Mei
3
Es produziert kein stderr, aber es gibt stdout.
Nickolay Kondratenko
1
Es schreibt nicht an stdout oder stderr. Versuchen wir dieses Beispiel ruby -e '%x{ls}'- beachten Sie, keine Ausgabe. (fyi %x{}ist gleichbedeutend mit backticks.)
ocodo
Das hat super geklappt. Die Verwendung von shwürde die Ausgabe an die Konsole (dh STDOUT) zurückgeben und zurückgeben. Das geht nicht.
Joshua Pinter
19

Ein anderer Weg ist:

f = open("|ls")
foo = f.read()

Beachten Sie, dass dies das "Pipe" -Zeichen vor "ls" in open ist. Dies kann auch verwendet werden, um Daten in die Standardeingabe des Programms einzuspeisen und dessen Standardausgabe zu lesen.

dwc
quelle
Ich habe dies nur verwendet, um die Standardausgabe eines aws cli-Befehls zu lesen, um den json und nicht den offiziellen Rückgabewert von 'true' zu lesen
kraftydevil
14

Ich fand, dass Folgendes nützlich ist, wenn Sie den Rückgabewert benötigen:

result = %x[ls]
puts result

Ich wollte speziell die Pids aller Java-Prozesse auf meinem Computer auflisten und habe Folgendes verwendet:

ids = %x[ps ax | grep java | awk '{ print $1 }' | xargs]
Geoff van der Meer
quelle
Es ist eine großartige Lösung.
Ronan Louarn
13

Wie Simon Hürlimann bereits erklärte , ist Open3 sicherer als Backticks usw.

require 'open3'
output = Open3.popen3("ls") { |stdin, stdout, stderr, wait_thr| stdout.read }

Beachten Sie, dass das Blockformular stdin, stdout und stderr automatisch schließt - andernfalls müssten sie explizit geschlossen werden .

Yarin
quelle
9

Während die Verwendung von Backticks oder Popen oft das ist, was Sie wirklich wollen, beantwortet es die gestellte Frage nicht wirklich. Es kann gültige Gründe für die Erfassung der systemAusgabe geben (möglicherweise für automatisierte Tests). Ein kleines Googeln ergab eine Antwort, von der ich dachte, ich würde sie hier zum Nutzen anderer veröffentlichen.

Da ich dies zum Testen benötigte, verwendet mein Beispiel ein Block-Setup, um die Standardausgabe zu erfassen, da der eigentliche systemAufruf im zu testenden Code vergraben ist:

require 'tempfile'

def capture_stdout
  stdout = $stdout.dup
  Tempfile.open 'stdout-redirect' do |temp|
    $stdout.reopen temp.path, 'w+'
    yield if block_given?
    $stdout.reopen stdout
    temp.read
  end
end

Diese Methode erfasst alle Ausgaben im angegebenen Block mithilfe einer Tempfile, um die tatsächlichen Daten zu speichern. Anwendungsbeispiel:

captured_content = capture_stdout do
  system 'echo foo'
end
puts captured_content

Sie können den systemAnruf durch alles ersetzen , was intern anruft system. Sie können auch eine ähnliche Methode verwenden, um zu erfassen, stderrwenn Sie möchten.

Eric Anderson
quelle
8

Wenn Sie möchten, dass die Ausgabe mithilfe von in eine Datei umgeleitet wird Kernel#system, können Sie Deskriptoren wie folgt ändern:

Leiten Sie stdout und stderr im Append-Modus in eine Datei (/ tmp / log) um:

system('ls -al', :out => ['/tmp/log', 'a'], :err => ['/tmp/log', 'a'])

Bei einem Befehl mit langer Laufzeit wird die Ausgabe in Echtzeit gespeichert. Sie können die Ausgabe auch mit einer IO.pipe speichern und vom Kernel # -System umleiten.

Ashrith
quelle
0

Ich habe dieses hier nicht gefunden, also hatte ich einige Probleme, die volle Ausgabe zu erhalten.

Sie können STDERR zu STDOUT umleiten, wenn Sie STDERR mit einem Backtick erfassen möchten.

output = `grep hosts / private / etc / * 2> & 1`

Quelle: http://blog.bigbinary.com/2012/10/18/backtick-system-exec-in-ruby.html

dac2009
quelle
-1
puts `date`
puts $?


Mon Mar  7 19:01:15 PST 2016
pid 13093 exit 0
Ron Hinchley
quelle