Scala: How to handle wildcard characters when running external commands

This is an excerpt from the Scala Cookbook (partially modified for the internet). This is Recipe 12.17, “How to handle wildcard characters when running external commands in Scala.”

Problem

You want to use a Unix shell wildcard character, such as *, when you execute an external command in a Scala application.

Solution

In general, the best thing you can do when using a wildcard character like * is to run your command while invoking a Unix shell. For instance, if you have .scala files in the current directory and try to list them with the following command, the command will fail:

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

scala> "ls *.scala".!
ls: *.scala: No such file or directory
res0: Int = 1

But by running the same command inside a Bourne shell, the command now correctly shows the .scala files (and returns the exit status of the command):

scala> val status = Seq("/bin/sh", "-c", "ls *.scala").!
AndOrTest.scala
Console.scala
status: Int = 0

Discussion

Putting a shell wildcard character like * into a command doesn’t work because the * needs to be interpreted and expanded by a shell, like the Bourne or Bash shells. In this example, even though there are files in the current directory named AndOrTest.scala and Console.scala, the first attempt doesn’t work. These other attempts will also fail as a result of the same problem:

scala> "echo *".!
*
res0: Int = 0

scala> Seq("grep", "-i", "foo", "*.scala").!
grep: *.scala: No such file or directory
res1: Int = 2

scala> Seq("ls", "*.scala").!
ls: *.scala: No such file or directory
res2: Int = 1

In each example, you can make these commands work by invoking a shell in the first two parameters to a Seq:

val status = Seq("/bin/sh", "-c", "echo *").!
val status = Seq("/bin/sh", "-c", "ls *.scala").!
val status = Seq("/bin/sh", "-c", "grep -i foo *.scala").!

An important part of this recipe is using the -c argument of the /bin/sh command. The sh manpage describes this parameter as follows:

-c string   If the -c option is present, then commands are read from string.

If there are arguments after the string, they are assigned to the positional parameters, starting with $0.

As an exception to this general rule, the -name option of the find command may work because it treats the * character as a wildcard character itself. As a result, the following find command finds the two files in the current directory without having to be run in a shell:

scala> val status = Seq("find", ".", "-name", "*.scala", "-type", "f").!
./AndOrTest.scala
./Console.scala
status: Int = 0

However, as shown, other commands generally require that the * wildcard character be interpreted and expanded by a shell.