Como chamar comandos de shell do Ruby

1077

Como chamo comandos de shell de dentro de um programa Ruby? Como faço para obter a saída desses comandos novamente para o Ruby?

CodingWithoutComments
fonte
3
Embora essa pergunta seja útil, ela não é bem feita. O Ruby tem várias maneiras de chamar subconjuntos que são bem documentados e facilmente encontrados lendo a documentação do Kernel e do Open3 e pesquisando aqui no SO.
the Tin Man
1
Infelizmente, este tópico é bastante complexo. Open3( docs ) é a melhor opção para a maioria das situações, IMO, mas em versões mais antigas do Ruby, ele não respeitará um código modificado PATH( bugs.ruby-lang.org/issues/8004 ), e dependendo de como você passa os argumentos (especificamente , se você usar hash op com não-palavras-chave), ele poderá ser quebrado. Mas, se você enfrentar essas situações, estará fazendo algo bem avançado e poderá descobrir o que fazer lendo a implementação de Open3.
Joshua Cheek
3
Estou surpreso que ninguém tenha mencionado Shellwords.escape( doc ). Você não deseja inserir dados do usuário diretamente nos comandos do shell - escape primeiro! Veja também comando de injeção .
Kelvin

Respostas:

1319

Essa explicação é baseada em um script Ruby comentado de um amigo meu. Se você deseja melhorar o script, fique à vontade para atualizá-lo no link.

Primeiro, observe que, quando Ruby chama um shell, ele normalmente chama /bin/sh, não Bash. Algumas sintaxas do Bash não são suportadas por /bin/shtodos os sistemas.

Aqui estão algumas maneiras de executar um script de shell:

