324

I want to deal with the command line input in Ruby:

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

What is the best way to do it? In particular I want to deal with blank STDIN, and I hope for an elegant solution.

#!/usr/bin/env ruby

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

ARGV.each do |b|
    puts b
end
griflet
  • 3,257
  • 3
  • 17
  • 4
  • 5
    Just a minor note: the first two command lines you give are exactly the same from the viewpoint of `myprog.rb`: the `input.txt` file is attached to _stdin_; the shell manages this for you. – Mei Jul 12 '11 at 15:52
  • 6
    ^^ this is often referred to as "useless use of cat", you'll see that a lot. – Steve Kehlet Mar 09 '12 at 00:22
  • 22
    @SteveKehlet however I believe it is more cleverly referred to as "cat abuse" – OneChillDude Aug 13 '14 at 19:26

10 Answers10

423

Following are some things I found in my collection of obscure 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 is your friend when it comes to input; it is a virtual file that gets all input from named files or all from 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

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

Credit to:

Jonke
  • 6,525
  • 2
  • 25
  • 40
  • 13
    ARGF is the way to go. It's Ruby's built in way to handle files and stdin in an all-around fashion. – Pistos Nov 08 '08 at 17:08
  • 1
    (saw this and thought of you) re those credits: http://blog.nicksieger.com/articles/2007/10/06/obscure-and-ugly-perlisms-in-ruby – deau Dec 03 '09 at 00:44
  • That's very nice. My day will be complete if there's a nice pattern to simulate the way AWK works (with zero or minimal interlocution). :-) – will Jan 27 '11 at 23:52
  • Perhaps should note that `idx` will be the "line number" in the virtual file concatenating all of the inputs, rather than the line number for each individual file. – Alec Jacobson Nov 14 '16 at 14:06
  • 1
    Note this `#!/usr/bin/env ruby -i` line doesn’t work on Linux: https://stackoverflow.com/q/4303128/735926 – bfontaine Apr 13 '18 at 10:31
  • The shebang has been discussed a lot on SO, https://www.in-ulm.de/~mascheck/various/shebang/ for a list of when it can be used. – Jonke Apr 20 '18 at 13:23
44

Ruby provides another way to handle STDIN: The -n flag. It treats your entire program as being inside a loop over STDIN, (including files passed as command line args). See e.g. the following 1-line script:

#!/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
Bill Caputo
  • 441
  • 4
  • 2
  • 8
    The three-parter shebang `#!/usr/bin/env ruby -n` will not work, as "ruby -n" will be passed to /usr/bin/env as the only argument. [See this answer](http://stackoverflow.com/a/4304187) for more details. The script **will** work if run with `ruby -n script.rb` explicitly. – artm Sep 01 '14 at 11:45
  • 5
    @jdizzle: It works on OSX, but not on Linux - and that's exactly the problem: it's not _portable_. – mklement0 May 07 '15 at 20:51
34

I am not quite sure what you need, but I would use something like this:

#!/usr/bin/env ruby

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

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

Note that because ARGV array is empty before first gets, Ruby won't try to interpret argument as text file from which to read (behaviour inherited from Perl).

If stdin is empty or there is no arguments, nothing is printed.

Few test cases:

$ 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!
Damir Zekić
  • 15,630
  • 2
  • 34
  • 35
20

Something like this perhaps?

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

Example:

> 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
Magnus Holm
  • 1,221
  • 7
  • 9
  • stdin don't need to be text. Notorius not text is for example some sort of compress/uncompress. (each_line is kind of only preparing for ascii). each_byte maybe ? – Jonke Nov 07 '08 at 22:17
13
while STDIN.gets
  puts $_
end

while ARGF.gets
  puts $_
end

This is inspired by Perl:

while(<STDIN>){
  print "$_\n"
}
SwiftMango
  • 15,092
  • 13
  • 71
  • 136
  • 6
    Hell yeah, for simplicity and readablility! Oh no, wait, what is that '$_'? Please use [English](http://www.ruby-doc.org/stdlib-2.0/libdoc/English/rdoc/English.html) on Stack Overflow! –  Dec 27 '14 at 05:25
4

Quick and simple:

STDIN.gets.chomp == 'YES'

Jose Alban
  • 7,286
  • 2
  • 34
  • 19
3

You can also use STDIN.each_line, and STDIN.each_line.to_a to get it as an array.

e.g.

STDIN.each_line do |line|
  puts line
end
Dorian
  • 7,749
  • 4
  • 38
  • 57
1

I'll add that in order to use ARGF with parameters, you need to clear ARGV before calling ARGF.each. This is because ARGF will treat anything in ARGV as a filename and read lines from there first.

Here's an example 'tee' implementation:

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

  ARGF.each do |line|
    puts line
    file.write(line)
  end
end
Richard Nienaber
  • 10,324
  • 6
  • 55
  • 66
1

I do something like this :

all_lines = ""
ARGV.each do |line|
  all_lines << line + "\n"
end
puts all_lines
wired00
  • 13,930
  • 7
  • 70
  • 73
0

It seems most answers are assuming the arguments are filenames containing content to be cat'd to the stdin. Below everything is treated as just arguments. If STDIN is from the TTY, then it is ignored.

$ cat tstarg.rb

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

Either arguments or stdin can be empty or have data.

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