Why does Runtime.exec(String) work for some but not all commands?
Why do some commands fail?
This happens because the command passed to Runtime.exec(String)
is not executed in a shell. The shell performs a lot of common support services for programs, and when the shell is not around to do them, the command will fail.
When do commands fail?
A command will fail whenever it depends on a shell features. The shell does a lot of common, useful things we don't normally think about:
The shell splits correctly on quotes and spaces
This makes sure the filename in
"My File.txt"
remains a single argument.Runtime.exec(String)
naively splits on spaces and would pass this as two separate filenames. This obviously fails.The shell expands globs/wildcards
When you run
ls *.doc
, the shell rewrites it intols letter.doc notes.doc
.Runtime.exec(String)
doesn't, it just passes them as arguments.ls
has no idea what*
is, so the command fails.The shell manages pipes and redirections.
When you run
ls mydir > output.txt
, the shell opens "output.txt" for command output and removes it from the command line, givingls mydir
.Runtime.exec(String)
doesn't. It just passes them as arguments.ls
has no idea what>
means, so the command fails.The shell expands variables and commands
When you run
ls "$HOME"
orls "$(pwd)"
, the shell rewrites it intols /home/myuser
.Runtime.exec(String)
doesn't, it just passes them as arguments.ls
has no idea what$
means, so the command fails.
What can you do instead?
There are two ways to execute arbitrarily complex commands:
Simple and sloppy: delegate to a shell.
You can just use Runtime.exec(String[])
(note the array parameter) and pass your command directly to a shell that can do all the heavy lifting:
// Simple, sloppy fix. May have security and robustness implications
String myFile = "some filename.txt";
String myCommand = "cp -R '" + myFile + "' $HOME 2> errorlog";
Runtime.getRuntime().exec(new String[] { "bash", "-c", myCommand });
Secure and robust: take on the responsibilities of the shell.
This is not a fix that can be mechanically applied, but requires an understanding the Unix execution model, what shells do, and how you can do the same. However, you can get a solid, secure and robust solution by taking the shell out of the picture. This is facilitated by ProcessBuilder
.
The command from the previous example that requires someone to handle 1. quotes, 2. variables, and 3. redirections, can be written as:
String myFile = "some filename.txt";
ProcessBuilder builder = new ProcessBuilder(
"cp", "-R", myFile, // We handle word splitting
System.getenv("HOME")); // We handle variables
builder.redirectError( // We set up redirections
ProcessBuilder.Redirect.to(new File("errorlog")));
builder.start();
Runtime.exec not working
If you use the Runtime.exec(String)
overload, the string is treated as a command and its arguments, and is crudely split into substrings at white-space boundaries. This splitting is standard behaviour for that overload. (Refer to the javadoc.)
Runtime.exec(...)
expects a native command and its arguments. You've provided a line of shell input. The exec methods don't understand shell input and don't know how to execute it properly. And the crude splitting (see above) messes up everything.
If you need to do that, then use the following:
String yourShellInput = "echo hi && echo ho"; // or whatever ...
String[] commandAndArgs = new String[]{ "/bin/sh", "-c", yourShellInput };
Runtime.getRuntime().exec(commandAndArgs);
This is equivalent to running:
$ /bin/sh -c "echo hi && echo ho".
If sh
is not installed as /bin/sh
use the path where it is installed instead.
Java Runtime exec() not working
Please note that you don't need xdg-open
to do this.
You can use the java platform-agnostic Desktop API:
if(Desktop.isDesktopSupported()) {
Desktop.open("/path/to/file.txt");
}
Update
If the standard approach still gives issues, you can pass the parameters as an array since Runtime.exec
does not invoke a shell and therefore does not support or allow quoting or escaping:
String program;
if (Program.isPlatformLinux())
{
program = "xdg-open";
} else {
program = "something else";
}
Runtime.getRuntime().exec(new String[]{program, file.getAbsolutePath()});
Runtime.exec not running but no exception
You can debug or print the Process.getErrorStream
and Process.getInputStream
to check for an error reported by the sub-process.
My guess is that you need to invoke /bin/sh
as the main program and pass your script as an argument, but I'm not sure. I know that you have to do that on windows.
java getRuntime().exec() does not work for running basic cmd commands
I solved this issue by using ProcessBuilder
. I still don't know why the earlier code didn't work for all the commands. But by using ProcessBuilder
, I was able to perform any cmd
query.
Here's the code for reference:
String command_ping = "ping " + host_name;
try {
ProcessBuilder builder = new ProcessBuilder("cmd.exe", "/c", command_ping);
builder.redirectErrorStream(true);
Process p = builder.start();
BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream()));
StringBuffer buffer = new StringBuffer();
String line = "";
while (true)
{
buffer.append(line).append("\n");
line = r.readLine();
if (line == null) { break; }
}
message_ping= buffer.toString();
p.waitFor();
r.close();
}
catch (IOException e)
{
e.printStackTrace();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
Command works in terminal, but not with Runtime.exec
This is the most common mistake of all times when it comes to a Process
.
A process is not a shell interpreter. As such, any special shell "keywords" will not be interpreted.
If you try and exec cmd1 && cmd2
, what happens is that the arguments of the process are literally cmd1
, &&
, cmd2
. Don't do that.
What is more, don't use Runtime.exec()
. Use a ProcessBuilder
instead. Sample code:
final Process p = new ProcessBuilder("cmd1", "arg1", "arg2").start();
final int retval = p.waitFor();
See the javadoc for ProcessBuilder
, it has a lot of niceties.
Oh, and if you use Java 7, don't even bother using external commands. Java 7 has Files.copy()
.
And also, man execve
.
getRuntime().exec() does nothing
You need to call the jar with java.exe, and you're not doing that. Also you need to trap the input and error streams from the process, something you can't do the way you're running this. Use ProcessBuilder instead, get your streams and then run the process.
For example (and I can only do a Windows example),
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;
public class ProcessEg {
private static Process p;
public static void main(String[] args) {
String[] commands = {"cmd", "/c", "dir"};
ProcessBuilder pBuilder = new ProcessBuilder(commands);
pBuilder.redirectErrorStream();
try {
p = pBuilder.start();
InputStream in = p.getInputStream();
final Scanner scanner = new Scanner(in);
new Thread(new Runnable() {
public void run() {
while (scanner.hasNextLine()) {
System.out.println(scanner.nextLine());
}
scanner.close();
}
}).start();
} catch (IOException e) {
e.printStackTrace();
}
try {
int result = p.waitFor();
p.destroy();
System.out.println("exit result: " + result);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Runtime.exec not running cd
You can't (usefully) issue a cd
command through Runtime.exec
. The cd
command, on most OS's, is a built-in command of the shell, not an executable (which is why you get the error you get), and it operates on the runtime environment of the shell. Although you could use Runtime.exec
to fire up a shell and execute the cd
command within it (for Windows that would be cmd.exe /c "cd path"
), it would only change the current directory within the shell, not for the program running.
What you need to do is resolve the directory within your program, using the various features of File
, and use that resolved absolute file path for whatever it is that you're trying to use cd
for.
If you post a (new) question saying what it is you're trying to achieve by using cd
, we can help you achieve that, but using Runtime.exec
to issue cd
isn't going to be the solution.
Runtime.exec command not working
I suspect this:
String command = "wget -O " + destFile + " \""+ srcFile +"\"";
is the problem. When you run in a shell, the quotes around the URL will be removed. However when you run via Java you're not running via a shell and your URL starts with "http...
(look closely at the error message).
If you don't want Runtime.exec() t
o parse and split your arguments then you might consider the variant that takes individual arguments. A more efficient solution still would be to download using HttpComponents.
Related Topics
Android Recyclerview Addition & Removal of Items
Any Good Orm Tools for Android Development
Change File Owner Group Under Linux with Java.Nio.Files
How to Run a Spring Boot Executable Jar in a Production Environment
How to Know Whether Enough Memory Is Free to Deploy a New Application on a Linux MAChine
Java 7 Language Features with Android
How to Return an Array from Jni to Java
How Does Activity.Finish() Work in Android
How to Group a 3X3 Grid of Radio Buttons
How to Make My Arrayadapter Follow the Viewholder Pattern
Android: How to Get the Current Day of the Week (Monday, etc...) in the User's Language
How to Make Unsigned Byte in Java