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

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 à:


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

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!

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

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"
}

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!


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

1

Je fais quelque chose comme ça:

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

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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.