Se chiamo un comando usando il sistema Kernel # in Ruby, come ottengo il suo output?
system("ls")
Se chiamo un comando usando il sistema Kernel # in Ruby, come ottengo il suo output?
system("ls")
Risposte:
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
ls #{filename}
.
command 2>&1
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 system
e Open3.popen3
fornire una variante sicura / in fuga in Ruby 1.8. In Ruby 1.9 IO::popen
accetta 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
gets
chiamate dovrebbero passare l'argomento nil
, altrimenti altrimenti otteniamo solo la prima riga dell'output. Quindi ad es stdout.gets(nil)
.
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.write
e il programma si blocca stdout.gets(nil)
.
Solo per la cronaca, se vuoi entrambi (risultato di output e operazione) puoi fare:
output=`ls no_existing_file` ; result=$?.success?
output=`ls no_existing_file 2>&1`; result=$?.success?
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 %x
alias 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 system
funzione, 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
, capture2e
E capture3
il 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')
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 .
capture2
, capture2e
e capture3
anche li chiudo automaticamente * s. (Per lo meno, non ho mai incontrato il problema da parte mia.)
Open3#popen2
,popen2e
e popen3
con un blocco predefinito: ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/...
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 [..].
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"
Usa i backtick:
`ls`
ruby -e '%x{ls}'
- nota, nessun output. (Fyi %x{}
equivale a backtick.)
sh
eco all'output sulla console (ad es. STDOUT) e lo restituisce. Questo no.
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 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]
Come ha già spiegato Simon Hürlimann , Open3 è più sicuro dei backtick ecc.
require 'open3'
output = Open3.popen3("ls") { |stdin, stdout, stderr, wait_thr| stdout.read }
Si noti che il modulo di blocco chiuderà automaticamente stdin, stdout e stderr, altrimenti dovrebbero essere chiusi esplicitamente .
Mentre usare backtick o popen è spesso ciò che vuoi davvero, in realtà non risponde alla domanda posta. Potrebbero esserci validi motivi per acquisire l' system
output (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 system
chiamata 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 system
chiamata con tutto ciò che chiama internamente system
. stderr
Se lo desideri, puoi anche utilizzare un metodo simile per acquisire .
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 #.
In sostituzione del sistema diretto (...) è possibile utilizzare Open3.popen3 (...)
Ulteriori discussioni: http://tech.natemurray.com/2007/03/ruby-shell-commands.html
Non ho trovato questo qui quindi aggiungendolo, ho avuto dei problemi a ottenere l'output completo.
È possibile reindirizzare STDERR su STDOUT se si desidera acquisire STDERR usando backtick.
output = `grep hosts / private / etc / * 2> & 1`
fonte: http://blog.bigbinary.com/2012/10/18/backtick-system-exec-in-ruby.html
puts `date`
puts $?
Mon Mar 7 19:01:15 PST 2016
pid 13093 exit 0