¿Mejores prácticas con STDIN en Ruby?


307

Quiero tratar con la entrada de línea de comando en Ruby:

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

¿Cuál es la mejor manera de hacerlo? En particular, quiero tratar con STDIN en blanco, y espero una solución elegante.

#!/usr/bin/env ruby

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

ARGV.each do |b|
    puts b
end

55
Solo una nota menor: las dos primeras líneas de comando que da son exactamente las mismas desde el punto de vista de myprog.rb: el input.txtarchivo está adjunto a stdin ; el caparazón maneja esto por ti.
Mei

66
^^ esto a menudo se conoce como "uso inútil del gato", lo verás mucho.
Steve Kehlet

18
@SteveKehlet, sin embargo, creo que se conoce más inteligentemente como "abuso de gato"
OneChillDude

Respuestas:


403

A continuación hay algunas cosas que encontré en mi colección de Ruby oscuro.

Entonces, en Ruby, una implementación simple sin campanas del comando Unix catsería:

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

ARGFes tu amigo cuando se trata de entrada; Es un archivo virtual que obtiene todas las entradas de los archivos con nombre o todo 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

Gracias a Dios, no obtuvimos el operador de diamantes en Ruby, pero lo conseguimos ARGFcomo reemplazo. Aunque oscuro, en realidad resulta ser útil. Considere este programa, que antecede los encabezados de copyright en el lugar (gracias a otro Perlism -i) a cada archivo mencionado en la línea de comandos:

#!/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 a:


12
ARGF es el camino a seguir. Está construido en Ruby para manejar archivos y stdin de una manera integral.
Pistos

1
(vi esto y pensé en ti) re esos créditos: blog.nicksieger.com/articles/2007/10/06/…
deau

Eso es muy lindo. Mi día estará completo si hay un patrón agradable para simular la forma en que funciona AWK (con cero o mínima interlocución). :-)
será

Quizás debería tener en cuenta que idxserá el "número de línea" en el archivo virtual que concatena todas las entradas, en lugar del número de línea para cada archivo individual.
Alec Jacobson

Tenga en cuenta que esta #!/usr/bin/env ruby -ilínea no funciona en Linux: stackoverflow.com/q/4303128/735926
bfontaine

43

Ruby proporciona otra forma de manejar STDIN: la bandera -n. Trata todo su programa como si estuviera dentro de un bucle sobre STDIN (incluidos los archivos pasados ​​como argumentos de línea de comando). Consulte, por ejemplo, el siguiente script de 1 línea:

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

8
El shebang de tres partes #!/usr/bin/env ruby -nno funcionará, ya que "ruby -n" se pasará a / usr / bin / env como único argumento. Vea esta respuesta para más detalles. El guión será trabajar si se ejecuta con ruby -n script.rbforma explícita.
artm

55
@jdizzle: Funciona en OSX, pero no en Linux, y ese es exactamente el problema: no es portátil .
mklement0

32

No estoy muy seguro de lo que necesita, pero usaría algo como esto:

#!/usr/bin/env ruby

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

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

Tenga en cuenta que debido a que la matriz ARGV está vacía antes gets, Ruby no intentará interpretar el argumento como un archivo de texto del cual leer (comportamiento heredado de Perl).

Si stdin está vacío o no hay argumentos, no se imprime nada.

Pocos casos de prueba:

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

18

¿Algo así quizás?

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

Ejemplo:

> 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

stdin no necesita ser texto. Notorius no texto es, por ejemplo, algún tipo de comprimir / descomprimir. (each_line solo se está preparando para ascii). cada_byte tal vez?
Jonke

12
while STDIN.gets
  puts $_
end

while ARGF.gets
  puts $_
end

Esto está inspirado en Perl:

while(<STDIN>){
  print "$_\n"
}

44
¡Claro que sí, por simplicidad y legibilidad! Oh no, espera, ¿qué es ese '$ _'? ¡Utilice inglés en Stack Overflow!


1

Agregaré eso para usar ARGFcon parámetros, debe borrar ARGVantes de llamar ARGF.each. Esto se debe a ARGFque tratará cualquier cosa ARGVcomo un nombre de archivo y leerá líneas desde allí primero.

Aquí hay un ejemplo de implementación 'tee':

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

  ARGF.each do |line|
    puts line
    file.write(line)
  end
end

1

Hago algo como esto:

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

0

Parece que la mayoría de las respuestas están asumiendo que los argumentos son nombres de archivos que contienen contenido para ser incluido en el stdin. Debajo de todo se trata solo como argumentos. Si STDIN es del TTY, entonces se ignora.

$ cat tstarg.rb

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

Los argumentos o stdin pueden estar vacíos o tener datos.

$ cat numbers 
1
2
3
4
5
$ ./tstarg.rb a b c < numbers
a
b
c
1
2
3
4
5
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.