Meilleures pratiques avec STDIN en Ruby?

307

Je veux traiter l'entrée de ligne de commande dans Ruby:

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

Quelle est la meilleure façon de procéder? En particulier, je veux traiter du STDIN vierge et j'espère une solution élégante.

#!/usr/bin/env ruby

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

ARGV.each do |b|
    puts b
end
griflet
la source
5
Juste une note mineure: les deux premières lignes de commande que vous donnez sont exactement les mêmes du point de vue de myprog.rb: le input.txtfichier est attaché à stdin ; le shell gère cela pour vous.
Mei
6
^^ ceci est souvent appelé "utilisation inutile du chat", vous le verrez souvent.
Steve Kehlet
18
@SteveKehlet mais je pense qu'il est plus intelligemment appelé "abus de chat"
OneChillDude

Réponses:

403

Voici quelques choses que j'ai trouvées dans ma collection de Ruby obscure.

Ainsi, dans Ruby, une implémentation simple et sans cloches de la commande Unix catserait:

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

ARGFest votre ami en matière de saisie; c'est un fichier virtuel qui obtient toutes les entrées des fichiers nommés ou toutes 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

Dieu merci, nous n'avons pas obtenu l'exploitant de diamants à Ruby, mais nous l'avons obtenu ARGFen remplacement. Bien qu'obscure, elle s'avère en fait utile. Considérez ce programme, qui ajoute les en-têtes de copyright en place (grâce à un autre Perlism -i) à chaque fichier mentionné sur la ligne de commande:

#!/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édit à:

Jonke
la source
12
L'ARGF est la voie à suivre. Il s'agit de la méthode intégrée de Ruby pour gérer les fichiers et stdin de manière globale.
Pistos
1
(vu et pensé à vous) concernant
deau
C'est très gentil. Ma journée sera complète s'il y a un motif sympa pour simuler le fonctionnement d'AWK (avec une interlocution nulle ou minimale). :-)
le
Il convient peut-être de noter que ce idxsera le "numéro de ligne" dans le fichier virtuel concaténant toutes les entrées, plutôt que le numéro de ligne pour chaque fichier individuel.
Alec Jacobson
Notez ceci #!/usr/bin/env ruby -i ligne ne fonctionne pas sous Linux: stackoverflow.com/q/4303128/735926
bfontaine
43

Ruby fournit une autre façon de gérer STDIN: l'indicateur -n. Il traite l'intégralité de votre programme comme étant à l'intérieur d'une boucle sur STDIN (y compris les fichiers passés comme arguments de ligne de commande). Voir par exemple le script 1 ligne suivant:

#!/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
la source
8
Le shebang en trois parties #!/usr/bin/env ruby -nne fonctionnera pas, car "ruby -n" sera passé à / usr / bin / env comme seul argument. Voir cette réponse pour plus de détails. Le script va fonctionner si elle est exécutée avec ruby -n script.rbexplicitement.
artm
5
@jdizzle: Cela fonctionne sur OSX, mais pas sur Linux - et c'est exactement le problème: ce n'est pas portable .
mklement0
32

Je ne sais pas exactement ce dont vous avez besoin, mais j'utiliserais quelque chose comme ceci:

#!/usr/bin/env ruby

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

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

Notez que parce que le tableau ARGV est vide avant le premier gets, Ruby n'essaiera pas d'interpréter l'argument comme un fichier texte à partir duquel lire (comportement hérité de Perl).

Si stdin est vide ou s'il n'y a pas d'arguments, rien n'est imprimé.

Peu de cas de test:

$ 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ć
la source
18

Quelque chose comme ça peut-être?

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

Exemple:

> 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
la source
stdin n'a pas besoin d'être du texte. Notorius not text est par exemple une sorte de compression / décompression. (each_line est une sorte de préparation à l'ascii). each_byte peut-être?
Jonke
12
while STDIN.gets
  puts $_
end

while ARGF.gets
  puts $_
end

Ceci est inspiré par Perl:

while(<STDIN>){
  print "$_\n"
}
texasbruce
la source
4
Enfer ouais, pour la simplicité et la lisibilité! Oh non, attendez, qu'est-ce que c'est '$ _'? Veuillez utiliser l' anglais sur Stack Overflow!
3

Rapide et simple:

STDIN.gets.chomp == 'YES'

Jose Alban
la source
1

J'ajouterai que pour utiliser ARGFavec des paramètres, vous devez effacer ARGVavant d'appeler ARGF.each. C'est parce ARGFque traitera toutARGV comme un nom de fichier et lira les lignes à partir de là en premier.

Voici un exemple d'implémentation «tee»:

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

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

Je fais quelque chose comme ça:

all_lines = ""
ARGV.each do |line|
  all_lines << line + "\n"
end
puts all_lines
filaire00
la source
0

Il semble que la plupart des réponses supposent que les arguments sont des noms de fichiers contenant du contenu à envoyer à la stdin. En dessous, tout est traité comme de simples arguments. Si STDIN provient du TTY, il est ignoré.

$ cat tstarg.rb

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

Les arguments ou stdin peuvent être vides ou contenir des données.

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