Ottenere l'output delle chiamate di sistema () in Ruby


309

Se chiamo un comando usando il sistema Kernel # in Ruby, come ottengo il suo output?

system("ls")


Questa è una discussione molto manuale, grazie. La classe per eseguire comandi e ottenere feedback è ottima nel codice di esempio.
Ylluminate

3
Per futuri googler. Se vuoi conoscere altre chiamate di comandi di sistema e le loro differenze, vedi questa risposta SO .
Uzbekjon,

Risposte:


347

Vorrei espandere e chiarire un po ' la risposta del caos .

Se il comando viene circondato da backtick, non è necessario (esplicitamente) chiamare system (). I backtick eseguono il comando e restituiscono l'output come stringa. È quindi possibile assegnare il valore a una variabile in questo modo:

output = `ls`
p output

o

printf output # escapes newline chars

4
cosa succede se devo fornire una variabile come parte del mio comando? Cioè, in che cosa si tradurrebbe qualcosa di simile a system ("ls" + nomefile) quando si usano i backtick?
Vijay Dev,

47
Si può fare la valutazione dell'espressione proprio come si farebbe con le stringhe regolari: ls #{filename}.
Craig Walker,

36
Questa risposta non è consigliabile: introduce il nuovo problema dell'input dell'utente non autorizzato.
Dogweather

4
@Dogweather: potrebbe essere vero, ma è diverso da qualsiasi altro metodo?
Craig Walker

20
se vuoi catturare stderr basta mettere 2> & 1 alla fine del tuo comando. es. output =command 2>&1
micred

243

Tenere presente che tutte le soluzioni in cui si passa una stringa contenente valori forniti dall'utente system, %x[]ecc. Non sono sicure! In realtà non sicuro significa: l'utente può attivare l'esecuzione del codice nel contesto e con tutte le autorizzazioni del programma.

Per quanto posso dire solo systeme Open3.popen3fornire una variante sicura / in fuga in Ruby 1.8. In Ruby 1.9 IO::popenaccetta anche un array.

Basta passare ogni opzione e argomento come un array a una di queste chiamate.

Se non è necessario solo lo stato di uscita, ma anche il risultato che probabilmente si desidera utilizzare 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

Si noti che il modulo di blocco chiuderà automaticamente stdin, stdout e stderr, altrimenti dovrebbero essere chiusi esplicitamente .

Maggiori informazioni qui: Formazione di comandi della shell sanitaria o chiamate di sistema in Ruby


26
Questa è l'unica risposta che risponde effettivamente alla domanda e risolve il problema senza introdurne di nuovi (input non automatizzati).
Dogweather

2
Grazie! Questo è il tipo di risposta che speravo. Una correzione: le getschiamate dovrebbero passare l'argomento nil, altrimenti altrimenti otteniamo solo la prima riga dell'output. Quindi ad es stdout.gets(nil).
Greg Price,

3
stdin, stdout e stderr devono essere chiusi esplicitamente in forma non bloccata .
Yarin,

Qualcuno sa se qualcosa è cambiato in Ruby 2.0 o 2.1? Modifiche o commenti sarebbero apprezzati ;-)
Simon Hürlimann,

1
Penso che la discussione intorno Open3.popen3 manchi un grosso problema: se si ha un sottoprocesso che scrive più dati su stdout di quanti ne possano contenere una pipe, il sottoprocesso viene sospeso stderr.writee il programma si blocca stdout.gets(nil).
hagello,

165

Solo per la cronaca, se vuoi entrambi (risultato di output e operazione) puoi fare:

output=`ls no_existing_file` ;  result=$?.success?

4
Questo e 'esattamente quello che stavo cercando. Grazie.
jdl

12
Questo cattura solo stdout e stderr va alla console. Per ottenere stderr, usa: output=`ls no_existing_file 2>&1`; result=$?.success?
peterept

