When I first started learning Ruby, the first book I read had a chapter at the front about all of the command line options and flags for the ruby
command. Missing the point, as usual, I flipped quickly through that section without reading much. "Yeah, yeah, yeah," I said to myself as I pulled open my editor. "Whatever. Just get to the good stuff. Show me those objects! Those classes!" Well, I'm here to say: I Missed Out. A chapter on a different book I'm now reading (Practical Ruby for System Administration by Andre Ben-Hamou) also had a section about this, and this time I paid attention. Now I want to share the highlights with everybody else!
The Good Stuff
Simple Execution
Let's start simple. The -e
flag will have the Ruby interpreter execute a command string inline and output the result to stdout
. Useful if you can't remember that darn Bash command for doing floating point math (hint: it's bc
)!
$ ruby -e 'puts (Math.sqrt(32**2/57.2))' > calc.txt
A note about quotation marks. Many of the useful Ruby one-liners contain special variables like $_, $<, and $.
. This is all fine and dandy until you add Bash string interpolation into the mix. Since you interpolate variables like this in Bash:
echo "That's no $silly_thing, that's my wife!"
If you surround your Ruby command with double quotes, your special variables will have unpredictable results. It's best to stick to single quotes around the command at all times.
The first example is a bit useful if you're stubbornly refusing to Google Bash commands, but only slightly more convenient. But stay with me, because we're just getting warmed up.
Line-by-Line Processing
The -n
flag wraps your executed one-liner in an implicit while gets ... end
block. When you combine this with the usage of Ruby's special global variable $_
, which stores the result of the most recent Kernel.gets
command, you can do some nice (and readable) file processing!
$ ruby -ne 'puts $_.strip if $_ =~ /soup/' /home/rpalo/recipes
Which will strip out excess whitespace and print me every line that contains the word 'soup'. I don't know about you, but a script like that would be critical to my infrastructure.
If you get tired of typing puts $_.something.something
after a while, don't worry. The -p
flag drops an implicit print $_
at the end of your command, so you can just modify the variable. We can clean up the previous example like this:
$ ruby -pe '$_.strip! if $_ =~ /soup/' /home/rpalo/recipes
Edit 2/19/18: In fact, the $_
variable is the default variable that gets tested against any regexes, so the above line can be simplified to:
$ ruby -pe '$_.strip! if /soup/' /home/rpalo/recipes
You're saving at least 4 whole characters there. Don't spend it all in one place.
In-Place File Editing
"I feel like I'm not a real developer because I haven't taken the time to learn to use the sed
or awk
command!" Firstly, that's silly. You should disregard anyone who begins any sentence with the phrase, "You're not a real developer if...". Additionally, you can make them feel sad when you edit files in place (like sed -i
does)! Here are four ways to do this, in order of increasing levels of shwoopiness. Let's say you wanted to remove the default comments from a configuration file (not recommended unless you promise to add your own back in).
Edit 2/19/18: Redirecting input and output to the same file is bad (wipes out the whole file). This next snippet has been updated to reflect that. See the comments for more details. Also, gsub
follows the same rules as regexes and operates on $_
by default, so that can be omitted.
$ ruby -pe 'gsub(/#.*$/, "")' .myconfig > .myconfig.new
$ mv .myconfig .myconfig.old
$ mv .myconfig.new .myconfig
Redirect the modified lines to a new file instead of printing to STDOUT
. You can cut out all that extra work if you use the -i
flag.
$ ruby -i -pe 'gsub(/#.*$/, "")' .myconfig
This works great when you want to run this command against multiple files.
$ ruby -i -pe 'gsub(/#.*$/, "")' *.conf
The glob match will run through each of the files ending in .conf and overwrite them.
Lastly, if you're paranoid and risk-averse (i.e. you've been using the terminal long enough to know the pain of overwriting a file with an error in your script and losing everything -- #GodBlessGit), you can add a file extension to your -i
flag like this: -i.bak
and it will save the old version with that file extension before writing; .myconfig
won't have comments, but there will be a new .myconfig.bak
that still does.
There MUST be NO space between the
-i
and the backup file extension or it won't work. You have been warned.
Farming the Rows and Fields
I want to talk about two more special global variables: $< and $.
. The first one refers to the file that was input. The second one refers to the current line number. (Note that this means that $. and $<.lineno
are synonymous). You can implement a rough version of head
like this:
$ ruby -ne 'puts $_ if $. <= 10' test.txt
To use fields in files that might be delimited, such as csv's or system files like /etc/passwd
, you might first try doing this:
$ ruby -ne 'puts $_.split.first + $_.split.last' test.txt
That works, but you can have the heavy lifting done for you with -a
, which stands for "autosplit". It drops the pre-split fields into a special variable called $F
.
$ ruby -a -ne 'puts $F.first + $F.last' test.txt
The above example only works with spaces as delimiters, though! you can specify that with -F
.
$ ruby -a -F: -ne 'puts $F.first' /etc/passwd
Setup and Teardown
If you have used awk
, then this next part may be familiar. You can do some initializing before you run through the input file and teardown/final output after, using BEGIN
and END
blocks. Useful for things like counting the total number of lines that match a pattern.
$ ruby -ne 'BEGIN { ducks = 0 }; ducks += 1 if $_ =~ /ducks/; END { puts ducks }' duckfile.txt
Using this setup and teardown, your one-liners get quite a bit more powerful.
Requiring Modules
You've probably actually used this in other ways, but you can require gems and modules from the standard library with the -r
command. For instance, to list out your machine's IP addresses, you could do this:
$ ruby -rsocket -e 'puts Socket.ip_address_list.map(&:inspect)'
Play with primes!
$ ruby -rprime -e 'puts Prime.first(20)'
Wrapping Up
Overall, these may not give you a 10x productivity boost, but they are fun and I feel like they provide an interesting approach. They seem especially useful if you've been deep into a Ruby project and switching your brain over to Bash is too much work. Halfway through writing this post, I went to Google, and there are an amazing number of blog posts about this topic. (Kind of made me feel lame for posting about it, to be honest). But it's a fun topic, and it's just one more of those things that makes Ruby fun to use, so I thought it would be good to share. Anyways, tweet/message me if you've got any cool Ruby one-liners that you use all the time! I'll add them to this list:
Submitted by Awesome People
## First two submitted by me to make this not be a sad empty list :|
# Delete trailing whitespace
$ ruby -pe 'gsub(/\s+$/, "\n")'
# Prints too long lines
$ ruby -ne 'puts $_ if $_.length > 80'
Edits 2/19/18 thanks to the wisdom of @learnbyexample.
Originally posted on assert_not magic?
Top comments (6)
This is great! So many helpful tips. I didn't know about the auto-split functions, for example, and will definitely use that one!
This article makes me think of Ryan Tomayko's awk-ward Ruby, which is worth a read if you haven't checked it out.
That article is super interesting and really well written! Thanks for sharing. 😬 glad you liked my post
Good one, liked the way concepts are introduced :)
Some note points:
1)
$_
is default for regex match (also note the missing dot)2) Don't redirect output to same file as input, you'll be left with empty file
See bash pit fall , stackoverflow, unix.stackexchange etc
3) As shown in last but one example,
$_.gsub!
can be shortened togsub
4) Can also use indexing to get first/last element of array, i.e
$F[0]
and$F[-1]
respectivelyThanks! I think I've got most everything updated. That input-output redirect is especially good to know.
I left the
F.first
andF.last
because I think they're a bit easier to read, and it's possibly a bit more idiomatic Ruby to use methods when they're available (like usingitem.zero?
instead ofitem == 0
). If you wanted the second item,$F[1]
would be fair though.Anyways, thanks for helping me make my post better, I really appreciate it!
very cool!
Thanks! Glad you liked it!