I have a perl script that i'm trying to execute through shell. The normal UNIX shell invocation is do is: /home/projects/bumble/script.pl --input /home/input/input.txt > /home/output/output.txt.
To do this in Java I wrote a function:
public void runCommand(String command, File directory) {
final ProcessBuilder pb = new ProcessBuilder(command);
pb.directory(directory);
final Process process = pb.start();
if(process.waitFor() != 0) {
throw new RuntimeException("ERROR");
}
}
I call this:
String command = "/home/projects/bumble/script.pl --input /home/input/input.txt > /home/output/output.txt";
File directory = new File("/home/projects/bumble");
runCommand(command,directory);
The exception I get is:
java.io.IOException: ... Cannot run program error=2, No such file or directory
I notice here: java.io.IOException: Cannot run program error=2, No such file or directory, implies that running programs given a full input with > into a completely different directory would fail. If I just run the command /home/projects/bumble/script.pl --input /home/input/input.txt, also fails with the same error. I run it directly through shell, it does work. Is there something I am missing or doing incorrectly?
Runtime.exec() traps: http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=3
2 problems with the code:
Command should be broken down into a String[] such as:
String[] command = {"perl", "/home/projects/bumble/script.pl", "--input", "/home/input/input.txt"}
Output needs to be handled seperately without the redirect ">". To do this, ProcessBuilder has Redirect. http://docs.oracle.com/javase/7/docs/api/java/lang/ProcessBuilder.Redirect.html
Programmatically the function is:
public static void runCommand(final String[] command, final File directory, final File output) throws IOException, InteruptException {
final ProcessBuilder pb = new ProcessBuiler(command);
pb.redirectErrorStream(true); //optional; easier for this case to only handle one stream
pb.redirectOutput(Redirect.to(output));
final Process p = pb.start();
if(p.waitFor != 0) {
//throw an exception / return error message
}
}
Related
Environment
Windows 10
Java 1.8
Process
I am running a 7zip's zip task.
The process takes 2 to 3 hours to complete.
Exception
java.lang.IllegalThreadStateException: process has not exited
at java.lang.ProcessImpl.exitValue(ProcessImpl.java:443)
at java.lang.ProcessImpl.waitFor(ProcessImpl.java:452at
My code
int exitValue = -1;
Process start = null;
try
{
ProcessBuilder processBuilder = new ProcessBuilder(commands);
start = processBuilder.start();
try(BufferedReader ipBuf = new BufferedReader(new InputStreamReader(start.getInputStream())))
{
String line = null;
while ((line = ipBuf.readLine()) != null)
{
LOGGER.info(line);
}
}
try(BufferedReader errBuf = new BufferedReader(new InputStreamReader(start.getErrorStream())))
{
String line;
while ((line = errBuf.readLine()) != null)
{
LOGGER.warning(line);
}
}
start.waitFor();
exitValue = start.exitValue();
}
finally
{
if (start != null)
{
start.destroy();
}
}
return exitValue;
I'm unable to find the root cause of this issue.
Note: I've tried this process with a similar demo instance on the same
machine and it works fine.
Please help me resolve this, Thanks.
There are two parts to your problem:
The JDK has a bug which causes an exception to be thrown when a Windows process returns an exit code of 259.
The command that you pass to ProcessBuilder exits with an exit code of 259 when it shouldn't.
Tackling each point in turn:
The bug in the JDK is caused by the following flawed logic in the Windows-specific implementation of Process.waitFor(): First, it waits until the process exits. Then, it calls exitValue() to get the exit value from the process. But unfortunately exitValue() gets the exit value and then checks if it's a special value that indicates the process hasn't exited. Since waitFor() knows that the process has exited, it should get the exit value directly instead of calling this method which does an unwanted check. Hopefully the JDK developers will fix this bug soon.
You should be using the command-line version of 7-zip, 7z.exe which exits with a set of well-defined exit values (so it never returns 259).
If your problem is caused by the Windows ProcessBuilder exit code 259 bug then there are workarounds available: all you need to do to make sure that your sub-process does not exit with status code 259 and Windows JRE won't report java.lang.IllegalThreadStateException.
You can easily reproduce this issue by executing the following command with Runtime.getRuntime().exec(cmd) or ProcessBuilder(cmd):
String[] cmd = {"cmd.exe /c exit /b 259"};
If you have written the code for the sub-process then just edit your code so that exit code is never set to 259.
If you have not written the code for the sub-process then a rather hacky workaround is to wrap your Java sub-process launch with a "CMD.EXE" and mini-script which adapts non-zero sub-process exit back to exit codes 0 or 1:
String[] fixed = new String[] { "cmd.exe", "/c",
"(call "+String.join(" ", cmd)+ ") || (echo !!! DETECTED ERROR!!! && exit 1)" };
Note: I'm no expert on CMD. The above fix definitely won't work for certain commands or punctuation (eg those with quotes / spaces etc), and because it runs under CMD.EXE environment settings the outcome might be different to direct launch from the calling JVM.
Here is an example class you could test with:
/** Examples to test with and without the fix:
java Status259 "cmd.exe /c exit /b 0"
java Status259 "cmd.exe /c exit /b 25"
java Status259 "cmd.exe /c exit /b 259"
java Status259 %JAVA_HOME%\bin\java -cp your.jar Status259$StatusXXX 0
java Status259 %JAVA_HOME%\bin\java -cp your.jar Status259$StatusXXX 33
java Status259 %JAVA_HOME%\bin\java -cp your.jar Status259$StatusXXX 259
*/
public class Status259 {
public static class StatusXXX {
public static void main(String ... args) {
int status = args.length > 0 ? Integer.parseInt(args[0]) : 0;
System.out.println("StatusXXX exit code: "+status);
System.exit(status);
}
}
public static int exec(String[] cmd) throws IOException, InterruptedException {
System.out.println("exec "+Arrays.toString(Objects.requireNonNull(cmd)));
ProcessBuilder pb = new ProcessBuilder(cmd);
// No STDERR => merge to STDOUT - or call redirectError(File)
pb.redirectErrorStream(true);
Process p = pb.start();
// send sub-process STDOUT to the Java stdout stream
try(var stdo = p.getInputStream()) {
stdo.transferTo(System.out);
}
int rc = p.waitFor();
System.out.println("exec() END pid="+p.pid()+" CODE "+rc +' '+(rc == 0 ? "OK":"**** ERROR ****"));
return rc;
}
public static void main(String ... args) throws IOException, InterruptedException {
// COMMENT OUT NEXT LINE TO SEE EFFECT OF DIRECT LAUNCH:
args = fixStatus259(args);
int rc = exec(args);
System.exit(rc);
}
private static String[] fixStatus259(String[] cmd) {
System.out.println("fixStatus259 "+Arrays.toString(cmd));
return new String[] {
"cmd.exe", "/c",
"(call "+String.join(" ", cmd)+ ") || (echo !!! DETECTED ERROR!!! && exit 1)"
};
}
}
I'm using ProcessBuilder to run Postgres SQL scripts and my process seems to hang, because I'm not getting the exit value.
My code has a test method that loads and executes certain SQL files.
I then run some individual queries before executing the script file whose operations I want to test on.
#Test
public void test340() throws Exception {
String filePath = testFilePath; // Script file I want to test
TreeSet<Path> set = getFilePaths();
for (Path file : set) { // Run previous scripts to setup the db
loadFileAndExecuteSQL(file.toString());
}
preCondition340(); // Run some inserts
loadFileAndExecuteSQL(testFilePath); // Now load the script file I need to test
postCondition340(); // Run some other queries to verify resultset
}
public void loadFileAndExecuteSQL(String scriptFilePath) throws Exception {
String command = String.format("psql -v ON_ERROR_STOP=1 --host %s --port %s --dbname %s --file %s",
"localhost", "5432", dbName, scriptFilePath);
List<String> commands = Arrays.asList(command.split("\\s+"));
ProcessBuilder pb = new ProcessBuilder(commands);
pb.redirectErrorStream(true);
final Process process = pb.start();
if(!process.waitFor(10000, TimeUnit.MILLISECONDS)) {
process.destroy();
}
System.out.println("Executing command " + command + process.exitValue());
}
The process seems to hang on my second call to loadFileAndExecuteSQL() Could anyone explain why this is happening and suggest how I can make sure this does not happen? Thanks.
I need to run the below comand using java but it is running fine in terminal as
svn list http://192.168.0.19/svn/cc/Branch/Jobs/tt/jobs/ --username prasadh --password prasadh2k > output.txt
But when running the same via process builder it is returning empty result.
My code:
ProcessBuilder pb = new ProcessBuilder("cmd", "C:\\Users\\dev112\\output", "svn", "list", "http://192.168.0.19/svn/cadgraf/Branch/Jobs/T0003SATHYABAMAT/Completedjobs", "--username", "prasadh", "--password", "prasadh2k", ">", "output.txt");
pb.redirectErrorStream(true);
try {
Process p = pb.start();
new Thread(new InputConsumerforImageMagick.InputConsumer(p.getInputStream())).start();
try {
System.err.println("Exited with: " + p.getErrorStream());
} catch (Exception ex) {
Logger.getLogger(AddImage.class.getName()).log(Level.SEVERE, null, ex);
}
} catch (IOException ex) {
Logger.getLogger(AddImage.class.getName()).log(Level.SEVERE, null, ex);
}
I/O redirection doesn't work well with ProcessBuilder. You should either call cmd.exe with
new ProcessBuilder("cmd", "/c", "svn ... > output.txt");
(i.e. you have to call cmd with exactly two arguments)
or you must redirect yourself, that is you need to start a background thread which reads stdout from the process and writes it to output.txt. In that case, you should use:
new ProcessBuilder("svn", "list", ...);
The former is brittle when you have spaces in arguments. So I suggest the latter even though the Java code is much more complex.
You should also have a look at Commons Exec which makes it much easier to deal with external processes.
Or with Java 7, you can use pb.redirectOutput();
Don't go through cmd. Just run the command directly:
final Path cwd = Paths.get("c:\\Users\\dev112\\output");
Files.createDirectories(cwd);
final Path outfile = cwd.resolve("output.txt");
final ProcessBuilder pb = new ProcessBuilder("svn", "list",
"http://192.168.0.19/svn/cadgraf/Branch/Jobs/T0003SATHYABAMAT/Completedjobs",
"--username", "prasadh", "--password", "prasadh2k");
pb.directory(cwd.toFile());
pb.redirectOutput(outfile.toFile());
final int retcode = pb.start().waitFor();
What is more, why do you get the process' standard output if you output to a file? Do one or the other, not both. If you output to a file then read the contents of that file after the command is executed.
The sample above outputs to a file; just open a stream to that file afterwards using Files.newInputStream(outfile) (well, that is, if retcode is 0; if it isn't, your command has ended with an error; which also means that you should redirect stderr somewhere, too)
This works for me:
String command = "svn list http://192.168.0.19/svn/cc/Branch/Jobs/tt/jobs/ --username prasadh --password prasadh2k";
ProcessBuilder processBuilder = new ProcessBuilder(command.split());
processBuilder.redirectOutput(new File("C:/Users/dev112/output/", "output.txt"));
processBuilder.start();
I am trying to backup MySQL DB using ProcessBuilder in Java but, I get this error.
"!Cannot run program "C:\Program Files\MySQL\MySQL Server 5.5\bin": CreateProcess error=5, Access is denied".
Here is my Code.
public static String backupDb() {
String resp=null;
try {
System.out.println("Started........");
ProcessBuilder builder = new ProcessBuilder("C:\\Program Files\\MySQL\\MySQL Server 5.5\\bin", "mysqldump -u root -pmypass mydb> c:\\backup\\mybackup.sql");
builder.redirectErrorStream(true);
Process p = builder.start();
} catch(Exception e) {
resp="!"+e.getMessage();
}
return resp;
}
Where could I be going wrong?
There are a few things you have to do for this to work:
open a terminal/console to run the mysql dump command in, else the redirection operator(>) won't work.
check that required folders from file path exist. For instance, if you want to backup your database in C:\\foo\bar\foobar\backup.sql but one of the C:\\foo, C:\\foo\\bar, C:\\foo\\bar\\foobar folders doesn't exist, you'll get an error
Adjust the path to mysqldump so that folder names containing white spaces are wrapped in " ", else you'll get awkward errors, such as : 'C:\Program' is not recognized as an internal or external command
read the error stream in case of error, else your process will hang. A return value of 0 indicates success.
Here is a tested version including all the things above. I'm passing the filepath as a parameter, because it's more flexible this way.
public static void backupDb(final String mysqlDumpFilePath)
throws IOException, InterruptedException {
String folderPath = mysqlDumpFilePath.substring(0, mysqlDumpFilePath.lastIndexOf("\\"));
File folder = new File(folderPath);
if (!folder.exists()) {
folder.mkdirs(); // 2
}
File f = new File(mysqlDumpFilePath);
ProcessBuilder builder = new ProcessBuilder("cmd.exe", "/c", "C:\\\"Program Files\"\\MySQL\\\"MySQL Server 5.5\"\\bin\\mysqldump -u root -pmypass mydb > "
+ f.getAbsolutePath()); //1 & 3
Process exec = builder.start();
int retCode = exec.waitFor();
if (retCode != 0) { //4
// something went wrong
InputStream errorStream = exec.getErrorStream();
byte[] buffer = new byte[errorStream.available()];
errorStream.read(buffer);
System.out.println(new String(buffer));
}
}
I have a java program in which i have this code,
JOptionPane.showConfirmDialog(null, "TEST");
String pathToJar = ClassRewriter.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath()+"ClassRewriter.class";
System.out.println(pathToJar);
ProcessBuilder pb = new ProcessBuilder("javaw "+pathToJar);
Process process = pb.start();
I have a java program in a class called classrewriter, and i am trying to start that program from within its main method but it doesnt seem to work.
public int runCommand(String command) throws Exception
{
Process s= Runtime.getRuntime().exec(command);
return s.exitValue();
}
So you write:
runCommand("java -jar "+pathToJar);
But it isn't recommend, because it isn't supported on all OSes (for example linux or mac).