Práticas recomendadas com STDIN em Ruby?

307

Eu quero lidar com a entrada da linha de comando no Ruby:

> cat input.txt | myprog.rb
> myprog.rb < input.txt
> myprog.rb arg1 arg2 arg3 ...

Qual é a melhor maneira de fazer isso? Em particular, quero lidar com o STDIN em branco e espero uma solução elegante.

#!/usr/bin/env ruby

STDIN.read.split("\n").each do |a|
   puts a
end

ARGV.each do |b|
    puts b
end
griflet
fonte
5
Apenas uma observação secundária: as duas primeiras linhas de comando que você fornece são exatamente as mesmas do ponto de vista de myprog.rb: o input.txtarquivo é anexado ao stdin ; o shell gerencia isso para você.
Mei
6
^^ isso geralmente é chamado de "uso inútil de gato"; você verá muito disso.
9788 Steve Kehlet #
18
@SteveKehlet no entanto eu acredito que é mais inteligente referido como "abuso de gato"
OneChillDude

Respostas:

403

A seguir, algumas coisas que encontrei em minha coleção de Ruby obscuros.

Portanto, no Ruby, uma implementação simples e sem sinos do comando Unix catseria:

#!/usr/bin/env ruby
puts ARGF.read

ARGFé seu amigo quando se trata de entrada; é um arquivo virtual que obtém toda a entrada de arquivos nomeados ou de STDIN.

ARGF.each_with_index do |line, idx|
    print ARGF.filename, ":", idx, ";", line
end

# print all the lines in every file passed via command line that contains login
ARGF.each do |line|
    puts line if line =~ /login/
end

Graças a Deus não conseguimos o operador de diamantes em Ruby, mas conseguimos ARGFcomo um substituto. Embora obscuro, ele acaba sendo útil. Considere este programa, que anexa os cabeçalhos de direitos autorais no local (graças a outro perlismo -i) a todos os arquivos mencionados na linha de comando:

#!/usr/bin/env ruby -i

Header = DATA.read

ARGF.each_line do |e|
  puts Header if ARGF.pos - e.length == 0
  puts e
end

__END__
#--
# Copyright (C) 2007 Fancypants, Inc.
#++

Crédito para:

Jonke
fonte
12
ARGF é o caminho a percorrer. É o Ruby construído para lidar com arquivos e stdin de uma maneira geral.
Pistos
1
(vi isso e pensei em você) com relação a esses créditos: blog.nicksieger.com/articles/2007/10/06/…
deau
Isso é muito simpático. Meu dia estará completo se houver um bom padrão para simular o funcionamento do AWK (com interlocução zero ou mínima). :-)
vai
Talvez você deva notar que esse idxserá o "número da linha" no arquivo virtual concatenando todas as entradas, em vez do número da linha de cada arquivo individual.
Alec Jacobson
Nota esta #!/usr/bin/env ruby -ilinha não funciona em Linux: stackoverflow.com/q/4303128/735926
bfontaine
43

Ruby fornece outra maneira de lidar com STDIN: o sinalizador -n. Ele trata todo o seu programa como estando dentro de um loop sobre STDIN (incluindo arquivos passados ​​como argumentos de linha de comando). Veja, por exemplo, o seguinte script de 1 linha:

#!/usr/bin/env ruby -n

#example.rb

puts "hello: #{$_}" #prepend 'hello:' to each line from STDIN

#these will all work:
# ./example.rb < input.txt
# cat input.txt | ./example.rb
# ./example.rb input.txt
Bill Caputo
fonte
8
O shebang de três participantes #!/usr/bin/env ruby -nnão funcionará, pois "ruby -n" será passado para / usr / bin / env como o único argumento. Veja esta resposta para mais detalhes. O script irá funcionar se correr com ruby -n script.rbexplicitamente.
Art
5
@jdizzle: Funciona no OSX, mas não no Linux - e esse é exatamente o problema: não é portátil .
Mklement0
32

Não tenho certeza do que você precisa, mas usaria algo como isto:

#!/usr/bin/env ruby

until ARGV.empty? do
  puts "From arguments: #{ARGV.shift}"
end

while a = gets
  puts "From stdin: #{a}"
end

Observe que, como o array ARGV está vazio antes do primeiro gets, Ruby não tentará interpretar o argumento como um arquivo de texto para leitura (comportamento herdado do Perl).

Se stdin estiver vazio ou não houver argumentos, nada será impresso.

Poucos casos de teste:

$ cat input.txt | ./myprog.rb
From stdin: line 1
From stdin: line 2

$ ./myprog.rb arg1 arg2 arg3
From arguments: arg1
From arguments: arg2
From arguments: arg3
hi!
From stdin: hi!
Damir Zekić
fonte
18

Algo assim talvez?

#/usr/bin/env ruby

if $stdin.tty?
  ARGV.each do |file|
    puts "do something with this file: #{file}"
  end
else
  $stdin.each_line do |line|
    puts "do something with this line: #{line}"
  end
end

Exemplo:

> cat input.txt | ./myprog.rb
do something with this line: this
do something with this line: is
do something with this line: a
do something with this line: test
> ./myprog.rb < input.txt 
do something with this line: this
do something with this line: is
do something with this line: a
do something with this line: test
> ./myprog.rb arg1 arg2 arg3
do something with this file: arg1
do something with this file: arg2
do something with this file: arg3
Magnus Holm
fonte
stdin não precisa ser texto. Notorius not text é, por exemplo, algum tipo de compactação / descompactação. (cada linha é meio que apenas se preparando para ascii). each_byte talvez?
7118 Jonke
12
while STDIN.gets
  puts $_
end

while ARGF.gets
  puts $_
end

Isso é inspirado no Perl:

while(<STDIN>){
  print "$_\n"
}
texasbruce
fonte
4
Inferno sim, por simplicidade e legibilidade! Oh não, espere, o que é esse '$ _'? Por favor, use o inglês no Stack Overflow!
3

Rápido e simples:

STDIN.gets.chomp == 'YES'

Jose Alban
fonte
1

Acrescentarei que, para usar ARGFcom os parâmetros, você precisa limpar ARGVantes de ligar ARGF.each. Isso ocorre porque ARGFtratará qualquer coisa emARGV como um nome de arquivo e lerá as linhas a partir daí primeiro.

Aqui está um exemplo de implementação 'tee':

File.open(ARGV[0], 'w') do |file|
  ARGV.clear

  ARGF.each do |line|
    puts line
    file.write(line)
  end
end
Richard Nienaber
fonte
1

Eu faço algo assim:

all_lines = ""
ARGV.each do |line|
  all_lines << line + "\n"
end
puts all_lines
wired00
fonte
0

Parece que a maioria das respostas está assumindo que os argumentos são nomes de arquivos que contêm conteúdo a ser atribuído ao stdin. Abaixo de tudo é tratado como apenas argumentos. Se STDIN for do TTY, ele será ignorado.

$ cat tstarg.rb

while a=(ARGV.shift or (!STDIN.tty? and STDIN.gets) )
  puts a
end

Argumentos ou stdin podem estar vazios ou ter dados.

$ cat numbers 
1
2
3
4
5
$ ./tstarg.rb a b c < numbers
a
b
c
1
2
3
4
5
Howard Barina
fonte