I have a simple GUI which selects an executable and a batch file. Clicking "run" should launch a command line instance, then run the executable given the selected batch. However, hiccups seem to show up at different points. This is the relevant code snippet:
String[] commands = {"cmd.exe", "/c", "C:\\Xilinx\\14.7\\ISE_DS\\settings64.bat && cd /d ",
"\"" + simFile.getParent() + "\"", " && ping localhost && ",
"\"" + jTextField1.getText() + "\"", " -tclbatch \"" + jTextField2.getText() + "\""};
ProcessBuilder simBuilder = new ProcessBuilder(commands);
simBuilder.redirectErrorStream(true);
Process simulation = simBuilder.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(simulation.getInputStream()));
String line;
while (true) {
line = reader.readLine();
if (line == null)
break;
System.out.println(line);
}
I chose to create a process through a ProcessBuilder rather than "Runtime.getRuntime().exec" because having the command and arguments as a String array is more readable and manageable. I took a look through the documentation of Runtime, Process, and ProcessBuilder. I also searched for similar questions, the following being the closest: Run cmd commands through Java. However, I'm still having issues getting all commands to run properly, if it all. First point: The program successfully executes the commands until "ping", which I placed to determine where the issue occurs. I get the cmd output in the console through the BufferedReader just fine. However, the next command, which should run the executable indicated by "jTextField1.getText()", gives an error of "The filename, directory name, or volume label syntax is incorrect" although I made sure the path is within escaped double quotes to account for spaces. Is it something in my syntax? Something to do with where the double ampersands are placed? Does every separate command with its argument need to be its own string in the array? I tried that and different things, but it always seems to result in an error.
You should check that your path names are correct, and try the cmd as one parameter value not as comma separated after cmd.exe /c. This will ensure the arguments are passed to CMD correctly as a single argument for the CMD shell to handle:
import java.nio.file.Files
System.out.println("Files.isDirectory(simFile.getParent())="+Files.isDirectory(simFile.getParent()));
System.out.println("Files.isExecutable(jTextField1.getText())="+Files.isExecutable(Path.of(jTextField1.getText())));
String cmd = "C:\\Xilinx\\14.7\\ISE_DS\\settings64.bat && cd /d "+
"\"" + simFile.getParent() + "\" && ping localhost && "+
"\"" + jTextField1.getText() + "\" -tclbatch \"" + jTextField2.getText() + "\"";
String[] commands = {"cmd.exe", "/c", cmd};
Related
I have written a JavaFX program that runs a python script that uses google console search api to get data. It works fine before I deploy it as a standalone program (.exe). After I install the program it runs fine until I click the button that runs the script; it does nothing.
After the button is clicked it succesfully checks if the python is installed or not, after that the problem occurs when it tries the run the script. I have specified the path like this, "./src/com/fortunamaritime/" and I think this might be the problem.
private static final String ANALYTICS_PATH ="./src/com/fortunamaritime/";
inside the method
//set up the command and parameter
String[] cmd = new String[3];
cmd[0] = "python";
cmd[1] = "analytics.py";
cmd[2] = "https://fortunamaritime.com.tr";
String command = cmd[0] + " " + cmd[1] + " " + cmd[2] + " " +
startDate + " " + endDate;
ProcessBuilder builder = new ProcessBuilder("cmd.exe", "/c", "cd " + new
File(ANALYTICS_PATH).getAbsolutePath() + " && " + command);
builder.redirectErrorStream(true);
Process p = builder.start();
I have redirected console output to a .txt file to see what was happening and there's only one line that said "system cannot find the path specified".
I have switched from absolute path to relative path and it produced this error:
The filename, directory name, or volume label syntax is incorrect.
I also printed this line to console
this.getClass().getResource("/com/fortunamaritime/analytics.py")
and the result is:
jar:file:/D:/IdeaProjects/FortunaWebTracker/out/artifacts/FortunaWebTracker/FortunaWebTracker.jar!/com/fortunamaritime/analytics.py
Is it possible to access the inside of the jar file and run the script from there via cmd through stripping parts of this URL?
Copy the script to a (maybe temporary) file and run it from that file.
You can get the script as InputStream with Main.class.getResourceAsStream(String resc) (relative to main) or Main.class.getClassLoader().getResourceAsStream(String resc) (relative to root)
You may use java.nio.file.Files#createTempFile(String,String and delete that file on exit.
You can also copy the script into the user home or relative to the exe (and don't delete it).
For example, you could do this:
File dir=new File("./resc");
if(!dir.exists()) {
dir.mkdirs();
}
File analyticsFile=new File(dir,"analytics.py");
if(!analyticsFile.exists()) {
/*
* alternative:
* this.getClass().getResourceAsStream("com/fortunamaritime/analytics.py")
*/
try(InputStream analyticsIn = this.getClass().getClassLoader().getResourceAsStream("analytics.py")){
Files.copy(analyticsIn, analyticsFile.toPath());
}
}
/*
* alternative: String command=String.join(" ",new String[]{"python",analyticsFile.getAbsolutePath(),"https://fortunamaritime.com.tr"});
*/
String command="python "+analyticsFile.getAbsolutePath()+" https://fortunamaritime.com.tr";
/*
* alternative (without error redirection): Process p=Runtime.getRuntime().exec(command);
*/
ProcessBuilder builder = new ProcessBuilder(command);
builder.redirectErrorStream(true);
Process p=builder.start();
I have solved my problem.
Here's the code sample first, explanation will follow;
String path = new
File(Main.class.getProtectionDomain().getCodeSource().getLocation()
.toURI()).getPath();
path=path.substring(0,path.length()-21);
// set up the command and parameter
String[] cmd = new String[3];
cmd[0] = "python";
cmd[1] = "analytics.py";
cmd[2] = "https://fortunamaritime.com.tr";
String command0="jar xf FortunaWebTracker.jar" +
"com/fortunamaritime/analytics.pycom/fortunamaritime/client_secrets.json" +
" com/fortunamaritime/webmasters.dat";
String command1 = cmd[0] + " " + cmd[1] + " " + cmd[2] + " " + startDate + " " +
endDate;
ProcessBuilder builder = new ProcessBuilder("cmd.exe", "/c", "cd " + path + " && " +
command0+"&&"+"cd "+path+"\\com\\fortunamaritime"+" && "+command1);
builder.redirectErrorStream(true);
Process p = builder.start();
My first mistake was to use absolute path, and the second problem arose when I got the path I wanted via:
File(Main.class.getProtectionDomain().getCodeSource().getLocation()
.toURI()).getPath();
//Jar's name is 21 characters long, minus that and I get the directory path
path=path.substring(0,path.length()-21);
The second problem was unpacking elements inside the jar so I used this command in cmd:
jar xf FortunaWebTracker.jar
com/fortunamaritime/analytics.py
com/fortunamaritime/client_secrets.json
com/fortunamaritime/webmasters.dat
then I could get it to work.
I am trying to run a command to read a string from a file inside a remote address (and I'm sure the file is there), this command works when I run it on the bash but it doesn't work when I run it in my java code.
Runtime rt = Runtime.getRuntime();
String[] command;
String line;
try {
command = new String[] {"sh", "-c", "\"sshpass " + "-p " + password + " ssh " + user + "#" + ip + " 'cat " + file.getAbsolutePath() + "'\"" };
Process mountProcess = rt.exec(command);
mountProcess.waitFor();
bufferedReader = new BufferedReader(new InputStreamReader(mountProcess.getInputStream()));
while ((line = bufferedReader.readLine()) != null) {
user_list.put(user, line);
}
bufferedReader.close();
bufferedReader = new BufferedReader(new InputStreamReader(mountProcess.getErrorStream()));
while ((line = bufferedReader.readLine()) != null) {
LOGGER.debug("Stderr: " + line);
}
bufferedReader.close();
} catch ...
No line is added to my user_list (so the line from getInputStream is null) and I get the following error from the logger in the code:
Stderr: sh: 1: sshpass: not found
If I use the exact same command on the bash it works and it prints the string I need.
sshpass -p password ssh remote#192.168.1.10 'cat /home/ID/ID'
Anyone knows why this is happening? thanks!
I'd suggest you don't need to use sh to wrap your command. Try
command = new String[] {"sshpass", "-p", password, "ssh", user + "#" + ip, "cat " + file.getAbsolutePath() };
If you need to use sh, then remove the escaped double quotes from the command string: you are sending those as literal characters:
command = new String[] {
"sh",
"-c",
String.format("sshpass -p %s ssh %s#%s 'cat %s'", password, user, ip, file.getAbsolutionPath())
};
If you're still getting "command not found", then you need to either specify the full path to sshpass, or ensure that its directory is in your PATH.
When doing this command with java the user is tomcat8 instead of root (when used in the bash terminal)
The solution that worked for me included some flags:
String.format("/usr/local/bin/sshpass -p %s /usr/bin/ssh -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no %s#%s 'cat %s'", password, user, ip, file.getAbsolutePath());
I use java to excute a command line to create a database, i get an error when i excute this piece of code:
private final String POSTGRES_PATH = "\"C:\\Program Files\\PostgreSQL\\9.3\\bin\\psql.exe\"";
private final String DATA_BASE = "bd_name";
private void creerDataBase() {
String command = this.POSTGRES_PATH + " -U postgres -d postgres -c \"CREATE DATABASE " + this.DATA_BASE + "\"";
System.out.println("command = " + command);
String creerBDD = executerCommande(command);
System.out.println("Resultat : " + creerBDD);
}
public String executerCommande(String command) {
String line;
String resultat = "";
try {
ProcessBuilder builder = new ProcessBuilder("cmd.exe", "/c", command);
builder.redirectErrorStream(true);
Process p = builder.start();
BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream()));
while (true) {
line = r.readLine();
if (line == null) {
break;
}
resultat += line + "\n";
}
} catch (Exception e) {
System.out.println("Exception = " + e.getMessage());
}
return resultat;
}
I get this result:
command = "C:\Program Files PostgreSQL\9.3\bin\psql.exe"\ -U postgres -d postgres -c "CREATE DATABASE bd_name"
and this error:
'C:\Program' n'est pas reconnu en tant que commande interne
This mean that Program is not an intern command.
but when i excute this command in CMD it work fine?
Is there any way to Build this Path because the ProcessBuilder not recognize C:\Program Files
Thanks for #Aaron his idea helps me so i solved this problem using this:
private final String POSTGRES_PATH = "C:\\PROGRA~1\\PostgreSQL\\9.3\\bin\\psql.exe";
this C:\\PROGRA~1 inteed of this: C:\\Program Files
A possible solution would be to remove the path (with spaces) from your constant field and use the directory method:
Sets this process builder's working directory. Subprocesses subsequently started by this object's start() method will use this as their working directory. The argument may be null -- this means to use the working directory of the current Java process, usually the directory named by the system property user.dir, as the working directory of the child process.
Changing your code to:
private final String POSTGRES_DIR = "C:\\Program Files\\PostgreSQL\\9.3\\bin\\"
private final String POSTGRES_COMMAND = "psql.exe";
....
ProcessBuilder builder = new ProcessBuilder("cmd.exe", "/c", command).directory(new File(POSTGRES_DIR));
.....
Don't run cmd.exe if you want to run a separate binary program.
cmd.exe is for scripts like *.cmd or *.bat
With cmd.exe you have to pass your command as an argument of CMD, and you should manage all OS-specific pitfalls like long paths with whitespaces, quotes inside qoutes etc.
Instead, you had better run psql itself.
The ProcessBuilder takes a command and all the arguments as a list of separate strings. And ProcessBuilder is clever enough to do all the necessary magic with quotes and whitespaces by itself.
And take attention to the list of arguments - shells separate arguments by whitespaces, while psql might recognize the sequence of strings as a single argument.
We may assume that -U postgress is a single argument for psql, but for shell (cmd.exe in our case) these are two separate arguments - -U and postgress, so we should pass them to the ProcessBuilder separately
So the better way to run psql is to run it directly, something like that:
new ProcessBuilder("C:\\Program Files\\PostgreSQL\\9.3\\bin\\psql.exe",
"-U", "postgres",
"-d", "postgres",
"-c", "\"CREATE DATABASE " + this.DATA_BASE + "\"");
What you could try is instead of the space between program and files is %20 or \s.
So like:
command = "C:\\Program%20Files\\PostgreSQL\\9.3\\bin\\psql.exe"
or
command = "C:\\Program\sFiles\\PostgreSQL\\9.3\\bin\\psql.exe"
I hope one of them works for you, please let me know
EDIT: use double \ to get it to recognize the \
Update 3
Progress! I wrote a CMD script which takes three parameters:
the absolute path to sqlite3cipher.exe
the absolute path to testdb
the absolute path to statements.sql
All the script does is execute the command
path\to\sqlite3cipher.exe path\to\testdb < path\to\statements.sql
This script is being called from within my Java code. And it works like a charm! At least with all the files residing in a folder in my home directory. Having the files in a subfolder of C:\Program Files doesn't work so there might be an issue with privileges which I have to work out with the administrator.
Update 2
I've created a dummy SQL file in my home directory. The code should now read the SQL file from and write the database file to my home directory.
Again omitting cmd /c in the command reads and writes the first line from the SQL file but stops after that (execution of the whole program comes to a halt).
Executing the command including cmd /c reads and writes every single line of the SQL file but once EOF is reached (in which case nothing will be written of course) I again get the Access denied message.
Update 1
Taking skarist's comment into account the following command (omitting cmd /c as it isn't mandatory for executing exe-files after all)
String cmdForRuntime = "\"" + prog.getAbsolutePath() + "\" \"" + fDb.getAbsolutePath() + "\"";
which spelled out looks like
"C:\Program Files\Apache Software Foundation\bin\sqlite3cipher.exe" "C:\Program Files\Apache Software Foundation\pages\dbdir\testdb"
doesn't produce any output. Also the code stops executing, so I assume there's something wrong with the I/O?
Problem
In order to encode a database I want to execute sqlite3cipher.exe on the command line in Java.
I ran into quite some diverging results for various approaches. At first the setup (notice that this is a testing environment where right now nothing can be changed regarding where files are located):
sqlite3cipher.exeresides in C:\Program Files\Apache Software Foundation\bin\
an empty file testdbresides in C:\Program Files\Apache Software Foundation\pages\dbdir\
a file containing SQL statements statements.sql resides in the same folder as testdb does
Executing sqlite3cipher.exe -help on the command line prints the usage. Here is an exerpt:
C:\Program Files\Apache Software Foundation\bin\sqlite3cipher.exe [OPTIONS] FILENAME [SQL]
FILENAME is the name of an SQLite database. A new database is created if the file does not previously exist.
Executing "C:\Program Files\Apache Software Foundation\bin\sqlite3cipher.exe" "C:\Program Files\Apache Software Foundation\pages\dbdir\testdb" < "C:\Program Files\Apache Software Foundation\pages\dbdir\statements.sql" on the command line has the desired effect: testdb now is an encrypted database according to the SQL statements that were put in through the statements.sql.
Now in Java my code looks like this:
String cmdForRuntime = "cmd /C \"" + prog.getAbsolutePath() + "\" \"" + fDb.getAbsolutePath() + "\" < \"" + fSqlScript.getAbsolutePath() + "\"";
Process process = Runtime.getRuntime().exec( cmdForRuntime );
BufferedReader stdIn = new BufferedReader(new InputStreamReader(process.getInputStream()));
String s;
while ((s = stdIn.readLine()) != null) {
logger.info("OUTPUT FROM PROG: " + s);
}
if (process.waitFor() == 0) {
logger.info("Process finished");
}
else {
logger.error("Error!");
// Get input streams
BufferedReader stdError = new BufferedReader(new InputStreamReader(process.getErrorStream()));
System.out.println("Standard error: ");
while ((s = stdError.readLine()) != null) {
System.out.println(s);
}
}
where prog , fDb and fSqlScript are of type File and represent sqlite3cipher.exe, testdb and statements.sql respectively.
I'm not well versed with Process.execute() but what I gathered from reading about it is that cmd /c as part of the command is required on Windows (the test server is running Windows 8) in order to start the command interpreter.
On execution this code returns Access denied on the command line (via the process's streams). Executing this exact command (including cmd /c) on the command line directly returns the same result: Access denied. Even if I start the command line as administrator.
Leaving cmd /c out of the command (because it had worked without on the command line before), I get the message that there are too many options.
On further investigation, I learned that this problem is caused by the < operator, used in the command to redirect the input. The exec() method won't recognize < as an operator and it was suggested to use the process's OutputStream. So I changed my code to look like this:
String cmdForRuntime = "cmd /c \"" + prog.getAbsolutePath() + "\" \"" + fDb.getAbsolutePath() + "\"";
Process process = Runtime.getRuntime().exec( cmdForRuntime );
BufferedWriter bufferedwriter = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
BufferedReader br = new BufferedReader(new FileReader(fSqlScript.getAbsolutePath()));
BufferedReader stdIn = new BufferedReader(new InputStreamReader(process.getInputStream()));
String s;
String fileInput = br.readLine();
while (fileInput != null) {
bufferedwriter.write(fileInput);
while ((s = stdIn.readLine()) != null) {
logger.info("OUTPUT FROM PROG: " + s);
}
fileInput = br.readLine();
}
if (process.waitFor() == 0) {
logger.info("Process finished");
}
else {
logger.error("Error!");
// Get input streams
BufferedReader stdError = new BufferedReader(new InputStreamReader(process.getErrorStream()));
System.out.println("Standard error: ");
while ((s = stdError.readLine()) != null) {
System.out.println(s);
}
}
As I understand it is important to read what the external program prints out so it won't block or stop working. That's why I read a line from statements.sql (through the BufferedReader br) and write it to the process's OutputStream. Then I read what the program will print out (logging that output). If there is nothing (left) to read I read the next line from the statements.sql. This will be repeated until there is nothing left to read from said file.
If I execute this code, my logger will log the following:
INFO Executing command: cmd /c "C:\Program Files\Apache Software Foundation\bin\sqlite3cipher.exe" "C:\Program Files\Apache Software Foundation\pages\dbdir\testdb"
ERROR Error!
Standard error:
Access denied
In this case, the code at least terminates resulting in an empty file testdb. In another approach I left cmd in the command but skipped the /c resulting in following output:
INFO Executing command: cmd "C:\Program Files\Apache Software Foundation\bin\sqlite3cipher.exe" "C:\Program Files\Apache Software Foundation\pages\dbdir\testdb"
INFO OUTPUT FROM PROG: Microsoft Windows [Version 6.3.9600]
INFO OUTPUT FROM PROG: (c) 2013 Microsoft Corporation. Alle Rechte vorbehalten.
INFO OUTPUT FROM PROG:
The output is the same you get when you type cmd on the command line. And that's where execution stops.
tl;dr
execution on command line directly works like a charm
exact same command executed from Java doesn't work
command prepended by cmd /c (as it apparently should be) and usage of the process's InputStream and OutputStream doesn't work: Access denied
I'm not sure what to do next, so I sincerely hope someone can help me.
So I solved the problem with the workaround I mentioned in update 3.
My Java code now looks like this (besides the cmdForRuntime variable it should be pretty much the same code as in the beginning, when I asked this question):
try {
File fDb = new File(this.inputDirectory + File.separator + dbName);
String cmdForRuntime = "\"" + cmdScript.getAbsolutePath() + "\"" + " " + // the script to be executed
"\"" + prog.getAbsolutePath() + "\"" + " " + // sqlite3cipher.exe
"\"" + fDb.getAbsolutePath() + "\"" + " " + // testdb
"\"" + fSqlScript.getAbsolutePath() + "\""; // statements.sql
logger.info("Executing command: " + cmdForRuntime );
Process process = Runtime.getRuntime().exec( cmdForRuntime );
BufferedReader stdIn = new BufferedReader(new InputStreamReader(process.getInputStream()));
String s;
while ((s = stdIn.readLine()) != null) {
logger.info("OUTPUT FROM PROG: " + s);
}
if (process.waitFor() == 0) {
logger.info("Process finished");
}
else {
logger.error("!!!! error !!!!");
// Get input streams
BufferedReader stdError = new BufferedReader(new InputStreamReader(process.getErrorStream()));
System.out.println("Standard error: ");
while ((s = stdError.readLine()) != null) {
System.out.println(s);
}
}
}
catch(Exception ioe) {
ioe.printStackTrace();
}
cmdForRuntime will read something like this (I will omit the paths, the only add to the confusion):
"SQLiteCipher.cmd" "sqlite3cipher.exe" "testdb" "statements.sql"
The content of the script looks like this:
%1 %2 < %3
This will invoke the sqlite3cipher.exe with the parameter testdb and a redirected input coming from statements.sql.
Critical is that the location of the script as well as the location of the exe is not a system folder.
If all these criteria are met everything works like a charm.
You are having User Account Control issue.
It seems like the program is running in non-elevated mode, to make changes to system directories the application need to run in elevated mode.
Running you program in elevated mode should solve the problem.
I want to create a full cross-plateform console in Java.
The problem I have is when I use the cd command, the path is reset. For example, if I do cd bin, then cd ../, I will execute the first one from the directory of my app and the second one exactly from the same directory.
If I want to go to a specific folder and execute a program I have to do something like that:
cd C:\mydir & cd bin & start.exe
What I want to do is to split this cmd in different parts:
cd C:\mydir then cd bin then start.exe
How can I do that? Is there a way to store the current cd path and use it then?
Here is the code I use:
String[] cmd_exec = new String[] {"cmd", "/c", cmd};
Process child = Runtime.getRuntime().exec(cmd_exec);
BufferedReader in = new BufferedReader(new InputStreamReader(child.getInputStream()));
StringBuffer buffer = new StringBuffer();
buffer.append("> " + cmd + "\n");
String line;
while ((line = in.readLine()) != null)
{
buffer.append(line + "\n");
}
in.close();
child.destroy();
return buffer.toString();
It executes the command, then return the content of the console. (This is for windows for the moment).
If you want to run a command from a specific directory, use ProcessBuilder instead of Runtime.exec. You can set the working directory using the directory method before you start the process. Don't try to use the cd command - you're not running a shell, so it doesn't make sense.
If you do cd you don't want to execute it. You just want to check if the relative path exists and then change a
File currentDir
to that directory. So I would propose you split your commands up in three: cd, dir/ls and other stuff. cd changes the dir, as I mentioned, by using a File currentDir, dir should just get folders and files of currentDir and list them and then the rest you should just execute as you do know.
Remember you can split a command string by "".split("&"); this way you can do "cd C:\mydir & cd bin & start.exe".split("&"); => {"cd C:\mydir", "cd bin", "start.exe"} and then you can execute them in order.
Good luck.
Thanks to Mads, I was able to do the trick:
Here is the code I used:
if (cmd.indexOf("cd ") >= 0)
{
String req_cmd = cmd.substring(0, 3);
String req_path = cmd.substring(3);
if (req_path.startsWith(File.separator) || req_path.substring(1, 2).equals(":"))
path = req_path;
else
if (new File(path + cmd.substring(3)).exists())
path += cmd.substring(3);
else return "[Error] Directory doesn't exist.\n";
if (!path.endsWith(File.separator)) path += File.separator;
cmd = req_cmd + path;
}
else cmd = "cd " + path + " & " + cmd;
Then you can execute the command calling:
Runtime.getRuntime().exec(new String[] {"cmd", "/c", cmd});
Don't forget to add this attribute in your class:
private static String path = System.getProperty("user.dir") + File.separator;