DEV Community

loading...
Cover image for Smooth Ruby One-Liners

Smooth Ruby One-Liners

Ryan Palo
Ryan is an engineer in the Sacramento Area with a focus in Python, Ruby, and Rust. Bash/Python Exercism mentor. Coding, physics, calculus, music, woodworking. Message me on DEV!
Originally published at assertnotmagic.com Updated on ・5 min read

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
Enter fullscreen mode Exit fullscreen mode

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!"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

This works great when you want to run this command against multiple files.

$ ruby -i -pe 'gsub(/#.*$/, "")' *.conf
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)'
Enter fullscreen mode Exit fullscreen mode

Play with primes!

$ ruby -rprime -e 'puts Prime.first(20)'
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

Edits 2/19/18 thanks to the wisdom of @learnbyexample.

Originally posted on assert_not magic?

Discussion (6)

Collapse
dmerand profile image
Donald Merand

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.

Collapse
rpalo profile image
Ryan Palo Author

That article is super interesting and really well written! Thanks for sharing. 😬 glad you liked my post

Collapse
learnbyexample profile image
Sundeep • Edited

Good one, liked the way concepts are introduced :)

Some note points:

1) $_ is default for regex match (also note the missing dot)

$ ruby -pe '$_strip! if $_ =~ /soup/' /home/rpalo/recipes
$ ruby -pe '$_.strip! if /soup/' /home/rpalo/recipes

2) Don't redirect output to same file as input, you'll be left with empty file

$ ruby -pe '$_.gsub!(/#.*$/, "")' .myconfig > .myconfig

See bash pit fall , stackoverflow, unix.stackexchange etc

3) As shown in last but one example, $_.gsub! can be shortened to gsub

4) Can also use indexing to get first/last element of array, i.e $F[0] and $F[-1] respectively

Collapse
rpalo profile image
Ryan Palo Author

Thanks! I think I've got most everything updated. That input-output redirect is especially good to know.

I left the F.first and F.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 using item.zero? instead of item == 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!

Collapse
ariera profile image
Alejandro Riera

very cool!

Collapse
rpalo profile image
Ryan Palo Author

Thanks! Glad you liked it!