I am trying to execute a command via Runtime.getRuntime().exec().
When i run the following command in my linux bash it works fine.
Command: bash -c "npm -v"
But when i try to run it with Java it fails with the following error:
-v": -c: line 0: unexpected EOF while looking for matching `"'
-v": -c: line 1: syntax error: unexpected end of file
Reproducible example:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
public class RunACommandTest
{
public static void main(String[] args)
{
try
{
Process exec = Runtime.getRuntime().exec("bash -c \"npm -v\"");
new BufferedReader(new InputStreamReader(exec.getInputStream(), StandardCharsets.UTF_8))
.lines()
.forEachOrdered(line -> System.out.println("IN " + line));
new BufferedReader(new InputStreamReader(exec.getErrorStream(), StandardCharsets.UTF_8))
.lines()
.forEachOrdered(line -> System.out.println("ERR " + line));
}
catch(IOException e)
{
throw new RuntimeException(e);
}
}
}
I have also tried to use single quotes instead of double quotes.
Unfortunately the now deprecated Runtime.exec(String) breaks up this command incorrectly as 4 argument command { "bash", "-c", "\"npm", "-v\"" }. Separating the parameter list as 3 argument command { "bash", "-c", "npm -v" } will avoid this problem.
It is easier to use ProcessBuilder which is used internally by Runtime.exec:
ProcessBuilder pb = new ProcessBuilder("bash","-c", "npm -v");
Process exec = pb.start();
Note that you must consume stdout + stderr in separate threads. What you have above may work, but it may freeze for other processes or conditions. You can avoid by redirect to file, or redirect error to stdout with:
pb.redirectErrorStream(true);
Related
I had question about running scripts using Junit 5. I have the following piece of code:
public class RunMvnSubprocess {
#Test
public void main() throws IOException, InterruptedException {
String[] cmd = new String[]{"mvn.cmd", "-version"}; // command to be executed on command prompt.
Process p = Runtime.getRuntime().exec(cmd);
try (BufferedReader output = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
String line;
while ((line = output.readLine()) != null) {
System.out.println(line);
}
}
p.waitFor();
}
}
I get no output when I run it using Junit 5.7.0. However, running this on Junit 4.13.2 works fine.
Please note that I am running this piece of test in Windows 10 Pro version 21H1.
EDIT:
Modifying
new String[]{"mvn.cmd", "-version"}
to
new String[]{"cmd", "/c", "\"mvn -version\""}
works for me, but launching a subshell is a bad practice so I am keeping this workaround as a last resort.
Note that you are implicity running a sub-shell as the Windows command CMD.EXE is called to interpret the contents of mvn.cmd, so your value of cmd is equivalent to:
cmd = new String[]{ "cmd", "/c", "call mvn.cmd -version"};
If you get no error code from waitFor or no output or no exception, then the issue will be reported in the STDERR stream. Change to use ProcessBuilder instead and you can merge STDERR to STDOUT as follows:
ProcessBuilder pb = new ProcessBuilder(cmd);
// No STDERR => merge to STDOUT
pb.redirectErrorStream(true);
Process p = pb.start();
Also, no need to write much code to consume STDOUT:
try(var stdo = p.getInputStream()) {
stdo.transferTo(System.out);
}
int rc = p.waitFor();
if (rc != 0) throw new RuntimeException("test failed");
Hopefully this will explain your problem with the mvn command.
Suppose something like this:
execInCurrentShell("cd /")
System.out.println("Ran command : cd /")
is in the main() function of MyClass
So that when I run the class, I cd into the / directory
user#comp [~] pwd
/Users/user
user#comp [~] java MyClass
Ran command : cd /
user#comp [/] pwd
/
The usual way to run shell commands, that is, through the Runtime class:
Runtime.getRuntime().exec("cd /")
Won't work here because it doesn't run the command in the current shell but in a new shell.
What would the execInCurrentShell() function (the one that actually works) look like?
You won't be able to run commands that affect the current invoking shell, only to run command line bash/cmd as sub-process from Java and send them commands as follows. I would not recommend this approach:
String[] cmd = new String[] { "/bin/bash" }; // "CMD.EXE"
ProcessBuilder pb = new ProcessBuilder(cmd);
Path out = Path.of(cmd[0]+"-stdout.log");
Path err = Path.of(cmd[0]+"-stderr.log");
pb.redirectOutput(out.toFile());
pb.redirectError(err.toFile());
Process p = pb.start();
String lineSep = System.lineSeparator();
try(PrintStream stdin = new PrintStream(p.getOutputStream(), true))
{
stdin.print("pwd");
stdin.print(lineSep);
stdin.print("cd ..");
stdin.print(lineSep);
stdin.print("pwd");
stdin.print(lineSep);
};
p.waitFor();
System.out.println("OUTPUT:"+Files.readString(out));
System.out.println("ERROR WAS: "+Files.readString(err));
}
This also works for CMD.EXE on Windows (with different commands). To capture the response per command you should replace use of pb.redirectOutput() with code to read pb.getInputStream() if you really need the responses per line rather than as one file.
On Windows to start a command shell from Java program, you can do it as follows:
import java.io.IOException;
public class Command {
public static void main(String[] args) {
try {
Runtime.getRuntime().exec("cmd.exe /c start");
} catch (IOException e) {
e.printStackTrace();
}
}
}
You need to use the same approach for Linux.
I'm trying to run an AWS command within Java code (in Linux). Like always, I try to run the bash command like this in Java. But I wonder it doesn't show anything. And just prints Exited with error code : 2. When I just run aws ls help in bash, it works.
What is the problem? How to solve it?
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class TestCMD {
public static void main(String[] args) {
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.command("bash", "-c", "aws ls help");
try {
Process process = processBuilder.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
int exitCode = process.waitFor();
System.out.println("\nExited with error code : " + exitCode);
} catch (Exception e) {
e.printStackTrace();
}
}
}
The Java code is not the problem. It works fine, what you can check by replacing the command with
processBuilder.command("bash", "-c", "echo 1 2 3");
You have 2 "problems".
The first problem is that aws writes its output to stderr, not stdout.
The second problem is that aws returns 2 where 0 would be IMHO better.
You can test this on the commandline with:
aws ls help 2>/dev/null; echo $?
The problems can be fixed with
processBuilder.command("bash", "-c", "/usr/bin/aws ls help 2>&1; true");
I am new to both Java and Linux, I was trying to use some Runtime.exec() commands that would allow my program to execute commands in Linux such as "cd /mnt/" and "ls --group-directories-first" to list files and directories contained in /mnt/ but I think I am making a problem with the execution.
I tried my code to only include the "ls --group-directories-first" and it worked like a charm, only problem was, it only listed subdirectories and files in the projects folder. I wanted to make my program go to /mnt/ first so I made my command line to a command array by using exec(String[] cmdarray) format as process1 = Runtime.getRuntime().exec(new String[]{"cd /mnt/","ls --group-directories-first"}); and when I ran it on linux, it just got executed without any printed runtime errors but also without any feedback/printed lines.
Here is my code:
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class linCom {
public static void main(String args[]) {
String s;
Process p;
try {
p = Runtime.getRuntime().exec("ls --group-directories-first");
BufferedReader br = new BufferedReader(
new InputStreamReader(p.getInputStream()));
while ((s = br.readLine()) != null)
System.out.println("line: " + s);
p.waitFor();
System.out.println ("exit: " + p.exitValue());
p.destroy();
} catch (Exception e) {}
}
}
This worked and printed out:
"line: DummyFolder1
line: linCom.class
line: linCom.java
exit: 0"
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class linCom {
public static void main(String args[]) {
String s;
Process p;
try {
p = Runtime.getRuntime().exec(new String[]{"cd /mnt/","ls --group-directories-first"});
BufferedReader br = new BufferedReader(
new InputStreamReader(p.getInputStream()));
while ((s = br.readLine()) != null)
System.out.println("line: " + s);
p.waitFor();
System.out.println ("exit: " + p.exitValue());
p.destroy();
} catch (Exception e) {}
}
}
This just got executed with no printed lines.
I expected my program to just go to the /mnt/ directory and print out subdirectories and files on there, but it just got executed with no visible runtime errors and no printed lines.
I have looked at other entries but could not find any answer to my problem.
EDIT: I changed "no errors" with "no error messages" to make it clear that if program had any errors, I did not get any feedback about it.
Here's where the UNIX process model can be confusing.
What you have tries to run the program named cd /mnt/ with the first parameter of ls --group-directories-first . Unix programs can be named anything (they're just filenames) but there's no program named cd /mnt. And anyway, the cd operation is actually performed by a shell, not as a forked/execed program.
You hope to run this shell command from your Java program: cd /mnt/; ls --group-directories-first . The trouble is, Java's .exec() method does not give you a shell, so shell commands don't work.
You can try this instead. It's like running the shell command
/bin/sh -c "cd /mnt/; ls --group-directories-first"
With this, you start a shell, then tell it to run -cthe command you want.
Process p = Runtime.getRuntime().exec(new String[]{"/bin/sh",
"-c",
"cd /mnt/; ls --group-directories-first"});
But it's quite dependent on the machine where your Java program runs, so be careful.
Reference: How to invoke a Linux shell command from Java
Do not use an external process just to list files. Java has plenty of ways to do that. All of them are in the Files class. For example:
Path dir = Paths.get("/mnt");
try (Stream<Path> files = Files.list(dir).sorted(
Comparator.comparing((Path p) -> !Files.isDirectory(p))
.thenComparing(Comparator.naturalOrder()))) {
files.forEach(System.out::println);
}
Do you really need to use Runtime.exec()comands? That would make your code platafform dependent.
You could use File.listFiles():
File folder = new File("/mnt");
for (File f : folder.listFiles()) {
System.out.println(f.getName());
}
That would make the code less plataform dependent
I am trying to use this command:
git checkout `git rev-list -n 1 --before="2009-07-27 13:37" master`
Which I am running through ProcessBuilder. Now, I know that the rest of my code works, as I have tested it with other examples. But I am not sure how to split this command so that it works for ProcessBuilder.
Apparently, this method did not work:
String[] command = {"git", "checkout", "`git rev-list -n 1 --before=\"2014-01-01 12:00\" master`"};
This did not work as well:
String[] command = {"git", "checkout", "`git", "rev-list", "-n", "1", "--before=\"2014-01-01 12:00\"", "master`"};
I am also not getting any output from the console (I do InputStream), which means it's harder for me to figure out what's the issue. I am thinking the problem should be obvious to people familiar enough with ProcessBuilders, so yeah...
The rest of my code looks as such:
ProcessBuilder processBuilder = new ProcessBuilder(command);
processBuilder.directory(new File(fullPath));
process = processBuilder.start();
And the path for the directory should be correct as I checked it (it is inside of the main directory of the repository).
If by any chance I need to provide other parts of the code, I will.
The back-ticks are actually handled by the unix shell - Java doesn't process them. When using process builder you should execute the command given in the back-ticks and pass the output as parameters to the second command. A simple example is below: (using 'ls').
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.io.IOUtils;
public class ProcessBuilderTest {
public static void main(String[] args) throws IOException, InterruptedException {
// Command: ls `ls /etc/passwd /etc/services`
// Read the output from the command second ls...
ProcessBuilder pb = new ProcessBuilder("ls", "/etc/passwd", "/etc/services");
Process process = pb.start();
InputStream is = process.getInputStream();
List<String> lines = IOUtils.readLines(is, Charset.defaultCharset());
ArrayList<String> cmdParts = new ArrayList<>();
cmdParts.add("ls");
cmdParts.addAll(lines);
// Construct the second command
ProcessBuilder pbLs = new ProcessBuilder(cmdParts);
Process lsProcess = pbLs.start();
System.out.println(IOUtils.readLines(lsProcess.getInputStream(), Charset.defaultCharset()));
lsProcess.waitFor();
}
}