8
Questa risposta non è sicura e non deve essere utilizzata: se il comando è tutt'altro che una costante, è probabile che la sintassi del backtick provochi un bug, probabilmente una vulnerabilità della sicurezza. (E anche se è una costante, probabilmente farà sì che qualcuno lo utilizzi per una non costante in seguito e causi un errore.) Vedere la risposta di Simon Hürlimann per una soluzione corretta.
Greg Price,

23
complimenti a Greg Price per la comprensione della necessità di sfuggire all'input dell'utente, ma non è corretto dire questa risposta come scritto non è sicuro. Il metodo Open3 menzionato è più complicato e introduce più dipendenze, e l'argomento secondo cui qualcuno lo "userà per un non costante in seguito" è un pagliaccio. È vero, probabilmente non li useresti in un'app Rails, ma per un semplice script di utilità di sistema senza possibilità di input dell'utente non attendibile, i backtick vanno benissimo e nessuno dovrebbe sentirsi male a usarli.
sbeam,

69

Il modo più semplice per fare questo in modo corretto e sicuro è quello di utilizzare Open3.capture2(), Open3.capture2e()o Open3.capture3().

L'uso dei backtick di ruby ​​e il suo %xalias NON SONO SICURI IN NESSUNA CIRCOSTANZE se utilizzati con dati non attendibili. È PERICOLOSO , chiaro e semplice:

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

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

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

La systemfunzione, al contrario, sfugge correttamente agli argomenti se usata correttamente :

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

Il problema è che restituisce il codice di uscita anziché l'output e l'acquisizione di quest'ultimo è contorta e disordinata.

La risposta migliore in questo thread finora menziona Open3, ma non le funzioni più adatte all'attività. Open3.capture2, capture2eE capture3il lavoro come system, ma ritorna due o tre argomenti:

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

Un'altra menziona IO.popen(). La sintassi può essere goffa nel senso che vuole un array come input, ma funziona anche:

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

Per comodità, puoi avvolgere Open3.capture3()una funzione, ad esempio:

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

Esempio:

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

Produce quanto segue:

nil
nil
false
false
/usr/bin/which         <— stdout from system('which', 'which')
true                   <- p system('which', 'which')
"/usr/bin/which"       <- p syscall('which', 'which')

2
Questa è la risposta corretta È anche il più informativo. L'unica cosa che manca è un avvertimento sulla chiusura degli std * s. Vedi questo altro commento : require 'open3'; output = Open3.popen3("ls") { |stdin, stdout, stderr, wait_thr| stdout.read } nota che il modulo di blocco chiuderà automaticamente stdin, stdout e stderr, altrimenti dovrebbero essere chiusi esplicitamente .
Peter H. Boling,

@ PeterH.Boling: meglio che ne sappia, il capture2 , capture2ee capture3anche li chiudo automaticamente * s. (Per lo meno, non ho mai incontrato il problema da parte mia.)
Denis de Bernardy,

senza usare il modulo a blocchi non c'è modo per un codebase di sapere quando qualcosa dovrebbe essere chiuso, quindi dubito fortemente che vengano chiusi. Probabilmente non hai mai riscontrato un problema perché non chiuderli non causerà problemi in un processo di breve durata e se riavvii un processo di lunga durata abbastanza spesso, otto non verrà visualizzato lì a meno che non apri std * s in un ciclo continuo. Linux ha un limite di descrittore di file elevato, che puoi colpire, ma finché non lo colpisci non vedrai il "bug".
Peter H. Boling,

2
@ PeterH.Boling: No no, vedi il codice sorgente. Le funzioni sono solo avvolgenti Open3#popen2,popen2e e popen3con un blocco predefinito: ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/...
Denis de Bernardy

