Кращі практики щодо STDIN в Ruby?


307

Я хочу розібратися з введенням командного рядка в Ruby:

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

Який найкращий спосіб це зробити? Зокрема, я хочу мати справу з порожнім STDIN, і я сподіваюся на елегантне рішення.

#!/usr/bin/env ruby

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

ARGV.each do |b|
    puts b
end

5
Лише незначне зауваження: перші два командні рядки, які ви надаєте, абсолютно однакові з точки зору myprog.rb: input.txtфайл додається до stdin ; оболонка управляє цим для вас.
Май

6
^^ це часто називають "марним використанням кота", ви побачите це багато.
Стів Келет

18
@SteveKehlet, проте я вважаю, що це більш спритно називається "зловживання котами"
OneChillDude

Відповіді:


403

Нижче наведено кілька речей, які я знайшов у своїй колекції незрозумілого Рубі.

Отже, в Ruby проста реалізація команди, catяка не використовує дзвіночків, буде:

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

ARGFваш друг, коли справа стосується введення інформації; це віртуальний файл, який отримує весь вхід із названих файлів або весь з 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

Слава богу, що ми не отримали алмазного оператора в Рубі, але отримали ARGFяк заміну. Хоч і незрозуміло, воно насправді виявляється корисним. Розглянемо цю програму, яка випереджає заголовки авторських прав на місці (завдяки іншому Perlism -i) для кожного файлу, згаданого в командному рядку:

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

Кредит:


12
ARGF - це шлях. Це Ruby створена таким чином, щоб обробляти файли та stdin у всебічному режимі.
Пістос

1
(побачив це і подумав про вас) знову ці кредити: blog.nicksieger.com/articles/2007/10/06/…
деа

Це дуже приємно. Мій день буде завершеним, якщо є приємний зразок, який змоделює спосіб роботи AWK (з нульовим або мінімальним розміщенням). :-)
буде

Можливо, слід зазначити, що idxбуде "номер рядка" у віртуальному файлі, що об'єднує всі входи, а не номер рядка для кожного окремого файлу.
Алек Джейкобсон

Зверніть увагу , ця #!/usr/bin/env ruby -iлінія не працює на Linux: stackoverflow.com/q/4303128/735926
bfontaine

43

Ruby - це ще один спосіб поводження з STDIN: прапор -n. Він розглядає всю вашу програму як внутрішню петлю над STDIN (включаючи файли, передані як аргументи командного рядка). Дивіться, наприклад, такий 1-рядовий сценарій:

#!/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
Трипартійний шебанг #!/usr/bin/env ruby -nне працюватиме, оскільки "ruby -n" буде переданий / usr / bin / env як єдиний аргумент. Дивіться цю відповідь для отримання більш детальної інформації. Сценарій буде працювати, якщо ruby -n script.rbявно запускається .
artm

5
@jdizzle: Він працює на OSX, але не на Linux - і саме в цьому проблема: він не портативний .
mklement0

32

Я не зовсім впевнений, що вам потрібно, але я б скористався чимось таким:

#!/usr/bin/env ruby

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

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

Зауважте, що оскільки масив ARGV порожній раніше gets, Ruby не намагатиметься інтерпретувати аргумент як текстовий файл, з якого слід читати (поведінка, успадкована від Perl).

Якщо stdin порожній або немає аргументів, нічого не друкується.

Кілька тестових випадків:

$ 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

Щось подібне, можливо?

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

Приклад:

> 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 не потрібно бути текстом. Notorius not text - це, наприклад, якесь стиснення / віджаття. (every_line - це лише форма, що готується до ascii). кожен_байт, можливо?
Jonke

12
while STDIN.gets
  puts $_
end

while ARGF.gets
  puts $_
end

На це надихає Perl:

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

4
Пекло так, для простоти і читабельності! О ні, чекайте, що це "$ _"? Будь ласка, використовуйте англійську мову на стеку Overflow!


1

Додамо, що для використання ARGFз параметрами вам потрібно зробити очищення ARGVперед викликом ARGF.each. Це тому ARGF, що будь-що розглядатиметься ARGVяк ім'я файлу та спочатку буде читати рядки звідти.

Ось приклад реалізації "трійника":

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

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


0

Здається, більшість відповідей припускають, що аргументи - це назви файлів, що містять вміст, який повинен бути cat'd to stdin. Нижче все трактується як лише аргументи. Якщо STDIN від TTY, він ігнорується.

$ cat tstarg.rb

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

Або аргументи, або stdin можуть бути порожніми або мати дані.

$ cat numbers 
1
2
3
4
5
$ ./tstarg.rb a b c < numbers
a
b
c
1
2
3
4
5
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.