在Ruby中使用STDIN的最佳做法?


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 2014年

Answers:


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中获得Diamond运算符,但确实获得了它ARGF作为替代。尽管晦涩难懂,但实际上却很有用。考虑一下该程序,该程序-i在命令行上提到的每个文件都预先添加了版权标头(这要归功于另一个Perlism ):

#!/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。
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上的循环内(包括作为命令行args传递的文件)。请参见例如以下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 2014年

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数组在first之前为空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

标准输入不需要是文本。Notorius not text例如是某种压缩/解压缩。(each_line只是为ascii做准备)。每个字节可能吗?
Jonke

12
while STDIN.gets
  puts $_
end

while ARGF.gets
  puts $_
end

这是受Perl启发的:

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

4
是的,为了简单和易读性!哦,不,等等,'$ _'是什么?请在堆栈溢出时使用英语


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

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.