Monday, April 22, 2013

Scala Command-Line Hacks

Do you like command-line scripting and one-liners with Perl, Ruby and the like?   

For instance, here's a Ruby one-liner that uppercases the input:

% echo matz | ruby -p -e '$_.tr! "a-z", "A-Z"'
MATZ

You like that kind of stuff?  Yes?  Excellent!  Then I offer you a hacking idea for Scala.

As you may know, Scala offers similar capability with the -e command-line option but it's fairly limited in its basic form because of the necessary boilerplate code to set up iteration over the standard input... it just begs for a simple HACK!

Using a simple bash wrapper,


#!/bin/bash
#
# Usage: scala-map MAP_CODE
#
code=$(cat <<END
scala.io.Source.stdin.getLines map { $@ } foreach println
END
)
scala -e "$code"

then we can express similar one-liners using Scala code and the standard library:

% ls | scala-map _.toUpperCase
FOO
BAR
BAZ
...

% echo "foo bar baz" | scala-map '_.split(" ").mkString("-")'
foo-bar-baz

Nifty, right?  Here's another script template to fold over the standard input,



#!/bin/bash
#
# Usage: scala-fold INITIAL_VALUE FOLD_CODE
#
# where the following val's can be used in FOLD_CODE:
#
#        `acc`  is bound to the accumulator value
#        `line` is bound to the current line
#
code=$(cat <<END
println(scala.io.Source.stdin.getLines.foldLeft($1) { case (acc, line) => $2 })
END
)
scala -e "$code"

Now if you wanted to calculate the sum of the second column of space-separated input, you'd write:

$ cat | scala-fold 0 'acc + (line.split(" ")(1).toInt)'
foo 1
bar 2
baz 3 
(CTRL-D)
6

You get the idea ...  hopefully this inspires you to try a few things with Scala scripting templates!

Disclaimer:  I am not advocating these hacks as replacement to learning other Unix power tools like grep, sed, awk, ... I am simply illustrating that Scala can be turned into an effective command-line scripting tool as part of the vast array of Unix tools.   Use what works best for you.

2 comments:

Anonymous said...

I needed to change the first example to:

code=$(cat <<EOF
scala.io.Source.stdin.getLines map { $@ } foreach println
EOF
)
scala -e "$code"

to make it work. Otherwise, great blog post and great tricks :)

Grega

Anonymous said...

Hi! I needed to change the bash code a bit to make the first example work:

code=$(cat <<EOF
scala.io.Source.stdin.getLines map { $@ } foreach println
EOF
)
scala -e "$code"

Great tricks, really liked the post!