How to execute external system commands in Scala

This is an excerpt from the 1st Edition of the Scala Cookbook. This is Recipe 12.11, “How to execute external system commands in Scala.”

Problem

You want to execute an external (system) command from within a Scala application. You’re not concerned about the output from the command, but you are interested in its exit code.

Solution

To execute external commands, use the methods of the scala.sys.process package. There are three primary ways to execute external commands:

  • Use the ! method to execute the command and get its exit status.
  • Use the !! method to execute the command and get its output.
  • Use the lines method to execute the command in the background and get its result as a Stream.

This recipe demonstrates the ! method, and the next recipe demonstrates the !! method. The lines method is shown in the Discussion of this recipe.

To execute a command and get its exit status, import the necessary members and run the desired command with the ! method:

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

scala> "ls -al".!
total 64
drwxr-xr-x  10 Al  staff   340 May 18 18:00 .
drwxr-xr-x   3 Al  staff   102 Apr  4 17:58 ..
-rw-r--r--   1 Al  staff   118 May 17 08:34 Foo.sh
-rw-r--r--   1 Al  staff  2727 May 17 08:34 Foo.sh.jar
res0: Int = 0

When using the ! method, you can get the exit code of the command that was run:

scala> val exitCode = "ls -al".!
total 64
drwxr-xr-x  10 Al  staff   340 May 18 18:00 .
drwxr-xr-x   3 Al  staff   102 Apr  4 17:58 ..
-rw-r--r--   1 Al  staff   118 May 17 08:34 Foo.sh
-rw-r--r--   1 Al  staff  2727 May 17 08:34 Foo.sh.jar
result: Int = 0

scala> println(exitCode)
0

Both of those examples work because of an implicit conversion that adds the ! method to a String when you add the import statement shown.

Discussion

I use this technique to execute the afplay system command on Mac OS X systems to play sound files in one of my Scala applications, as shown in this method:

def playSoundFile(filename: String): Int = {
    val cmd = "afplay " + filename
    val exitCode = cmd.!
    exitCode
}

That method attempts to play the given filename as a sound file with the afplay command, and returns the exitCode from the command. This method can be shortened to just one line, but I prefer the approach shown because it’s easy to read, especially if you don’t execute system processes very often.

To execute system commands I generally just use ! after a String, but the Seq approach is also useful. The first element in the Seq should be the name of the command you want to run, and subsequent elements are considered to be arguments to it, as shown in these examples:

val exitCode = Seq("ls", "-al").!
val exitCode = Seq("ls", "-a", "-l").!
val exitCode = Seq("ls", "-a", "-l", "/tmp").!

I’ve omitted the output from each of those examples, but each command provides the same directory listing you’d get at the Unix command line.

You can also create a Process object to execute an external command, if you prefer:

val exitCode = Process("ls").!

When running these commands, be aware of whitespace around your command and arguments. All of the following examples fail because of extra whitespace:

// beware leading whitespace
scala> " ls".!
java.io.IOException: Cannot run program "": error=2,
    No such file or directory
    at java.lang.ProcessBuilder.start(ProcessBuilder.java:460)

scala> val exitCode = Seq(" ls ", "-al").!
    java.io.IOException: Cannot run program " ls ": error=2,
    No such file or directory

// beware trailing whitespace
scala> val exitCode = Seq("ls", " -al ").!
    ls:  -al : No such file or directory
    exitCode: Int = 1

If you enter the strings yourself, leave the whitespace out, and if you get the strings from user input, be sure to trim them.

Using the `lines` method

The lines method is an interesting alternative to the ! and !! commands. With lines, you can immediately execute a command in the background. For instance, the following command will run for a long time on a Unix system and result in a large amount of output:

val process = Process("find / -print").lines

The variable process in this example is a Stream[String]. With lines running the process in the background, you can either work with the result immediately or at some later point. For instance, you can read from the stream like this:

process.foreach(println)

The lines method throws an exception if the exit status of the command is nonzero. You can catch that with a try/catch expression, but if this is a problem, or if you also want to retrieve the standard error from the command, use the lines_! method instead of lines. The lines_! method is demonstrated in Recipe 12.12 and discussed in Table 12-1 in Recipe 12.20.

External commands versus built-in commands

As a final note, you can run any external command from Scala that you can run from the Unix command line. However, there’s a big difference between an external command and a shell built-in command. The ls command is an external command that’s available on all Unix systems, and can be found as a file in the /bin directory:

$ which ls
/bin/ls

Some other commands that can be used at a Unix command line, such as the cd or for commands in the Bash shell, are actually built into the shell; you won’t find them as files on the filesystem. Therefore, these commands can’t be executed unless they’re executed from within a shell. See Recipe 12.14, “Building a Pipeline of Commands” for an example of how to execute a shell built-in command.