Obtendo saída de chamadas system () em Ruby


309

Se eu chamar um comando usando o sistema Kernel # no Ruby, como obtenho sua saída?

system("ls")

1
Você pode querer ter um olhar para esta discussão em comp.lang.ruby
Manrico Corazzi

Esta é uma discussão muito manual, obrigado. A classe para executar comandos e obter feedback é excelente no código de exemplo.
yuminuminate 1/11/11

3
Para futuros googlers. Se você quiser aprender sobre outras chamadas de comando do sistema e suas diferenças, consulte esta resposta do SO .
Uzbekjon

Respostas:


347

Eu gostaria de expandir e esclarecer um pouco a resposta do caos .

Se você cercar seu comando com reticências, não precisará (explicitamente) chamar system (). Os backticks executam o comando e retornam a saída como uma string. Você pode atribuir o valor a uma variável da seguinte maneira:

output = `ls`
p output

ou

printf output # escapes newline chars

4
e se eu precisar fornecer uma variável como parte do meu comando? Ou seja, no que algo como sistema ("ls" + nome do arquivo) se traduz quando os backticks devem ser usados?
Vijay Dev

47
Você pode fazer a avaliação da expressão, tal como faria com cordas regulares: ls #{filename}.
Craig Walker

36
Esta resposta não é aconselhável: apresenta o novo problema de entrada não autorizada do usuário.
Dogweather

4
@ Dogweather: isso pode ser verdade, mas é diferente de qualquer outro método?
Craig Walker

20
se você quiser capturar o stderr, basta colocar 2> & 1 no final do seu comando. por exemplo, a saída =command 2>&1
micred

243

Esteja ciente de que todas as soluções em que você passa uma string contendo os valores do usuário fornecido para system, %x[]etc. não são seguras! Inseguro significa: o usuário pode acionar o código para ser executado no contexto e com todas as permissões do programa.

Tanto quanto eu posso dizer única systeme Open3.popen3fornecem a / escapando variante segura no Ruby 1.8. No Ruby 1.9 IO::popentambém aceita uma matriz.

Basta passar todas as opções e argumentos como uma matriz para uma dessas chamadas.

Se você precisar não apenas do status de saída, mas também do resultado que provavelmente deseja usar 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

Observe que o formulário do bloco fecha automaticamente stdin, stdout e stderr; caso contrário, eles precisam ser fechados explicitamente .

Mais informações aqui: Formação de comandos shell sanitários ou chamadas de sistema em Ruby


26
Esta é a única resposta que realmente responde à pergunta e resolve o problema sem introduzir novas (entrada não autorizada).
Dogweather

2
Obrigado! Esse é o tipo de resposta que eu esperava. Uma correção: as getschamadas devem passar o argumento nil, caso contrário, apenas obtemos a primeira linha da saída. Então, por exemplo stdout.gets(nil).
Greg Price

3
stdin, stdout e stderr devem ser fechados explicitamente em formato não-bloco .
Yarin

Alguém sabe se algo mudou no Ruby 2.0 ou 2.1? Edições ou comentários seria apreciada ;-)
Simon Hürlimann

1
Eu acho que a discussão em torno Open3.popen3está perdendo um grande problema: se você tem um subprocesso que grava mais dados no stdout do que um pipe pode suportar, o subprocesso é suspenso stderr.writee seu programa fica preso stdout.gets(nil).
hagello

165

Apenas para o registro, se você deseja os dois (resultado de saída e operação), pode:

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

4
Era exatamente isso que eu estava procurando. Obrigado.
JDL

12
Isso captura apenas o stdout e o stderr vai para o console. Para obter o stderr, use: output=`ls no_existing_file 2>&1`; result=$?.success?
peterept

8
Essa resposta é insegura e não deve ser usada - se o comando não for uma constante, é provável que a sintaxe do backtick cause um bug, possivelmente uma vulnerabilidade de segurança. (E mesmo que seja uma constante, provavelmente fará com que alguém a use por uma não-constante posteriormente e cause um erro.) Veja a resposta de Simon Hürlimann para obter uma solução correta.
Greg Price

23
parabéns a Greg Price por entender a necessidade de escapar da entrada do usuário, mas não é correto dizer que esta resposta como escrita é insegura. O método Open3 mencionado é mais complicado e introduz mais dependências, e o argumento de que alguém "o usará depois para não ser constante" é um palhaço. É verdade que você provavelmente não os usaria em um aplicativo Rails, mas, para um script de utilitário de sistema simples, sem possibilidade de entrada não confiável do usuário, os backticks são perfeitamente adequados e ninguém deve se sentir mal por usá-los.
sbeam

69

A maneira simples de fazer isso corretamente e de forma segura é usar Open3.capture2(), Open3.capture2e()ou Open3.capture3().

O uso de backticks de rubi e seu %xapelido NÃO SÃO SEGUROS SOB QUALQUER CIRCUNSTÂNCIA se usados ​​com dados não confiáveis. É PERIGOSO , puro e simples:

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

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

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

A systemfunção, por outro lado, escapa argumentos corretamente se usada corretamente :

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

O problema é que ele retorna o código de saída em vez da saída e a captura do último é complicada e confusa.

A melhor resposta neste tópico até agora menciona o Open3, mas não as funções mais adequadas para a tarefa. Open3.capture2, capture2ee capture3funciona como system, mas retorna dois ou três argumentos:

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

Outro menciona IO.popen(). A sintaxe pode ser desajeitada no sentido em que deseja uma matriz como entrada, mas também funciona:

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

