Ruby as a scripting language
At $DAYJOB, after a recent bout of scripting, I have gotten irritated enough of Bash to look earnestly into alternatives. The primary requirements, as I see it:
- Has to be a mainstream language, else it's going to be a non starter. So, no haskell, no Lisp, none of the smaller Shell languages that have sprouted up like elvish, ergonomica etc.
- Has to be a saner language than Bash. I think pretty much everything qualifies, except maybe perl.
- Has to support very low overhead command invokation. Really, python fails badly here.
Turns out Ruby may fit the bill after all. This wonderful article takes you most of the way. What extra things I need, I document in this blog post.
Firstly, the backtick syntax does not work for directory manipulation commands like cd
. Also for things like mkdir
, the method recommended in the guide above is a bit clunky. Enter fileutils. See this excellent SO answer.
require 'fileutils' include FileUtils cd "dirname" mkdir "subdir" touch "file.txt" # These also work, without the import, albeit a bit clunky. # `mkdir subdir` # Dir.mkdir "subdir"
Note: You now need to quote dir/file names, but that's a small cost for the win.
Also, it does not map one-one with the Bash semantics. For eg, the mv
command takes a list and a dir. At first glance, renaming a single file by wildcard does not seem to be trivially possible. Of course, for these things, one can switch back to the backtick notation.
One of the simplest ways to run a background process is as follows:
fork do `command using backticks` end
This, of course, means that the background process cannot be killed later on. A version which allows such control is documented in the original blog post.
When you want to pass multiple arguments to a function in simpler manner:
function(arg1, arg2) # obviously works function arg1, arg2 # works function arg1 arg2 # does not work
Another nice article worth a read.
Ok, how do you run throw-off commands that can potentially fail? In bash, usually, we just use command specific flags and simply not check "$?". For eg:
# assuming presence of fileutils mkdir "sample"
will throw an exception. Manually add the begin/rescue/end block seems tedious. Luckily ruby has that right amount of metaprogramming to solve this elegantly.
def silently begin yield rescue end end silently { mkdir "sample" } # note: this WILL NOT WORK - we exit the block at the first failure, if sample/ exists, sample1 will never be created silently do mkdir "sample" mkdir "sample1" end
As you can see, while this looks elegant, it has a nice foot gun embedded in it. I need to understand the art of creating DSLs in Ruby better to come up with better solutions.
Will keep iterating on my scripts and write a separate post on this when I understand this space better.