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 aStream
.
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.
this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |