Лучшие практики с 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
griflet
источник
5
Небольшое замечание: первые две командные строки, которые вы указываете, абсолютно одинаковы с точки зрения myprog.rb: input.txtфайл прикреплен к stdin ; оболочка управляет этим для вас.
Мей
6
^^ это часто называют «бесполезным использованием кошки», вы увидите это очень часто.
Стив Келет
18
@SteveKehlet, однако, я считаю, что это более умно называют "жестокое обращение с кошками"
OneChillDude

Ответы:

403

Ниже приведены некоторые вещи, которые я нашел в своей коллекции малоизвестного Ruby.

Итак, в Ruby простая реализация команды Unix без звонков 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

Слава Богу, мы не получили алмазного оператора в Ruby, но мы получили 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.
#++

Кредит для:

Jonke
источник
12
ARGF это путь. Это Ruby, созданный таким образом, чтобы обрабатывать файлы и stdin всесторонним образом.
Pistos
1
(видел это и подумал о вас) повторно эти кредиты: blog.nicksieger.com/articles/2007/10/06/...
Deau
Это очень мило. Мой день будет полным, если есть хороший шаблон для имитации работы 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).

Если стандартный ввод пуст или аргументов нет, ничего не печатается.

Несколько тестовых случаев:

$ 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, а не текст, например, является своего рода сжатие / распаковка. (each_line вроде только готовится к ascii). каждый_байт может быть?
Jonke
12
while STDIN.gets
  puts $_
end

while ARGF.gets
  puts $_
end

Это вдохновлено Perl:

while(<STDIN>){
  print "$_\n"
}
texasbruce
источник
4
Да, черт возьми, за простоту и читабельность! О нет, подожди, что это за $ _? Пожалуйста, используйте английский на Stack Overflow!
3

Быстро и просто:

STDIN.gets.chomp == 'YES'

Хосе Албан
источник
1

Я добавлю это, чтобы использовать ARGFс параметрами, вам нужно очистить ARGVперед вызовом ARGF.each. Это потому, что ARGFбудет обрабатывать что-либо ARGVкак имя файла и сначала читать строки оттуда.

Вот пример реализации 'tee':

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

  ARGF.each do |line|
    puts line
    file.write(line)
  end
end
Ричард Нинабер
источник
1

Я делаю что-то вроде этого:

all_lines = ""
ARGV.each do |line|
  all_lines << line + "\n"
end
puts all_lines
wired00
источник
0

Кажется, что большинство ответов предполагают, что аргументы являются именами файлов, содержащими контент, который нужно передать на стандартный ввод. Ниже все трактуется как аргументы. Если 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
Говард Барина
источник