In Progress
Unit 1, Lesson 1
In Progress

ARGF

Video transcript & code

So we're putting the finishing touches on our new ultra-secure encryption algorithm, and we'd like to make it available as a command-line utility.

KEYS   = ('a'..'z').to_a
VALUES = KEYS.rotate(13)
CYPHER = Hash[KEYS.zip(VALUES)]

def sekrit(text)
  text.downcase.chars.map{ |char|
    CYPHER[char] || char
  }.join
end

sekrit("hello, world")          
# => "uryyb, jbeyq"

For a command-line text filtering utility to be a good UNIX citizen, it should accept input in several different ways. It should be possible to pipe input in via STDIN:

$ sekrit < my_secret_stuff.txt

It should also be possible to supply a filename as an argument:

$ sekrit my_secret_stuff.txt

Or even multiple filenames as arguments:

$ sekrit file1.txt file2.txt

Now, I could show you the hard way to handle these differing ways of calling the utility, but that's no fun. Instead, let's just skip straight to the easy way. The easy way involves reading from the ARGF constant.

KEYS   = ('a'..'z').to_a
VALUES = KEYS.rotate(13)
CYPHER = Hash[KEYS.zip(VALUES)]

def sekrit(text)
  text.downcase.chars.map{ |char|
    CYPHER[char] || char
  }.join
end

ARGF.lines do |line|
  print sekrit(line)
end

What is ARGF? It's a special kind of object which behaves like a read-only IO object. When you read from the ARGF object, Ruby first looks through the ARGV array. If it finds any arguments there, it treats them as filenames and tries to open them. Reading from ARGF then yields the content of each of those files in turn, as if they had all been concatenated into a single file.

If Ruby finds no arguments in ARGV, it instead makes ARGF behave as if it is another name for STDIN. The upshot is that with these few lines of code, all of our desired calling styles work.

We can pipe input into our program:

$ ruby sekrit.rb < my_secret_stuff.txt   
lbh'er zl snibevgr fhofpevore!

We can provide the input as a filename argument:

$ ruby sekrit.rb my_secret_stuff.txt 
lbh'er zl snibevgr fhofpevore!

And we can provide more than one filename argument:

f$ ruby sekrit.rb file1.txt file2.txt 
frperg fghss sebz svyr bar
guvf vf nyfb sebz svyr bar
frperg fghss sebz svyr gjb
guvf vf nyfb sebz svyr gjb

There's a lot more to ARGF. For example, ARGF can tell you which file argument it is currently pulling from. Just to demonstrate this, we'll modify our program to prefix each line with the name of the file it comes from:

KEYS   = ('a'..'z').to_a
VALUES = KEYS.rotate(13)
CYPHER = Hash[KEYS.zip(VALUES)]

def sekrit(text)
  text.downcase.chars.map{ |char|
    CYPHER[char] || char
  }.join
end

ARGF.lines do |line|
  print "#{ARGF.path}: #{sekrit(line)}"
end
$ ruby sekrit2.rb file1.txt file2.txt    
file1.txt: frperg fghss sebz svyr bar
file1.txt: guvf vf nyfb sebz svyr bar
file2.txt: frperg fghss sebz svyr gjb
file2.txt: guvf vf nyfb sebz svyr gjb

ARGF can be a real timesaver for anyone writing command-line utilities. We've scratched the surface of its capabilities in this episode; to get a fuller picture, check out the online Ruby documentation.

That's it for today. Happy hacking!

Responses