Najlepsze praktyki ze STDIN w Ruby?

307

Chcę poradzić sobie z danymi wiersza poleceń w Ruby:

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

Jak najlepiej to zrobić? W szczególności chcę poradzić sobie z czystym STDIN i mam nadzieję na eleganckie rozwiązanie.

#!/usr/bin/env ruby

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

ARGV.each do |b|
    puts b
end
żaba
źródło
5
Drobna uwaga: pierwsze dwa wiersze poleceń, które podajesz, są dokładnie takie same z punktu widzenia myprog.rb: input.txtplik jest dołączony do standardowego wejścia ; powłoka zarządza tym za ciebie.
Mei
6
^^ często jest to określane jako „bezużyteczne korzystanie z kota”, często to zobaczysz.
Steve Kehlet
18
@SteveKehlet jednak uważam, że bardziej sprytnie jest to określane jako „nadużycie kota”
OneChillDude

Odpowiedzi:

403

Oto kilka rzeczy, które znalazłem w mojej kolekcji niejasnego Ruby.

Tak więc w Rubim prostą implementacją komendy uniksowej catbyłoby:

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

ARGFjest twoim przyjacielem, jeśli chodzi o wkład; jest to plik wirtualny, który pobiera wszystkie dane wejściowe z nazwanych plików lub wszystkie z 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

Dzięki Bogu nie dostaliśmy operatora diamentów w Ruby, ale dostaliśmy go ARGFjako zamiennik. Choć niejasne, w rzeczywistości okazuje się przydatne. Rozważ ten program, który umieszcza nagłówki praw autorskich w miejscu (dzięki innemu Perlizmowi -i) do każdego pliku wymienionego w wierszu poleceń:

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

Kredyt dla:

Jonke
źródło
12
ARGF jest właściwą drogą. Jest on zbudowany w taki sposób, aby obsługiwać pliki i działać w sposób uniwersalny.
Pistos
1
(widziałem to i pomyślałem o tobie) re tych punktów: blog.nicksieger.com/articles/2007/10/06/...
Deau
To bardzo miłe. Mój dzień będzie skończony, jeśli będzie fajny wzór do symulacji działania AWK (z zerową lub minimalną rozmową). :-)
będzie
Być może należy zauważyć, że idxbędzie to „numer wiersza” w pliku wirtualnym łączącym wszystkie dane wejściowe, a nie numer wiersza dla każdego pojedynczego pliku.
Alec Jacobson
Uwaga: ta #!/usr/bin/env ruby -ilinia nie działa w systemie Linux: stackoverflow.com/q/4303128/735926
bfontaine
43

Ruby zapewnia inny sposób obsługi STDIN: flaga -n. Traktuje cały program jako znajdujący się w pętli nad STDIN (w tym pliki przekazywane jako argumenty wiersza poleceń). Zobacz np. Następujący 1-wierszowy skrypt:

#!/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
źródło
8
Trzyczęściowy shebang #!/usr/bin/env ruby -nnie będzie działał, ponieważ „ruby -n” zostanie przekazany do / usr / bin / env jako jedyny argument. Zobacz tę odpowiedź, aby uzyskać więcej informacji. Skrypt będzie działał, jeśli zostanie uruchomiony ruby -n script.rbjawnie.
artm
5
@jdizzle: Działa na OSX, ale nie na Linuksie - i to jest dokładnie problem: nie jest przenośny .
mklement0
32

Nie jestem pewien, czego potrzebujesz, ale użyłbym czegoś takiego:

#!/usr/bin/env ruby

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

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

Zauważ, że ponieważ tablica ARGV jest pusta przed pierwszym gets, Ruby nie będzie próbowała interpretować argumentu jako pliku tekstowego, z którego można czytać (zachowanie odziedziczone po Perlu).

Jeśli standardowe wejście jest puste lub nie ma żadnych argumentów, nic nie jest drukowane.

Kilka przypadków testowych:

$ 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ć
źródło
18

Może coś takiego?

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

Przykład:

> 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
źródło
standardowe nie musi być tekstem. Notorius not text to na przykład rodzaj kompresji / dekompresji. (each_line jest jakby tylko przygotowaniem do ascii). Może każdy_bajt?
Jonke,
12
while STDIN.gets
  puts $_
end

while ARGF.gets
  puts $_
end

Inspiruje się to Perl:

while(<STDIN>){
  print "$_\n"
}
texasbruce
źródło
4
Do diabła tak, dla prostoty i czytelności! O nie, czekaj, co to jest „$ _”? Proszę używać angielskiego na przepełnienie stosu!
3

Szybki i prosty:

STDIN.gets.chomp == 'YES'

Jose Alban
źródło
1

Dodam, że aby użyć ARGFparametrów, musisz wyczyścić ARGVprzed wywołaniem ARGF.each. Jest tak, ponieważ ARGFtraktuje wszystko ARGVjako nazwę pliku i najpierw odczytuje wiersze.

Oto przykładowa implementacja „tee”:

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

  ARGF.each do |line|
    puts line
    file.write(line)
  end
end
Richard Nienaber
źródło
1

Robię coś takiego:

all_lines = ""
ARGV.each do |line|
  all_lines << line + "\n"
end
puts all_lines
przewodowy00
źródło
0

Wydaje się, że większość odpowiedzi zakłada, że ​​argumentami są nazwy plików zawierające treść, która ma zostać przypisana do standardowego wejścia. Poniżej wszystko jest traktowane tylko jako argumenty. Jeśli STDIN pochodzi z TTY, to jest ignorowane.

$ cat tstarg.rb

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

Argumenty lub standardowe wejście może być puste lub zawierać dane.

$ cat numbers 
1
2
3
4
5
$ ./tstarg.rb a b c < numbers
a
b
c
1
2
3
4
5
Howard Barina
źródło