Best Practices mit STDIN in Ruby?

307

Ich möchte mich mit der Befehlszeileneingabe in Ruby befassen:

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

Was ist der beste Weg, um es zu tun? Insbesondere möchte ich mich mit leerem STDIN befassen und hoffe auf eine elegante Lösung.

#!/usr/bin/env ruby

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

ARGV.each do |b|
    puts b
end
Griflet
quelle
5
Nur eine kleine Anmerkung: Die ersten beiden Befehlszeilen, die Sie eingeben, sind unter folgenden Gesichtspunkten genau gleich myprog.rb: Die input.txtDatei wird an stdin angehängt ; Die Shell verwaltet dies für Sie.
Mei
6
^^ Dies wird oft als "nutzloser Gebrauch von Katze" bezeichnet, das werden Sie oft sehen.
Steve Kehlet
18
@SteveKehlet jedoch glaube ich, dass es klüger als "Katzenmissbrauch" bezeichnet wird
OneChillDude

Antworten:

403

Im Folgenden sind einige Dinge aufgeführt, die ich in meiner Sammlung von obskurem Ruby gefunden habe.

In Ruby wäre eine einfache No-Bells-Implementierung des Unix-Befehls catalso:

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

ARGFist dein Freund, wenn es um Input geht; Es ist eine virtuelle Datei, die alle Eingaben von benannten Dateien oder alle von STDIN erhält.

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

Gott sei Dank haben wir den Diamantenbetreiber in Ruby nicht bekommen, aber wir haben ihn ARGFals Ersatz bekommen. Obwohl dunkel, erweist es sich tatsächlich als nützlich. Betrachten Sie dieses Programm, bei dem Copyright-Header (dank eines anderen Perlismus -i) jeder in der Befehlszeile genannten Datei vorangestellt werden:

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

Gutschrift an:

Jonke
quelle
12
ARGF ist der richtige Weg. Es ist Rubys eingebauter Weg, um Dateien und Stdin rundum zu handhaben.
Pistos
1
(saw dies und dachte an dich) wieder diese Gutschriften: blog.nicksieger.com/articles/2007/10/06/...
deau
Das ist sehr nett. Mein Tag wird beendet sein, wenn es ein schönes Muster gibt, um die Funktionsweise von AWK zu simulieren (mit null oder minimaler Interlokation). :-)
wird
Vielleicht sollte beachtet werden, dass idxdies die "Zeilennummer" in der virtuellen Datei ist, die alle Eingaben verkettet, und nicht die Zeilennummer für jede einzelne Datei.
Alec Jacobson
Beachten Sie, dass diese #!/usr/bin/env ruby -iZeile unter Linux nicht funktioniert: stackoverflow.com/q/4303128/735926
bfontaine
43

Ruby bietet eine andere Möglichkeit, mit STDIN umzugehen: Das Flag -n. Es behandelt Ihr gesamtes Programm als eine Schleife über STDIN (einschließlich Dateien, die als Befehlszeilenargumente übergeben werden). Siehe zB das folgende 1-zeilige Skript:

#!/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
quelle
8
Der Dreiteiler-Shebang #!/usr/bin/env ruby -nfunktioniert nicht, da "ruby -n" als einziges Argument an / usr / bin / env übergeben wird. Siehe diese Antwort für weitere Details. Das Skript wird funktionieren , wenn mit laufen ruby -n script.rbexplizit.
Artm
5
@jdizzle: Es funktioniert unter OSX, aber nicht unter Linux - und genau das ist das Problem: Es ist nicht portabel .
mklement0
32

Ich bin mir nicht ganz sicher, was Sie brauchen, aber ich würde so etwas verwenden:

#!/usr/bin/env ruby

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

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

Beachten Sie, dass getsRuby , da das ARGV-Array zuvor leer war , nicht versucht, das Argument als Textdatei zu interpretieren, aus der gelesen werden soll (von Perl geerbtes Verhalten).

Wenn stdin leer ist oder keine Argumente vorhanden sind, wird nichts gedruckt.

Einige Testfälle:

$ 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ć
quelle
18

So etwas vielleicht?

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

Beispiel:

> 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
quelle
stdin muss kein Text sein. Notorius nicht Text ist zum Beispiel eine Art Komprimieren / Dekomprimieren. (each_line bereitet sich nur auf ascii vor). each_byte vielleicht?
Jonke
12
while STDIN.gets
  puts $_
end

while ARGF.gets
  puts $_
end

Dies ist inspiriert von Perl:

while(<STDIN>){
  print "$_\n"
}
texasbruce
quelle
4
Zur Hölle, zur Vereinfachung und Lesbarkeit! Oh nein, warte, was ist das '$ _'? Bitte verwenden Sie Englisch für Stapelüberlauf!
3

Schnell und einfach:

STDIN.gets.chomp == 'YES'

Jose Alban
quelle
1

Ich werde das hinzufügen, um ARGFmit Parametern zu verwenden, müssen Sie ARGVvor dem Aufruf löschen ARGF.each. Dies liegt daran ARGF, dass alles in ARGVals Dateiname behandelt wird und zuerst die Zeilen von dort gelesen werden.

Hier ist ein Beispiel für eine Tee-Implementierung:

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

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

Ich mache so etwas:

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

Es scheint, dass die meisten Antworten davon ausgehen, dass die Argumente Dateinamen sind, die Inhalte enthalten, die dem Standard zugeordnet werden sollen. Im Folgenden wird alles nur als Argument behandelt. Wenn STDIN vom TTY stammt, wird es ignoriert.

$ cat tstarg.rb

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

Entweder Argumente oder stdin können leer sein oder Daten enthalten.

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