James Edward Gray II ✤ I am a regular panelist on The Ruby Rogues podcast ✤ I was a Rubyist before Rails shipped and made us popular ✤ I have written a lot of code and documentation for Ruby, including the standard CSV library
I Suspect You’ve Been Learning… ✤ How clean code helps you manage complexity ✤ How well written code eases communication with others ✤ How to identify and improve problem spots in your code
The Wheel of Time ✤ A fantasy series from my childhood ✤ The female magic users have a strict training process built up over 100’s of years of magic use ✤ The male magic users, brand new to magic, try to catch up by using it for everything: cooking, chores, etc.
The -e (execute) Switch $ ruby -e 'p 21 * 2' 42 $ ruby -e 'p "some data"' "some data" $ ruby -e 'var = 42; p var' 42 ✤ Run some Ruby without a script ✤ Hints: ✤ Use ‘…’ for shell quoting and “…” for Ruby quoting (to minimize escaping) ✤ Use ;’s to get multiple lines
The -e (execute) Switch $ ruby -e 'p 21 * 2' 42 $ ruby -e 'p "some data"' "some data" $ ruby -e 'var = 42; p var' 42 ✤ Run some Ruby without a script ✤ Hints: ✤ Use ‘…’ for shell quoting and “…” for Ruby quoting (to minimize escaping) ✤ Use ;’s to get multiple lines
The -e (execute) Switch $ ruby -e 'p 21 * 2' 42 $ ruby -e 'p "some data"' "some data" $ ruby -e 'var = 42; p var' 42 ✤ Run some Ruby without a script ✤ Hints: ✤ Use ‘…’ for shell quoting and “…” for Ruby quoting (to minimize escaping) ✤ Use ;’s to get multiple lines
The -e (execute) Switch $ ruby -e 'p 21 * 2' 42 $ ruby -e 'p "some data"' "some data" $ ruby -e 'var = 42; p var' 42 ✤ Run some Ruby without a script ✤ Hints: ✤ Use ‘…’ for shell quoting and “…” for Ruby quoting (to minimize escaping) ✤ Use ;’s to get multiple lines
The “Unix Filter” Pattern ✤ Accepts a file name on the command-line ✤ Or multiple names at once ✤ Or reads from STDIN ✤ Writes to STDOUT $ echo 'A line from file_1.' > file_1.txt $ echo 'A line from file_2.' > file_2.txt $ cat file_1.txt A line from file_1. $ cat file_1.txt file_2.txt A line from file_1. A line from file_2. $ echo 'A line from $stdin.' | cat A line from $stdin.
The “Unix Filter” Pattern ✤ Accepts a file name on the command-line ✤ Or multiple names at once ✤ Or reads from STDIN ✤ Writes to STDOUT $ echo 'A line from file_1.' > file_1.txt $ echo 'A line from file_2.' > file_2.txt $ cat file_1.txt A line from file_1. $ cat file_1.txt file_2.txt A line from file_1. A line from file_2. $ echo 'A line from $stdin.' | cat A line from $stdin.
The “Unix Filter” Pattern ✤ Accepts a file name on the command-line ✤ Or multiple names at once ✤ Or reads from STDIN ✤ Writes to STDOUT $ echo 'A line from file_1.' > file_1.txt $ echo 'A line from file_2.' > file_2.txt $ cat file_1.txt A line from file_1. $ cat file_1.txt file_2.txt A line from file_1. A line from file_2. $ echo 'A line from $stdin.' | cat A line from $stdin.
The “Unix Filter” Pattern ✤ Accepts a file name on the command-line ✤ Or multiple names at once ✤ Or reads from STDIN ✤ Writes to STDOUT $ echo 'A line from file_1.' > file_1.txt $ echo 'A line from file_2.' > file_2.txt $ cat file_1.txt A line from file_1. $ cat file_1.txt file_2.txt A line from file_1. A line from file_2. $ echo 'A line from $stdin.' | cat A line from $stdin.
Ruby’s ARGF ✤ ARGF means use ARGV to build a Unix Filter ✤ It supports the standard Unix Filter patterns ✤ You can use it like an IO object $ ruby -e 'puts ARGF.read' file_1.txt A line from file_1. $ ruby -e 'puts ARGF.read' file_1.txt file_2.txt A line from file_1. A line from file_2. $ echo 'A line from $stdin.' | ruby -e 'puts ARGF.read' A line from $stdin.
Ruby’s ARGF ✤ ARGF means use ARGV to build a Unix Filter ✤ It supports the standard Unix Filter patterns ✤ You can use it like an IO object $ ruby -e 'puts ARGF.read' file_1.txt A line from file_1. $ ruby -e 'puts ARGF.read' file_1.txt file_2.txt A line from file_1. A line from file_2. $ echo 'A line from $stdin.' | ruby -e 'puts ARGF.read' A line from $stdin.
Ruby’s ARGF ✤ ARGF means use ARGV to build a Unix Filter ✤ It supports the standard Unix Filter patterns ✤ You can use it like an IO object $ ruby -e 'puts ARGF.read' file_1.txt A line from file_1. $ ruby -e 'puts ARGF.read' file_1.txt file_2.txt A line from file_1. A line from file_2. $ echo 'A line from $stdin.' | ruby -e 'puts ARGF.read' A line from $stdin.
Ruby’s ARGF ✤ ARGF means use ARGV to build a Unix Filter ✤ It supports the standard Unix Filter patterns ✤ You can use it like an IO object $ ruby -e 'puts ARGF.read' file_1.txt A line from file_1. $ ruby -e 'puts ARGF.read' file_1.txt file_2.txt A line from file_1. A line from file_2. $ echo 'A line from $stdin.' | ruby -e 'puts ARGF.read' A line from $stdin.
Ruby’s ARGF ✤ ARGF means use ARGV to build a Unix Filter ✤ It supports the standard Unix Filter patterns ✤ You can use it like an IO object $ ruby -e 'puts ARGF.read' file_1.txt A line from file_1. $ ruby -e 'puts ARGF.read' file_1.txt file_2.txt A line from file_1. A line from file_2. $ echo 'A line from $stdin.' | ruby -e 'puts ARGF.read' A line from $stdin.
Kernel#gets ✤ Ruby’s default “get a line of input” method ✤ Available everywhere ✤ It reads from ARGF ✤ You get a Unix Filter for free $ ruby -e 'puts gets' file_1.txt A line from file_1. $ ruby -e '2.times do puts gets end' file_1.txt file_2.txt A line from file_1. A line from file_2. $ echo 'A line from $stdin.' | ruby -e 'puts gets' A line from $stdin.
Kernel#gets ✤ Ruby’s default “get a line of input” method ✤ Available everywhere ✤ It reads from ARGF ✤ You get a Unix Filter for free $ ruby -e 'puts gets' file_1.txt A line from file_1. $ ruby -e '2.times do puts gets end' file_1.txt file_2.txt A line from file_1. A line from file_2. $ echo 'A line from $stdin.' | ruby -e 'puts gets' A line from $stdin.
The Current Line Variable: $_ ✤ Ruby has a special variable for “the current line:” $_ ✤ Note how it looks like a line ✤ Kernel#gets assigns to $_ ✤ Kernel#print prints $_ if no arguments are given $ ruby -e 'gets; print' file_1.txt A line from file_1. $ ruby -e 'gets; $_.tr! " ", "+"; print' file_1.txt A+line+from+file_1.
The Current Line Variable: $_ ✤ Ruby has a special variable for “the current line:” $_ ✤ Note how it looks like a line ✤ Kernel#gets assigns to $_ ✤ Kernel#print prints $_ if no arguments are given $ ruby -e 'gets; print' file_1.txt A line from file_1. $ ruby -e 'gets; $_.tr! " ", "+"; print' file_1.txt A+line+from+file_1.
The Current Line Variable: $_ ✤ Ruby has a special variable for “the current line:” $_ ✤ Note how it looks like a line ✤ Kernel#gets assigns to $_ ✤ Kernel#print prints $_ if no arguments are given $ ruby -e 'gets; print' file_1.txt A line from file_1. $ ruby -e 'gets; $_.tr! " ", "+"; print' file_1.txt A+line+from+file_1.
The Current Line Variable: $_ ✤ Ruby has a special variable for “the current line:” $_ ✤ Note how it looks like a line ✤ Kernel#gets assigns to $_ ✤ Kernel#print prints $_ if no arguments are given $ ruby -e 'gets; print' file_1.txt A line from file_1. $ ruby -e 'gets; $_.tr! " ", "+"; print' file_1.txt A+line+from+file_1.
The -p (print loop) Switch ✤ -p wraps -e code in a loop that: ✤ reads a line ✤ runs the -e code ✤ prints the line while gets # -e code goes here print end $ ruby -pe '# do nothing' file_1.txt file_2.txt A line from file_1. A line from file_2. $ ruby -pe '$_ = "Line #{ARGF.lineno}\n"' file_1.txt file_2.txt Line 1 Line 2
The -p (print loop) Switch ✤ -p wraps -e code in a loop that: ✤ reads a line ✤ runs the -e code ✤ prints the line while gets # -e code goes here print end $ ruby -pe '# do nothing' file_1.txt file_2.txt A line from file_1. A line from file_2. $ ruby -pe '$_ = "Line #{ARGF.lineno}\n"' file_1.txt file_2.txt Line 1 Line 2
The -p (print loop) Switch ✤ -p wraps -e code in a loop that: ✤ reads a line ✤ runs the -e code ✤ prints the line while gets # -e code goes here print end $ ruby -pe '# do nothing' file_1.txt file_2.txt A line from file_1. A line from file_2. $ ruby -pe '$_ = "Line #{ARGF.lineno}\n"' file_1.txt file_2.txt Line 1 Line 2
The -p (print loop) Switch ✤ -p wraps -e code in a loop that: ✤ reads a line ✤ runs the -e code ✤ prints the line while gets # -e code goes here print end $ ruby -pe '# do nothing' file_1.txt file_2.txt A line from file_1. A line from file_2. $ ruby -pe '$_ = "Line #{ARGF.lineno}\n"' file_1.txt file_2.txt Line 1 Line 2
The -p (print loop) Switch ✤ -p wraps -e code in a loop that: ✤ reads a line ✤ runs the -e code ✤ prints the line while gets # -e code goes here print end $ ruby -pe '# do nothing' file_1.txt file_2.txt A line from file_1. A line from file_2. $ ruby -pe '$_ = "Line #{ARGF.lineno}\n"' file_1.txt file_2.txt Line 1 Line 2
The -n (non-print loop) Switch ✤ Same as -p, but it doesn’t print ✤ This allows you to decide when to print $ ruby -ne 'print if $_ =~ /2\.\Z/' file_1.txt file_2.txt A line from file_2.
The -n (non-print loop) Switch ✤ Same as -p, but it doesn’t print ✤ This allows you to decide when to print $ ruby -ne 'print if $_ =~ /2\.\Z/' file_1.txt file_2.txt A line from file_2.
The -n (non-print loop) Switch ✤ Same as -p, but it doesn’t print ✤ This allows you to decide when to print $ ruby -ne 'print if $_ =~ /2\.\Z/' file_1.txt file_2.txt A line from file_2.
A Bare Regexp Conditional ✤ My last example can be shortened ✤ A bare Regexp conditional is assumed to be a test against $_ $ ruby -ne 'print if /2\.\Z/' file_1.txt file_2.txt A line from file_2.
A Bare Regexp Conditional ✤ My last example can be shortened ✤ A bare Regexp conditional is assumed to be a test against $_ $ ruby -ne 'print if /2\.\Z/' file_1.txt file_2.txt A line from file_2.
The “Flip-flop” Operator ✤ A conditional can also be a Range of two Regexp objects ✤ It “turns on” when the first matches and will stay true until the second matches $ ruby -e 'puts %w[one two three four five six]' > numbers.txt $ ruby -ne 'print if /\A[os]/../ee\Z/' numbers.txt one two three six
The “Flip-flop” Operator ✤ A conditional can also be a Range of two Regexp objects ✤ It “turns on” when the first matches and will stay true until the second matches $ ruby -e 'puts %w[one two three four five six]' > numbers.txt $ ruby -ne 'print if /\A[os]/../ee\Z/' numbers.txt one two three six
The -i (in place edit) Switch ✤ Passing -i turns causes your filter to replace the input itself ✤ This is done with a buffer, so it feels like you are reading from and writing to the same place $ cat numbers.txt one two three four five six $ ruby -pi \ > -e '$_.tr! "aeiou", "X"' \ > numbers.txt $ cat numbers.txt XnX twX thrXX fXXr fXvX sXx
The -i (in place edit) Switch ✤ Passing -i turns causes your filter to replace the input itself ✤ This is done with a buffer, so it feels like you are reading from and writing to the same place $ cat numbers.txt one two three four five six $ ruby -pi \ > -e '$_.tr! "aeiou", "X"' \ > numbers.txt $ cat numbers.txt XnX twX thrXX fXXr fXvX sXx
The -i (in place edit) Switch ✤ Passing -i turns causes your filter to replace the input itself ✤ This is done with a buffer, so it feels like you are reading from and writing to the same place $ cat numbers.txt one two three four five six $ ruby -pi \ > -e '$_.tr! "aeiou", "X"' \ > numbers.txt $ cat numbers.txt XnX twX thrXX fXXr fXvX sXx
Now With Backups ✤ -i can also accept a backup argument ✤ The original content is moved into a file with the backup suffix appended ✤ This can really protect you when you try to make sweeping changes with some gnarly Regexp! $ ruby -pi.bak \ > -e '$_.delete! "X"' \ > numbers.txt $ cat numbers.txt n tw thr fr fv sx $ cat numbers.txt.bak XnX twX thrXX fXXr fXvX sXx
Now With Backups ✤ -i can also accept a backup argument ✤ The original content is moved into a file with the backup suffix appended ✤ This can really protect you when you try to make sweeping changes with some gnarly Regexp! $ ruby -pi.bak \ > -e '$_.delete! "X"' \ > numbers.txt $ cat numbers.txt n tw thr fr fv sx $ cat numbers.txt.bak XnX twX thrXX fXXr fXvX sXx
Now With Backups ✤ -i can also accept a backup argument ✤ The original content is moved into a file with the backup suffix appended ✤ This can really protect you when you try to make sweeping changes with some gnarly Regexp! $ ruby -pi.bak \ > -e '$_.delete! "X"' \ > numbers.txt $ cat numbers.txt n tw thr fr fv sx $ cat numbers.txt.bak XnX twX thrXX fXXr fXvX sXx
Now With Backups ✤ -i can also accept a backup argument ✤ The original content is moved into a file with the backup suffix appended ✤ This can really protect you when you try to make sweeping changes with some gnarly Regexp! $ ruby -pi.bak \ > -e '$_.delete! "X"' \ > numbers.txt $ cat numbers.txt n tw thr fr fv sx $ cat numbers.txt.bak XnX twX thrXX fXXr fXvX sXx
The -a (autosplit) Switch ✤ -a will cause $_ to be split() into $F (fields) with each read ✤ By default, split() uses a pattern like: /\s+/ $ ruby -e 'puts "some spaced\tfields"' | \ > ruby -nae 'p $F' ["some", "spaced", "fields"]
The -a (autosplit) Switch ✤ -a will cause $_ to be split() into $F (fields) with each read ✤ By default, split() uses a pattern like: /\s+/ $ ruby -e 'puts "some spaced\tfields"' | \ > ruby -nae 'p $F' ["some", "spaced", "fields"]
The -a (autosplit) Switch ✤ -a will cause $_ to be split() into $F (fields) with each read ✤ By default, split() uses a pattern like: /\s+/ $ ruby -e 'puts "some spaced\tfields"' | \ > ruby -nae 'p $F' ["some", "spaced", "fields"]
The -F (field separator) Switch ✤ Used with -a, -F will let you change the split() pattern ✤ WARNING: this is a weird old switch that doesn’t allow a space before the pattern starts! $ echo "1,2, 3" | \ > ruby -naF',\s*' -e 'p $F' ["1", "2", "3\n"]
The -F (field separator) Switch ✤ Used with -a, -F will let you change the split() pattern ✤ WARNING: this is a weird old switch that doesn’t allow a space before the pattern starts! $ echo "1,2, 3" | \ > ruby -naF',\s*' -e 'p $F' ["1", "2", "3\n"]
The -F (field separator) Switch ✤ Used with -a, -F will let you change the split() pattern ✤ WARNING: this is a weird old switch that doesn’t allow a space before the pattern starts! $ echo "1,2, 3" | \ > ruby -naF',\s*' -e 'p $F' ["1", "2", "3\n"]
The -l (line ending) Switch ✤ -l trims the line ending off of read input ✤ The record separator (Ruby’s idea of a line ending) can be changed by passing an octal character code to -0 (not shown) $ echo "1,2, 3" | \ > ruby -nalF',\s*' -e 'p $F' ["1", "2", "3"]
The -l (line ending) Switch ✤ -l trims the line ending off of read input ✤ The record separator (Ruby’s idea of a line ending) can be changed by passing an octal character code to -0 (not shown) $ echo "1,2, 3" | \ > ruby -nalF',\s*' -e 'p $F' ["1", "2", "3"]
The Choice is Yours ✤ You could take the blue pill and pretend Ruby is always beautiful, without any of these ugly features ✤ You could take the red pill, go further down the rabbit hole, and learn yourself some command-line Ruby wizardry
For Those Who Like Red Pills ✤ Learn some regular expression ✤ Look into the standard “find” library ✤ When you’re ready for the really crazy stuff, look up Ruby’s BEGIN { … } and END { … } blocks
One Last Warning ✤ Remember, I didn’t say these are the best ways to do things ✤ There are definitely shell commands that do some of these things better ✤ You can move on to learning those later ✤ Or you may already know them! ✤ That’s not the point ✤ The point: get new ideas and learn new things