How to use Scala as a scripting language

This is an excerpt from the Scala Cookbook (partially modified for the internet). This is Recipe 14.10, “How to use Scala as a scripting language.”

Problem

You want to use Scala as a scripting language on Unix systems, replacing other scripts you’ve written in a Unix shell (Bourne Shell, Bash), Perl, PHP, Ruby, etc.

Solution

Save your Scala code to a text file, making sure the first three lines of the script contain the lines shown, which will execute the script using the scala interpreter:

#!/bin/sh
exec scala "$0" "$@"
!#
println("Hello, world")

To test this, save the code to a file named hello.sh, make it executable, and then run it:

$ chmod +x hello.sh

$ ./hello.sh
Hello, world

As detailed in the next recipe, command-line parameters to the script can be accessed via an args array, which is implicitly made available to you:

#!/bin/sh
exec scala "$0" "$@"
!#
args.foreach(println)

Discussion

Regarding the first three lines of a shell script:

  • The #! in the first line is the usual way to start a Unix shell script. It invokes a Unix Bourne shell.
  • The exec command is a shell built-in. $0 expands to the name of the shell script, and $@ expands to the positional parameters.
  • The !# characters as the third line of the script is how the header section is closed. A great thing about using Scala in your scripts is that you can use all of its advanced features, such as the ability to create and use classes in your scripts:
#!/bin/sh
exec scala "$0" "$@"
!#

class Person(var firstName: String, var lastName: String) {
    override def toString = firstName + " " + lastName
}

println(new Person("Nacho", "Libre"))

Using the App trait or main method

To use an App trait in a Scala script, start the script with the usual first three header lines, and then define an object that extends the App trait:

#!/bin/sh
exec scala "$0" "$@"
!#
object Hello extends App {
    println("Hello, world")
    // if you want to access the command line args:
    //args.foreach(println)
}

Hello.main(args)

The last line in that example shows how to pass the script’s command-line arguments to the implicit main method in the Hello object. As usual in an App trait object, the arguments are available via a variable named args.

You can also define an object with a main method to kick off your shell script action:

#!/bin/sh
exec scala "$0" "$@"
!#
object Hello {
    def main(args: Array[String]) {
        println("Hello, world")
        // if you want to access the command line args:
        //args.foreach(println)
    }
}

Hello.main(args)

Building the classpath

If your shell script needs to rely on external dependencies (such as JAR files), add them to your script’s classpath using this syntax:

#!/bin/sh
exec scala -classpath "lib/htmlcleaner-2.2.jar:lib/scalaemail_2.10.0-1.0.jar:lib/stockutils_2.10.0-1.0.jar" "$0" "$@"
!#

You can then import these classes into your code as usual. The following code shows a complete script I wrote that retrieves stock quotes and mails them to me:

#!/bin/sh
exec scala -classpath "lib/htmlcleaner-2.2.jar:lib/scalaemail_2.10.0-1.0.jar:lib/stockutils_2.10.0-1.0.jar" "$0" "$@"
!#

import java.io._
import scala.io.Source
import com.devdaily.stocks.StockUtils
import scala.collection.mutable.ArrayBuffer

object GetStocks {

  case class Stock(symbol: String, name: String, price: BigDecimal)

  val DIR = System.getProperty("user.dir")
  val SLASH = System.getProperty("file.separator")
  val CANON_STOCKS_FILE = DIR + SLASH + "stocks.dat"
  val CANON_OUTPUT_FILE = DIR + SLASH + "quotes.out"

  def main(args: Array[String]) {
    // read the stocks file into a list of strings ("AAPL|Apple")
    val lines = Source.fromFile(CANON_STOCKS_FILE).getLines.toList
    // create a list of Stock from the symbol, name, and by
    // retrieving the price
    var stocks = new ArrayBuffer[Stock]()
    lines.foreach{ line =>
      val fields = line.split("\\|")
      val symbol = fields(0)
      val html = StockUtils.getHtmlFromUrl(symbol)
      val price = StockUtils.extractPriceFromHtml(html, symbol)
      val stock = Stock(symbol, fields(1), BigDecimal(price))
      stocks += stock
    }

    // build a string to output
    var sb = new StringBuilder
    stocks.foreach { stock =>
      sb.append("%s is %s\n".format(stock.name, stock.price))
    }
    val output = sb.toString

    // write the string to the file
    val pw = new PrintWriter(new File(CANON_OUTPUT_FILE))
    pw.write(output)
    pw.close
  }
}

GetStocks.main(args)

I run this script twice a day through a crontab entry on a Linux server. The stocks.dat file it reads has entries like this:

AAPL|Apple
KKD|Krispy Kreme
NFLX|Netflix

See Also