Por conveniência, você pode agrupar Open3.capture3()uma função, por exemplo:

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

Exemplo:

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

Rende o seguinte:

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
Essa é a resposta correta. É também o mais informativo. A única coisa que falta é um aviso sobre o fechamento dos std * s. Veja este outro comentário : require 'open3'; output = Open3.popen3("ls") { |stdin, stdout, stderr, wait_thr| stdout.read } Observe que o formulário do bloco fecha automaticamente stdin, stdout e stderr; caso contrário, eles precisam ser fechados explicitamente .
Peter H. Boling

@ PeterH.Boling: É melhor que eu saiba capture2, capture2ee capture3também os feche std * s automaticamente. (Pelo menos, eu nunca corri para o problema no meu fim.)
Denis de Bernardy

sem usar a forma de bloco não há nenhuma maneira para uma base de código para saber quando algo deve ser fechada, então eu altamente duvido que eles estão sendo fechados. Você provavelmente nunca encontrou um problema porque não fechá-los não causará problemas em um processo de curta duração, e se você reiniciar um processo de execução com bastante frequência, o otto também não aparecerá lá, a menos que você esteja abrindo std * s em uma volta. O Linux tem um limite alto de descritor de arquivos, que você pode atingir, mas até atingi-lo, não verá o "bug".
Peter H. Boling

2
@ PeterH.Boling: Não, não, consulte o código fonte. As funções são apenas wrappers em torno Open3#popen2, popen2ee popen3com um bloco predefinido: ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/...
Denis de Bernardy

1
@Dennis de Barnardy Talvez você tenha perdido o link para a mesma documentação de classe (embora para Ruby 2.0.0, e um método diferente. Ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/… Do exemplo : `` stdin, stdout, stderr, wait_thr = Open3.popen3 ([env,] cmd ... [, opts]) pid = wait_thr [: pid] # pid do processo iniciado ... stdin.close # stdin , stdout e stderr devem ser fechados explicitamente neste formulário. stdout.close stderr.close `` `Eu estava apenas citando a documentação." # stdin, stdout e stderr devem ser fechados explicitamente neste formulário. "
Peter H. Boling

61

Você pode usar system () ou% x [] dependendo do tipo de resultado necessário.

system () retornando true se o comando foi encontrado e executado com êxito; caso contrário, 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 [..] por outro lado, salva os resultados do comando como uma string:

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

O post de Jay Fields explica em detalhes as diferenças entre usar system, exec e% x [..].


2
Obrigado pela dica de usar% x []. Ele resolveu um problema que eu tinha, quando utilizava back ticks em um script ruby ​​no Mac OS X. Ao executar o mesmo script em uma máquina Windows com Cygwin, ele falhou por causa dos back ticks, mas trabalhou com% x [].
9117 Henrik Warne

22

Se você precisar escapar dos argumentos, no Ruby 1.9, o IO.popen também aceita uma matriz:

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

Nas versões anteriores, você pode usar o Open3.popen3 :

require "open3"

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

Se você também precisa passar o stdin, isso deve funcionar em 1.9 e 1.8:

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

Obrigado! Isto é perfeito.
Greg Price

21

Você usa backticks:

`ls`

5
Backticks não produzem saída no terminal.
Mei

3
Não produz stderr, mas fornece stdout.
Nickolay Kondratenko

1
Não escreve para stdout ou stderr. Vamos tentar este exemplo ruby -e '%x{ls}'- note, sem saída. (fyi %x{}é equivalente a backticks).
ocodo 13/08/2015

Isso funcionou muito bem. Usar shecoaria a saída para o console (ou seja, STDOUT) e também a retornaria. Isso não acontece.
Joshua Pinter

19

Outra maneira é:

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

Observe que esse é o caractere "pipe" antes de "ls" em aberto. Isso também pode ser usado para alimentar dados na entrada padrão do programa e também para ler sua saída padrão.


Apenas usado isso para ler a saída padrão de um comando cli AWS, a fim de ler a JSON e não o valor de retorno oficial da 'verdadeira'
kraftydevil

14

Achei que o seguinte é útil se você precisar do valor de retorno:

result = %x[ls]
puts result

Eu queria especificamente listar os pids de todos os processos Java na minha máquina e usei isso:

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

É uma ótima solução.
Ronan Louarn


9

Embora o uso de backticks ou popen geralmente seja o que você realmente deseja, ele não responde à pergunta. Pode haver razões válidas para capturar a systemsaída (talvez para testes automatizados). Um pouco de pesquisador no Google encontrou uma resposta que pensei em publicar aqui para o benefício de outras pessoas.

Como eu precisava disso para testar, meu exemplo usa uma configuração de bloco para capturar a saída padrão, pois a systemchamada real está oculta no código que está sendo testado:

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

Este método captura qualquer saída no bloco especificado usando um arquivo temporário para armazenar os dados reais. Exemplo de uso:

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

Você pode substituir a systemchamada por qualquer coisa que chame internamente system. Você também pode usar um método semelhante para capturar, stderrse quiser.


8

Se você deseja que a saída seja redirecionada para um arquivo usando Kernel#system, você pode modificar descritores como este:

redirecione stdout e stderr para um arquivo (/ tmp / log) no modo de acréscimo:

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

Para um comando de longa execução, isso armazenará a saída em tempo real. Você também pode armazenar a saída usando um IO.pipe e redirecioná-la do sistema Kernel #.




Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.