cmd = "echo 'hi'" # Sample string that can be used
  1. Kernel#` , comumente chamado backticks - `cmd`

    É como muitas outras linguagens, incluindo Bash, PHP e Perl.

    Retorna o resultado (isto é, saída padrão) do comando shell.

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

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

    Após o xcaractere, há um delimitador, que pode ser qualquer caractere. Se o delimitador é um dos personagens (, [, {, ou <, o literal consiste dos personagens até o delimitador de fechamento correspondente, tendo em conta pares de delimitadores aninhadas. Para todos os outros delimitadores, o literal compreende os caracteres até a próxima ocorrência do caractere delimitador. A interpolação de string #{ ... }é permitida.

    Retorna o resultado (ou seja, saída padrão) do comando shell, assim como os backticks.

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

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

    Executa o comando fornecido em um subshell.

    Retorna truese o comando foi encontrado e executado com êxito, falsecaso contrário.

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

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

    Substitui o processo atual executando o comando externo fornecido.

    Retorna nenhum, o processo atual é substituído e nunca continua.

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

Aqui estão alguns conselhos adicionais:, $?que é o mesmo que $CHILD_STATUS, acessa o status do último comando executado pelo sistema se você usar os backticks, system()ou %x{}. Você pode acessar as propriedades exitstatuse pid:

$?.exitstatus

Para mais leitura, consulte:

Steve Willard
fonte
4
Preciso registrar as saídas do meu executável no servidor de produção, mas não encontrei nenhuma maneira. Eu usei puts #{cmd}e logger.info ( #{cmd}). Existe alguma maneira de registrar suas saídas na produção?
Omer Aslam
5
E / S # popen () e Open3 # popen3 (). mentalized.net/journal/2010/03/08/...
hughdbrown
6
Por uma questão de completude (como eu pensei que isso também seria um comando Ruby): Rake tem sh, que executa "Execute o comando do sistema cmd. Se forem fornecidos vários argumentos, o comando não será executado com o shell (mesma semântica que o Kernel :: exec e Kernel :: system) ".
sschuberth
40
Backticks não capturam STDERR por padrão. Acrescente `2> & 1` ao comando se você deseja capturar #
Andrei Botalov 17/02/2013
14
Eu acho que essa resposta seria ligeiramente melhorada se dissesse que backticks e% x retornavam a "saída", em vez do "resultado", do comando fornecido. Este último pode ser confundido com status de saída. Ou isso sou só eu?
skagedal
275

Aqui está um fluxograma baseado em " Quando usar cada método de inicialização de um subprocesso no Ruby ". Veja também, " Engane um aplicativo para pensar que seu padrão é um terminal, não um tubo ".

insira a descrição da imagem aqui

Ian
fonte
24
Uau haha. Muito útil, mesmo que o fato de que isso exista seja infeliz #
Josh Bodah 19/12/16
Como uma observação lateral, acho o método spawn () encontrado em muitos lugares diferentes (por exemplo, Kernele Processque é mais versátil. É mais ou menos o mesmo PTY.spawn(), mas mais genérico.)
Smar
160

A maneira que eu gosto de fazer isso é usar o %xliteral, o que facilita (e é legível!) Usar aspas em um comando, da seguinte maneira:

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

Que, nesse caso, preencherá a lista de arquivos com todos os arquivos de teste no diretório atual, que você pode processar conforme o esperado:

directorylist.each do |filename|
  filename.chomp!
  # work with file
end
cynicalman
fonte
4
Será que %x[ cmd ]retorna um array para você?
x-yuri
2
o acima não funciona para mim. `` <main> ': método indefinido each' for :String (NoMethodError) como funcionou para você? Estou usando ruby -v ruby 1.9.3p484 (2013-11-22 revision 43786) [i686-linux]Você tem certeza de que uma matriz é retornada do comando para que o loop realmente funcione?
Nasser
% x [cmd] .split ("\ n") retornará uma lista :) :)
Ian Ellis
65

Aqui está o melhor artigo na minha opinião sobre a execução de scripts de shell no Ruby: " 6 maneiras de executar comandos de shell no Ruby ".

Se você só precisa obter a saída, use backticks.

Eu precisava de coisas mais avançadas, como STDOUT e STDERR, então usei a gema Open4. Você tem todos os métodos explicados lá.

Mihai A
fonte
2
A postagem descrita aqui não discute a %xopção de sintaxe.
Mei
+1 para o Open4. Eu já havia começado a tentar implementar minha própria versão do spawnmétodo quando o encontrei.
Brandan
40

O meu favorito é o Open3

  require "open3"

  Open3.popen3('nroff -man') { |stdin, stdout, stderr| ... }
anshul
fonte
2
Eu também gosto do open3, especialmente do Open3.capture3: ruby-doc.org/stdlib-1.9.3/libdoc/open3/rdoc/… -> stdout, stderr, status = Open3.capture3('nroff -man', :stdin_data => stdin)
severin
Existe alguma documentação sobre como executar testes de especificação e unidade com o Open3 ou outros Open's no std-lib do Ruby? É difícil testar shell outs no meu nível atual de entendimento.
FilBot3
29

Algumas coisas a considerar ao escolher entre esses mecanismos são:

  1. Você só quer stdout ou precisa de stderr também? Ou mesmo separados?
  2. Qual é o tamanho da sua saída? Deseja manter o resultado inteiro na memória?
  3. Deseja ler parte de sua saída enquanto o subprocesso ainda está em execução?
  4. Você precisa de códigos de resultado?
  5. Você precisa de um objeto Ruby que represente o processo e permita matá-lo sob demanda?

Você pode precisar de qualquer coisa, desde simples backticks (``) system()e IO.popenaté completos Kernel.fork/ Kernel.execcom IO.pipee IO.select.

Você também pode colocar limites de tempo na mistura se um subprocesso demorar muito para ser executado.

Infelizmente, depende muito .

Nick Brosnahan
fonte
25

Mais uma opção:

Quando você:

  • precisa stderr, bem como stdout
  • não pode / não usa o Open3 / Open4 (eles lançam exceções no NetBeans no meu Mac, não sei por que)

Você pode usar o redirecionamento de shell:

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

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

A 2>&1sintaxe funciona no Linux , Mac e Windows desde os primeiros dias do MS-DOS.

jg-faustus
fonte
25

Definitivamente, não sou especialista em Ruby, mas vou tentar:

$ irb 
system "echo Hi"
Hi
=> true

Você também deve ser capaz de fazer coisas como:

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

As respostas acima já são ótimas, mas eu realmente quero compartilhar o seguinte artigo de resumo: " 6 maneiras de executar comandos do shell em Ruby "

Basicamente, ele nos diz:

Kernel#exec:

exec 'echo "hello $HOSTNAME"'

systeme $?:

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 - uma jóia:

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

Se você realmente precisa do Bash, de acordo com a nota na "melhor" resposta.

Primeiro, observe que, quando Ruby chama um shell, ele normalmente chama /bin/sh, não Bash. Algumas sintaxas do Bash não são suportadas por /bin/shtodos os sistemas.

Se você precisar usar o Bash, insira bash -c "your Bash-only command"dentro do método de chamada desejado:

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

Testar:

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

Ou se você estiver executando um arquivo de script existente como

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

Ruby deve honrar o shebang, mas você sempre pode usar

system("bash ./my_script.sh")

para garantir que, embora possa haver uma pequena sobrecarga na /bin/shexecução /bin/bash, você provavelmente não notará.

dragon788
fonte
11

Você também pode usar os operadores de backtick (`), semelhantes ao Perl:

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

Útil se você precisar de algo simples.

Qual método você deseja usar depende exatamente do que você está tentando realizar; consulte os documentos para obter mais detalhes sobre os diferentes métodos.

Rufo Sanchez
fonte
10

Podemos alcançá-lo de várias maneiras.

Usando Kernel#exec, nada após a execução deste comando:

exec('ls ~')

Usando backticks or %x

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

Usando Kernel#systemcommand, retorna truese for bem-sucedido, falsese malsucedido e retorna nilse a execução do comando falhar:

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

A maneira mais fácil é, por exemplo:

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

Usando as respostas aqui e vinculadas na resposta de Mihai, montei uma função que atende a esses requisitos:

  1. Captura perfeitamente STDOUT e STDERR para que não "vazem" quando meu script é executado no console.
  2. Permite que os argumentos sejam passados ​​para o shell como uma matriz, portanto, não há necessidade de se preocupar em escapar.
  3. Captura o status de saída do comando para que fique claro quando ocorrer um erro.

Como um bônus, este também retornará STDOUT nos casos em que o comando shell sair com sucesso (0) e colocar qualquer coisa em STDOUT. Dessa maneira, difere de system, que simplesmente retorna truenesses casos.

Código a seguir. A função específica é 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
fonte
9

Não esqueça o spawncomando para criar um processo em segundo plano para executar o comando especificado. Você pode até esperar sua conclusão usando a Processclasse e a retornada 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

O documento diz: Este método é semelhante, #systemmas não espera que o comando seja concluído.

MonsieurDart
fonte
2
Kernel.spawn()parece ser muito mais versátil do que todas as outras opções.
Kashyap
6

Se você tiver um caso mais complexo do que o caso comum que não pode ser tratado ``, faça check-out Kernel.spawn(). Este parece ser o mais genérico / com todos os recursos fornecidos pelo estoque Ruby para executar comandos externos.

Você pode usá-lo para:

  • crie grupos de processos (Windows).
  • redirecionar dentro, fora, erro para arquivos / uns aos outros.
  • definir env vars, umask.
  • altere o diretório antes de executar um comando.
  • definir limites de recursos para CPU / dados / etc.
  • Faça tudo o que puder ser feito com outras opções em outras respostas, mas com mais código.

A documentação do Ruby tem bons exemplos:

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

O método backticks (`) é o mais fácil de chamar comandos de shell do Ruby. Retorna o resultado do comando shell:

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

Dado um comando como attrib:

require 'open3'

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

Descobri que, embora esse método não seja tão memorável quanto

system("thecommand")

ou

`thecommand`

nos backticks, uma coisa boa desse método, em comparação com outros métodos, é que backticks parecem não me deixar putso comando que eu corro / armazeno o comando que eu quero executar em uma variável e system("thecommand")não me permite obter a saída, enquanto esse método me permite fazer as duas coisas e permite acessar stdin, stdout e stderr independentemente.

Consulte " Executando comandos em ruby " e a documentação do Ruby Open3 .

barlop
fonte
3

Esta não é realmente uma resposta, mas talvez alguém ache útil:

Ao usar a TK GUI no Windows, e você precisa chamar comandos de shell do rubyw, sempre haverá uma janela CMD irritante aparecendo por menos de um segundo.

Para evitar isso, você pode usar:

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

ou

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

Ambos armazenam a ipconfigsaída dentro log.txt, mas nenhuma janela será exibida.

Você precisará require 'win32ole'dentro do seu script.

system(), exec()e spawn()todos aparecerão nessa janela irritante ao usar TK e rubyw.

lucaortis
fonte
-2

Aqui está um exemplo legal que eu uso em um script ruby ​​no OS X (para que eu possa iniciar um script e obter uma atualização mesmo depois de sair da janela):

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