1
@Dennis de Barnardy Forse vi siete persi che ho collegato alla stessa documentazione della classe (anche se per Ruby 2.0.0, e un metodo diverso. Ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/... Dall'esempio : `` `stdin, stdout, stderr, wait_thr = Open3.popen3 ([env,] cmd ... [, opts]) pid = wait_thr [: pid] # pid del processo avviato ... stdin.close # stdin , stdout e stderr dovrebbero essere esplicitamente chiusi in questo modulo. stdout.close stderr.close `` `Stavo solo citando la documentazione." # stdin, stdout e stderr dovrebbero essere esplicitamente chiusi in questo modulo. "
Peter H. Boling

61

Puoi usare system () o% x [] a seconda del tipo di risultato di cui hai bisogno.

system () restituisce true se il comando è stato trovato ed eseguito correttamente, false altrimenti.

>> 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 [..] d'altra parte salva i risultati del comando come stringa:

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

Il post sul blog di Jay Fields spiega in dettaglio le differenze tra l'utilizzo di system, exec e% x [..].


2
Grazie per la punta di usare% x []. Risolveva solo un problema che avevo quando usavo i tick indietro in uno script ruby ​​in Mac OS X. Quando eseguivo lo stesso script su un computer Windows con Cygwin, falliva a causa dei tick posteriori, ma funzionava con% x [].
Henrik Warne,

22

Se è necessario sfuggire agli argomenti, in Ruby 1.9 IO.popen accetta anche un array:

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

Nelle versioni precedenti è possibile utilizzare Open3.popen3 :

require "open3"

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

Se devi passare anche lo stdin, questo dovrebbe funzionare sia in 1.9 che in 1.8:

out = IO.popen("xxd -p", "r+") { |io|
    io.print "xyz"
    io.close_write
    io.read.chomp
}
p out # "78797a"

Grazie! Questo è perfetto.
Greg Price,

21

Usa i backtick:

`ls`

5
I backtick non producono output sul terminale.
Mei

3
Non produce stderr ma dà stdout.
Nickolay Kondratenko,

1
Non scrive su stdout o stderr. Proviamo questo esempio ruby -e '%x{ls}'- nota, nessun output. (Fyi %x{}equivale a backtick.)
Ocodo

Questo ha funzionato alla grande. L'uso fa sheco all'output sulla console (ad es. STDOUT) e lo restituisce. Questo no.
Joshua Pinter

19

Un altro modo è:

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

Nota che è il carattere "pipe" prima di "ls" in open. Questo può anche essere usato per inserire dati nell'input standard del programma e per leggere l'output standard.


Ho appena usato questo per leggere l'output standard da un comando aws cli per leggere il json e non il valore di ritorno ufficiale di 'true'
kraftydevil,

14

Ho trovato utile quanto segue se è necessario il valore restituito:

result = %x[ls]
puts result

Volevo specificamente elencare i pid di tutti i processi Java sulla mia macchina e ho usato questo:

ids = %x[ps ax | grep java | awk '{ print $1 }' | xargs]

È un'ottima soluzione.
Ronan Louarn,


9

Mentre usare backtick o popen è spesso ciò che vuoi davvero, in realtà non risponde alla domanda posta. Potrebbero esserci validi motivi per acquisire l' systemoutput (forse per test automatici). Un piccolo Google cercò una risposta ho pensato di pubblicare qui a beneficio di altri.

Dato che ne avevo bisogno per il test, il mio esempio usa un'impostazione a blocchi per catturare l'output standard poiché la systemchiamata effettiva è sepolta nel codice testato:

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

Questo metodo acquisisce qualsiasi output nel blocco dato usando un file temporaneo per memorizzare i dati effettivi. Esempio di utilizzo:

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

È possibile sostituire la systemchiamata con tutto ciò che chiama internamente system. stderrSe lo desideri, puoi anche utilizzare un metodo simile per acquisire .


8

Se si desidera che l'output venga reindirizzato a un file mediante Kernel#system , è possibile modificare i descrittori in questo modo:

reindirizzare stdout e stderr su un file (/ tmp / log) in modalità append:

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

Per un comando di lunga durata, questo memorizzerà l'output in tempo reale. Puoi anche salvare l'output usando un IO.pipe e reindirizzarlo dal sistema Kernel #.




Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.