How to run shell commands from the Scala REPL

This is an excerpt from the 1st Edition of the Scala Cookbook (#ad) (partially modified for the internet). This is Recipe 14.4, “How to run a shell command from the Scala REPL.”

Problem

You want to be able to run a shell command from within the Scala REPL, such as listing the files in the current directory.

Solution

Run the command using the :sh REPL command, then print the output. The following example shows how to run the Unix ls -al command from within the REPL, and then show the results of the command:

scala> :sh ls -al
res0: scala.tools.nsc.interpreter.ProcessResult = `ls -al` (6 lines, exit 0)

scala> res0.show
total 24
drwxr-xr-x   5 Al  staff  170 Jul 14 17:14 .
drwxr-xr-x  29 Al  staff  986 Jul 14 15:27 ..
-rw-r--r--   1 Al  staff  108 Jul 14 15:34 finance.csv
-rw-r--r--   1 Al  staff  469 Jul 14 15:38 process.scala
-rw-r--r--   1 Al  staff  412 Jul 14 16:24 process2.scala

Alternatively you can import the scala.sys.process package, and then use the normal Process and ProcessBuilder commands described in Recipe 12.11, “Executing External Commands”:

scala> import sys.process._
import sys.process._

scala> "ls -al" !
total 24
drwxr-xr-x   5 Al  staff  170 Jul 14 17:14 .
drwxr-xr-x  29 Al  staff  986 Jul 14 15:27 ..
-rw-r--r--   1 Al  staff  108 Jul 14 15:34 finance.csv
-rw-r--r--   1 Al  staff  469 Jul 14 15:38 process.scala
-rw-r--r--   1 Al  staff  412 Jul 14 16:24 process2.scala
res0: Int = 0

Scala’s -i option

Although those examples show the correct approach, you can improve the situation by loading your own custom code when you start the Scala interpreter. For instance, I almost always start the REPL in my /Users/Al/tmp directory, and I have a file in that directory named repl-commands with these contents:

import sys.process._

def clear = "clear".!
def cmd(cmd: String) = cmd.!!
def ls(dir: String) { println(cmd(s"ls -al $dir")) }
def help {
    println("\n=== MY CONFIG ===")
    "cat /Users/Al/tmp/repl-commands".!
}

case class Person(name: String)
val nums = List(1, 2, 3)
val strings = List("sundance", "rocky", "indigo")
// lets me easily see the methods from StringOps
// with tab completion
val so = new collection.immutable.StringOps("")

With this setup, I start the Scala interpreter with the -i argument, telling it to load this file when it starts:

$ scala -i repl-commands

This makes those pieces of code available to me inside the REPL. For instance, I can clear my terminal window by invoking the clear method:

scala> clear

My ls method provides a directory listing:

scala> ls("/tmp")

With my cmd method I can run other external commands:

scala> cmd("cat /etc/passwd")

The help method uses the system cat command to display this file, which is helpful if I haven’t used it in a while. The nums and strings variables and Person class also make it easy to run quick experiments.

This approach is similar to using a startup file to initialize a Unix login session, like a .bash_profile file for Bash users, and I highly recommend it. As you use the REPL more and more, use this technique to customize its behavior.

To make this even easier, I created the following Unix alias and put it in my .bash_profile file:

alias repl="scala -i /Users/Al/tmp/repl-commands"

I now use this alias to start a REPL session, rather than starting it by typing scala:

$ repl

See Also

  • The “Executing external commands” recipes in Chapter 12 for more examples of executing external commands from Scala code