Ruby中STDIN的最佳实践是什么?

324
我希望处理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 上,shell 会为您管理。 - Mei
6
这通常被称为“无用的cat使用”,你会经常看到它。 - Steve Kehlet
22
@SteveKehlet,然而我认为更巧妙地称之为“虐猫”。 - OneChillDude
10个回答

424

以下是我在我的冷门 Ruby 收藏中发现的一些东西。

So, in Ruby, a simple no-bells implementation of the Unix command cat would be:

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

https://web.archive.org/web/20080725055721/http://www.oreillynet.com/ruby/blog/2007/04/trivial_scripting_with_ruby.html#comment-565558

ARGF 是处理输入的好帮手;它是一个虚拟文件,可以从指定文件或标准输入中获取所有输入。

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

Thank goodness we didn’t get the diamond operator in Ruby, but we did get ARGF as a replacement. Though obscure, it actually turns out to be useful. Consider this program, which prepends copyright headers in-place (thanks to another Perlism, -i) to every file mentioned on the command-line:

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

http://blog.nicksieger.com/articles/2007/10/06/obscure-and-ugly-perlisms-in-ruby

来源:


13
ARGF是个好选择。它是Ruby的内置方式,可以全面处理文件和标准输入(stdin)。 - Pistos
1
看到这个想起你了,关于那些积分:http://blog.nicksieger.com/articles/2007/10/06/obscure-and-ugly-perlisms-in-ruby - deau
非常好。如果有一个漂亮的模式来模拟AWK的工作方式(零或最小限度的交互),那么我的一天就会变得完美。 :-) - will
或许应该注意到,idx将是虚拟文件中连接所有输入的“行号”,而不是每个单独文件的行号。 - Alec Jacobson
1
请注意,此 #!/usr/bin/env ruby -i 行在 Linux 上不起作用:https://dev59.com/M2855IYBdhLWcg3wg0nC - bfontaine
在SO上已经讨论了很多关于shebang的问题,可以查看https://www.in-ulm.de/~mascheck/various/shebang/以获取其可用情况列表。 - Jonke

44

Ruby提供了另一种处理标准输入的方法:-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
三部分的 shebang #!/usr/bin/env ruby -n 无法工作,因为 "ruby -n" 会作为唯一的参数传递给 /usr/bin/env。更多细节请参见 此答案。如果显式地使用 ruby -n script.rb 运行脚本,则脚本将会被执行。 - artm
5
它可以在OSX上运行,但不能在Linux上运行——这正是问题所在:它不具备可移植性。 - mklement0

34

我不太确定你需要什么,但我会使用类似于这样的东西:

#!/usr/bin/env ruby

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

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

请注意,由于在第一次gets之前ARGV数组为空,因此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!

20

可能是这样的吗?

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

标准输入不需要是文本。例如,一些压缩/解压缩工具就不是文本。(each_line仅用于ASCII格式的准备)。也许可以使用each_byte? - Jonke

13
while STDIN.gets
  puts $_
end

while ARGF.gets
  puts $_
end

这是受 Perl 启发的:

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

6
好的,为了简单易懂起见,当然没问题!哦不,等等,那个 '$_' 是什么意思?请在 Stack Overflow 上使用 English - user1115652

4

简单易懂:

STDIN.gets.chomp == 'YES'


3
你也可以使用 STDIN.each_lineSTDIN.each_line.to_a 将其作为数组获取。
例如:
STDIN.each_line do |line|
  puts line
end

1
我会补充一下,为了使用带参数的ARGF,你需要在调用ARGF.each之前清空ARGV。这是因为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

0

看起来大多数答案都假定参数是包含要cat到stdin的内容的文件名。在下面的例子中,所有东西都被视为参数。如果STDIN来自TTY,则会被忽略。

$ cat tstarg.rb

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

参数或标准输入可以为空或包含数据。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接