Introduction
In this article, we'll take a look at how we can leverage the Runtime
and ProcessBuilder
classes to execute shell commands and scripts with Java.
We use computers to automate many things in our daily jobs. System administrators run many commands all the time, some of which are very repetitive and require minimal changes in-between runs.
This process is also ripe for automation. There's no need to run everything manually. Using Java, we can run single or multiple shell commands, execute shell scripts, run the terminal/command prompt, set working directories and manipulate environment variables through core classes.
Runtime.exec()
The Runtime
class in Java is a high-level class, present in every single Java application. Through it, the application itself communicates with the environment it's in.
By extracting the runtime associated with our application via the getRuntime()
method, we can use the exec()
method to execute commands directly or run .bat
/.sh
files.
The exec()
method offers a few overloaded variations:
public Process exec(String command)
- Executes the command contained incommand
in a separate process.public Process exec(String command, String[] envp)
- Executes thecommand
, with an array of environment variables. They're provided as an array of Strings, following thename=value
format.public Process exec(String command, String[] envp, File dir)
- Executes thecommand
, with the specified environment variables, from within thedir
directory.public Process exec(String cmdArray[])
- Executes a command in the form of an array of Strings.public Process exec(String cmdArray[], String[] envp)
- Executes a command with the specified environment variables.public Process exec(String cmdarray[], String[] envp, File dir)
- Executes a command, with the specified environment variables, from within thedir
directory.
It's worth noting that these processes are run externally from the interpreter and will be system-dependent.
What's also worth noting is the difference between String command
and String cmdArray[]
. They achieve the same thing. A command
is broken down into an array anyway, so using any of these two should yield the same results.
It's up to you to decide whether exec("dir /folder")
or exec(new String[]{"dir", "/folder"}
is what you'd like to use.
Let's write up a few examples to see how these overloaded methods differ from each other.
Executing a Command from String
Let's start off with the simplest approach out of these three:
Process process = Runtime.getRuntime().exec("ping www.stackabuse.com");
Running this code will execute the command we've supplied in String format. However, we don't see anything when we run this.
To validate if this ran correctly, we'll want to get ahold of the process
object. Let's use a BufferedReader
to take a look at what's going on:
public static void printResults(Process process) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line = "";
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
Now, when we run this method after the exec()
method, it should yield something along the lines of:
Pinging www.stackabuse.com [104.18.57.23] with 32 bytes of data:
Reply from 104.18.57.23: bytes=32 time=21ms TTL=56
Reply from 104.18.57.23: bytes=32 time=21ms TTL=56
Reply from 104.18.57.23: bytes=32 time=21ms TTL=56
Reply from 104.18.57.23: bytes=32 time=21ms TTL=56
Ping statistics for 104.18.57.23:
Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 21ms, Maximum = 21ms, Average = 21ms
Keep in mind that we'll have to extract the process information from the Process
instances as we go through other examples.
Specify the Working Directory
If you'd like to run a command from, say, a certain folder, we'd do something along the lines of:
Process process = Runtime.getRuntime()
.exec("cmd /c dir", null, new File("C:\\Users\\"));
//.exec("sh -c ls", null, new File("Pathname")); for non-Windows users
printResults(process);
Here, we've provided the exec()
method with a command
, a null
for new environment variables and a new File()
which is set as our working directory.
The addition of cmd /c
before a command such as dir
is worth noting.
Since I'm working on Windows, this opens up the cmd
and /c
carries out the subsequent command. In this case, it's dir
.
The reason why this wasn't mandatory for the ping
example, but is mandatory for this example is nicely answered by an SO user.
Running the previous piece of code will result in:
Volume in drive C has no label.
Volume Serial Number is XXXX-XXXX
Directory of C:\Users
08/29/2019 05:01 PM <DIR> .
08/29/2019 05:01 PM <DIR> ..
08/18/2016 09:11 PM <DIR> Default.migrated
08/29/2019 05:01 PM <DIR> Public
05/15/2020 11:08 AM <DIR> User
0 File(s) 0 bytes
5 Dir(s) 212,555,214,848 bytes free
Let's take a look at how we could supply the previous command in several individual parts, instead of a single String:
Process process = Runtime.getRuntime().exec(
new String[]{"cmd", "/c", "dir"},
null,
new File("C:\\Users\\"));
printResults(process);
Running this piece of code will also result in:
Volume in drive C has no label.
Volume Serial Number is XXXX-XXXX
Directory of C:\Users
08/29/2019 05:01 PM <DIR> .
08/29/2019 05:01 PM <DIR> ..
08/18/2016 09:11 PM <DIR> Default.migrated
08/29/2019 05:01 PM <DIR> Public
05/15/2020 11:08 AM <DIR> User
0 File(s) 0 bytes
5 Dir(s) 212,542,808,064 bytes free
Ultimately, regardless of the approach - using a single String or a String array, the command you input will always be broken down into an array before getting processed by underlying logic.
Which one you'd like to use boils down just to which one you find more readable.
Using Environment Variables
Let's take a look at how we can use environment variables:
Process process = Runtime.getRuntime().exec(
"cmd /c echo %var1%",
new String[]{"var1=value1"});
printResults(process);
We can supply as many environment variables as we'd like within the String array. Here, we've just printed the value of var1
using echo
.
Running this code will return:
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
value1
Running .bat and .sh Files
Sometimes, it's just much easier to offload everything into a file and run that file instead of adding everything programmatically.
Depending on your operating system, you'd use either .bat
or .sh
files. Let's create one with the contents:
echo Hello World
Then, let's use the same approach as before:
Process process = Runtime.getRuntime().exec(
"cmd /c start file.bat",
null,
new File("C:\\Users\\User\\Desktop\\"));
This'll open up the command prompt and run the .bat
file in the working directory we've set.
Running this code surely enough results in:
With all of the overloaded exec()
signatures taken care of, let's take a look at the ProcessBuilder
class and how we can execute commands using it.
ProcessBuilder
ProcessBuilder
is the underlying mechanism that runs the commands when we use the Runtime.getRuntime().exec()
method:
/**
* Executes the specified command and arguments in a separate process with
* the specified environment and working directory.
*...
*/
public Process exec(String[] cmdarray, String[] envp, File dir) throws IOException {
return new ProcessBuilder(cmdarray)
.environment(envp)
.directory(dir)
.start();
}
JavaDocs for the Runtime
class
Taking a look at how the ProcessBuilder
takes our input from the exec()
method and runs the command, gives us a good idea of how to use it as well.
It accepts a String[] cmdarray
, and that's enough to get it running. Alternatively, we can supply it with optional arguments such as the String[] envp
and File dir
.
Let's explore these options.
ProcessBuilder: Executing Command from Strings
Instead of being able to provide a single String, such as cmd /c dir
, we'll have to break it up in this case. For example, if we wanted to list the files in the C:/Users
directory like before, we'd do:
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.command("cmd", "/c", "dir C:\\Users");
Process process = processBuilder.start();
printResults(process);
To actually execute a Process
, we run the start()
command and assign the returned value to a Process
instance.
Running this code will yield:
Volume in drive C has no label.
Volume Serial Number is XXXX-XXXX
Directory of C:\Users
08/29/2019 05:01 PM <DIR> .
08/29/2019 05:01 PM <DIR> ..
08/18/2016 09:11 PM <DIR> Default.migrated
08/29/2019 05:01 PM <DIR> Public
05/15/2020 11:08 AM <DIR> User
0 File(s) 0 bytes
5 Dir(s) 212,517,294,080 bytes free
However, this approach isn't any better than the previous one. What's useful with the ProcessBuilder
class is that it's customizable. We can set things programmatically, not just via commands.
ProcessBuilder: Specify the Working Directory
Instead of supplying the working directory via the command, let's set it programmatically:
processBuilder.command("cmd", "/c", "dir").directory(new File("C:\\Users\\"));
Here, we've set the working directory to be the same as before, but we've moved that definition out of the command itself. Running this code will provide the same result as the last example.
ProcessBuilder: Environment Variables
Using ProcessBuilder
s methods, it's easy to retrieve a list of environment variables in the form of a Map
. It's also easy to set environment variables so that your program can use them.
Let's get the environment variables currently available and then add some for later use:
ProcessBuilder processBuilder = new ProcessBuilder();
Map<String, String> environmentVariables = processBuilder.environment();
environmentVariables.forEach((key, value) -> System.out.println(key + value));
Here, we've packed the returned environment variables into a Map
and ran a forEach()
on it to print out the values to our console.
Running this code will yield a list of the environment variables you have on your machine:
DriverDataC:\Windows\System32\Drivers\DriverData
HerokuPathE:\Heroku
ProgramDataC:\ProgramData
...
Now, let's add an environment variable to that list and use it:
environmentVariables.put("var1", "value1");
processBuilder.command("cmd", "/c", "echo", "%var1%");
Process process = processBuilder.start();
printResults(process);
Running this code will yield:
value1
Of course, once the program has finished running, this variable will not stay in the list.
ProcessBuilder: Running .bat and .sh Files
If you'd like to run a file, again, we'd just supply the ProcessBuilder
instance with the required information:
processBuilder
.command("cmd", "/c", "start", "file.bat")
.directory(new File("C:\\Users\\User\\Desktop"));
Process process = processBuilder.start();
Running this code results in the command prompt opening up and executing the .bat
file:
Conclusion
In this article, we've explored examples of running shell commands in Java. We've used the Runtime
and ProcessBuilder
classes to do this.
Using Java, we can run single or multiple shell commands, execute shell scripts, run the terminal/command prompt, set working directories and manipulate environment variables through core classes.