I know that this question looks a bit similar to this, but mine relates to Windows and in a crucial way. I have the following piece of code:
public static void main(String[] args) {
//String command = "\"psql --version\"";
String psqlPath = "\"C:\\Program Files\\PostgreSQL\\10\\bin\\psql.exe\"";
String command = "\""+psqlPath+" --version \"";
runPostgresqlTableCreator("start cmd.exe /k "+command);
}
private static void runPostgresqlTableCreator(String command) {
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.command("cmd.exe", "/c", command);
try {
Process process = processBuilder.start();
process.waitFor();
} catch (Exception ex) {
ex.printStackTrace();
}
}
This code works using psqlPath whereby it launches a cmd and prints the PostgresQL version as psql (PostgreSQL) 10.7. However, with using psql --version, it says 'psql' is not recognized as an internal or external command,
operable program or batch file. Running psql --version on a cmd launched by me prints out the version as well. The environment variables are also set properly. From the link provided, the answer states that Java seems to have a problem recognizing the psql path from environment variables (though String command = "\"java -version\"" displays the Java version).
For Linux, it is an okay thing to put the full path of /usr/local/bin/psql, because in most cases, this will always be the path. However, in Windows, users can decide to install PostgresQL in any directory and in any disk volume, making it difficult to get the full path to psql.exe. In my desktop application project using JavaFX, I need to run some psql commands but for something else and not version. I do not wish to set a place where clients can put the path to psql.exe on the interface.
My question is thus, is there a workaround to making psql work without using full path? (Please note that it is not the Postgresql version that I am interested in).
You can use psql without using the full path every time by adding the full path to psql to your system variables. You can do this by going to the control panel, opening the Advanced system settings, clicking on "Environmental Variables" in system variables, and editing the environmental variables. Once this is done, psql can be referenced by simply typing "psql" as the path.
Note: do not delete the other variables. Add the path to psql to the list of environmental variables.
Here is a java document explaining adding to the path as well:
https://www.java.com/en/download/help/path.xml
Once the environmental variable is set you can read it using the System.getenv(String name) method to access a specific variable. You can also use System.getenv() to return a map of all the environmental variables
System.getenv() JavaDoc:https://docs.oracle.com/javase/8/docs/api/java/lang/System.html#getenv--
Related
Scenario
While executing a series of commands using the ProcessBuilder I noticed I am currently not able to set an environment variable such that it remains "known" after a set of commands has been executed.
Question
How do I recreate the effects* of the export TASKDDATA=/var/taskd command in a .jar file?
Attempt 0
ProcessBuilder environment variable in java Provides a way to set the environment variable for each specific command, but when I execute the .jar of that solution and check if the environment variable $u is still set after execution, I find it is not. Whereas $TASKDDATA does remain set after execution. To illustrate:
a#DESKTOP-desktopName:/mnt/e$ echo $TASKDDATA
a#DESKTOP-desktopName:/mnt/e$ TASKDDATA=/var/taskd
a#DESKTOP-desktopName:/mnt/e$ echo $TASKDDATA
/var/taskd
a#DESKTOP-desktopName:/mnt/e$ sudo java -jar autoInstallTaskwarrior.jar
[sudo] password for a:
Process ended with rc=0
Standard Output:
util/
Standard Error:
a#DESKTOP-desktopName:/mnt/e$ echo $TASKDDATA
/var/taskd
a#DESKTOP-desktopName:/mnt/e$ echo $u
Attempt 1
For a single command the environment variable can be using the solution I wrote in: Java ProcessBuilder how to get binary output from command. However that does not conserve the taskvariable for a second command in which it needs to be set again. However, when the export command is used, the environment variable is not required to be set again. Specifically this difference comes to light when the java code is finished and the user wants to enter any additional commands that require the environment variable. In that case, the user needs to first type the export command again.
Attempt 2
An additional difference occurs when a new shell is opened to acquire root priviliges with sudo -s. Not only does setting the environment within the .jar file require setting it again for every separate command but additionally the environment variable is not passsed along to the new shell with root priviliges. For example executing the following commands:
commandLines[53] = new String[4];
commandLines[53][0] = "sudo";
commandLines[53][1] = "-s";
commandLines[53][2] = "taskdctl";
commandLines[53][3] = "start";
commands[53].setCommandLines(commandLines[53]);
commands[53].setEnvVarContent("/var/taskd");
commands[53].setEnvVarName("TASKDDATA");
commands[53].setWorkingPath("/usr/share/taskd/pki");
commandLines[54] = new String[5];
commandLines[54][0] = "sudo";
commandLines[54][1] = "-s";
commandLines[54][2] = "task";
commandLines[54][3] = "sync";
commandLines[54][4] = "init";
commands[54].setCommandLines(commandLines[54]);
commands[54].setEnvVarContent("/var/taskd");
commands[54].setEnvVarName("TASKDDATA");
commands[54].setWorkingPath("/usr/share/taskd/pki");
returns:
53RUNNINGCOMMAND=sudo -s taskdctl start
The TASKDDATA variable must be set.
54RUNNINGCOMMAND=sudo -s task sync
Could not connect to 0.0.0.0 53589
Sync failed. Could not connect to the Taskserver.
Syncing with 0.0.0.0:53589
Note 1
Setting the environment variable $TASKDDATA=/var/taskd before executing the .jar with: TASKDDATA=/var/taskd sudo java -jar autoInstallTaskwarrior.jar does not ensure the environment variable $TASKDDATA remains available after the execution of the .jar file. Furthermore it is beyond the scope of the question as it is not set within the .jar file but outside the .jar file.
Note 2
I understood it is not appropriate to use the export command as a command that is executed by the processbuilder, much like the cd command.
*That is why the question focuses on reproducing the "longterm/lasting" effect/availability of setting the environment variable, rather than "how to execute the export command.".
when I execute the .jar of that solution and check if the environment variable $u is still set after execution, I find it is not.
That is the expected behavior. Some operating systems support the concept of global "environment" variables. But not UNIX. In UNIX like operating systems every process has its own private copy of environment vars. A process cannot modify the environment of another process. This is also why changing the current working directory in a process does not change the cwd of its parent process. Each process has its own cwd.
The usual way to workaround that limitation is for the child process to write the var=val pairs to stdout and the parent shell then evaluates that output to set the vars in its environment. For illustration assume the command is the following shell script, named myscript.sh, rather than a Java program:
#!/bin/sh
echo VAR_A=val_a
echo VAR_B=val_b
Then the parent shell does
export $(./myscript.sh)
I'm a little curious about the behaviour of Runtime's exec() method when I run mysqldump. I'm running the following command:
mysqldump --user=root --hex-blob [database name] -r [path to sql file]
What I'm wondering is, where does Runtime search for the program mysqldump.exe?
I see that some people supply the whole file path to mysqldump.exe when executing it using Runtime. Why is this?
The reason why I'm curious is because I have two scenarios:
On one windows machine, if I open run and type "cmd" it will open a command window with the default location C:/. Running the mysqldump command on this machine works.
On another windows machine, if I open run and type "cmd" it will open a command window with the default location H:/. Running the mysqldump command on this machine fails. Java's Runtime cannot find the file mysqldump.exe.
Is it possible that the two windows machines have different default drives and if I don't supply the full path to mysqldump.exe, the system will look in the default driver?
Thanks in advance!
As mentioned in the documentation:
Starting an operating system process is highly system-dependent. Among the many things that can go wrong are:
The operating system program file was not found.
Access to the program file was denied
The working directory does not exist.
What I would suggest is starting with a ProcessBuilder, something like:
ProcessBuilder pb = new ProcessBuilder("mysqldump ...");
Map<String, String> env = pb.environment();
env.put("PATH", env.get("PATH") + ";Path/to/mysqldump");
try {
Process process = pb.start();
//some code
} catch(IOException e){
}
This way you ensure the environment variable is correctly set.
Everywhere I search, it says you can get an environment variable by using System.getenv(str).
It's not working for me. Here's what I am doing:
OS : Mac OS x 10.7
Java 1.6.x
If I do export abc=/hello/ in my terminal and then echo $abc, it gives me the variable. If I close the terminal, reopen it again and do echo $abc, it's gone. To overcome this, I edited my .bash_profile file and inserted export abc=/hello/. Close the terminal, do echo $abc and it works. So I understood that the env variable is permanent now.
Now if in my java console app, I print System.getenv("abc"), it returns null. What am I missing?
The reason that you needed to put the export in your .bash_profile is that setting environment variables in a shell only persist the variables in that shell, and - since you used export - to children of that shell, or in other words, other programs launched by that shell.
If you're running your java code from Eclipse, and you launch Eclipse from a shell with your environment variables set, then your program should see the added environment variables. To launch Eclipse from the shell, you'll need to use the OS X open command:
$ open /Applications/eclipse/Eclipse.app
Alternately, you can set the environment variables within your Eclipse project, and you'll need to do this if you're not launching Eclipse from a shell with the proper environment. In the Run Configurations dialog, look for a tab named Environment. Here you'll find a table for adding environment variables that will be passed to your program.
It's better to add the environment variables to the Run Configuration since that way they'll always be available to your project. Your code doesn't actually care where the environment variables are coming from, and adding them to the project is simpler, and will work the same way on different platforms.
Of course, when you run your program outside Eclipse, you'll need to make sure that the same environment variables exist in the shell where you e.g. run java.
Eclipse does not use the system's env variables unless launched directly from the shell (which is how it is generally launched, by clicking its icon). In that case you will have to explicitly set the required env variables in the environment tab of the run configuration of the program.
I too faced The same issue , I resolved it this way.
Open Terminal
cd to the folder where eclipse.app is located E.g cd /Users/Shared/eclipse/jee-2020-09
Type open Eclipse.app/
Eclipse will now open and will be able to access the system environment variables as well.
Check it using the code:
System.getenv().forEach((k, v) -> {
System.out.println("ENV : " + k + ":" + v);
});
The code in question has worked in more or less the exact same configuration on earlier releases of Windows, however, it is not known to have run on Windows 7 YET! That's what I need to solve now.
Briefly, some C code performs some configuration and security checks before launching a java program, passing some data that would be neigh-into-impossible to do easily in Java. The Java in turn, at the appropriate time launches the same C code which then itself launches a different Java program. The second program launch needs to be completely independent, (think nohup) hence the second launch.
What's happening now is that the C program launches the Java program in the ordinary way, but when the Java tries to launch the C program, it errors out like this:
/cygdrive/c/opt/ST/v3.3/bin/ST.exe: error while loading shared
libraries: ?: cannot open shared object file: No such file or
directory
Because Windows has been such a bear over the years, the C code is written in the posix environment of Cygwin, but all it really does is ordinary C types of things (nothing about it is unique to Cygwin and, indeed, in the past it has been built with Microsoft's development tools, but that environ is not available at present). The Cygwin environ adds a lot of other great benefits, like command-line management of services (cygrunsrv) and a full-on 'nix-like environment (bash, etc). In fact, because Windows has changed how one launches a program from Java so many times, Cygwin helps standardize the Java launch code. Here's an excerpt:
if (ClientOS.indexOf("Windows") != -1)
{
if (ClientOS.equals("Windows 95"))
{
cmd = "command.com /C ";
} else if (ClientOS.equals("Windows 98"))
{
cmd = "command.com /C ";
//cmd = "cmd.exe /C ";
} else if (ClientOS.equals("Windows NT"))
{
cmd = "cmd.exe /C ";
} else if (ClientOS.equals("Windows 2000"))
{
cmd = "cmd.exe /C ";
} else if (ClientOS.equals("Windows XP"))
{
cmd = "cmd.exe /C ";
} else {
cmd = "cmd.exe /C ";
}
if (cygwin)
{
cmd += Shell+" '"+Command+"'";
} else {
cmd += Command;
}
} else {
cmd = Command;
}
(Yes, the if structure could be better optimized.)
In this case, "Shell" equals:
Shell=C:/cygwin/bin/bash -c
And, there's a test program to ensure the above and supporting code works OK - it runs a bit of shell program and ensures it got back what it thought it should. It says:
Checking the ability to run a program using a shell...
Yes, shell programs work fine.
The final contents of cmd look something like this:
cmd.exe /C C:/cygwin/bin/bash -c '/cygdrive/c/opt/ST/v3.3/bin/ST.exe'
WHAT I SUSPECT:
I suspect what's going on is that the Cygwin1.DLL file isn't being found properly. It lives in C:/cygwin/bin/cygwin1.dll
NOTE THAT both the system-level PATH and the Cygwin PATH include the path to the cygwin .dll files. Moving a copy of cygwin1.dll to the bin directory where the target executable lives didn't work either.
Would LD_LIBRARY_PATH lend any help here? If so, any idea how it is to be set?
Other ideas?
Thanks.
C:/cygwin/bin/bash -c '/cygdrive/c/opt/ST/v3.3/bin/ST.exe'
This won't work because your working directory is where you reside when you executed this command. You will have to copy the cygwin dependent dlls to the directory you execute this from. Othwerwise, you will have to place the cygwin bin directory in your system PATH variable, not sure you want to do that, can cause dll hell.
Also, if you're using anything in your profile, you need to add --login parameter to bash:
bash --login -c
Also, print the final command out at the end, before you spawn the process:
printf('%s\n',cmd)
Just to be sure it's exactly what you want.
You may also run the program with strace, if you're unsure about the dlls being referenced.
Several ways.
cygwin1.dll needs to be in %WINDIR%\system32 or equivalent.
or
you modify the PATH variable to add the path to cygwin1.dll in the caller's environment.
or
you call a .bat file that sets the environment before you call the cygwin built exe.
or
you build a standalone version of the .exe (i.e. no cygwin dependency).
I'm making an editor-like program. If the user chooses File->Open in the main window I want to start a new copy of the editor process with the chosen filename as an argument. However, for that I need to know what command was used to start the first process:
java -jar myapp.jar blabalsomearguments // --- need this information
> Open File (fileUrl)
> exec("java -jar myapp.jar blabalsomearguments fileUrl");
I'm not looking for an in-process solution, I've already implemented that. I'd like to have the benefits that seperate processes bring.
Since you are launching Java -> Java, you can use the existing classpath to set the classpath on the command line. This type of thing works really nice in the dev environment too.
ProcessBuilder selfLauncher = new ProcessBuilder(
"java", "-cp", System.getProperty("java.class.path"),
"com.my.mainClass" );
selfLauncher.start();
Update:
For executable jar files, you will have a classpath which is simply the relative path to the jar file itself. If you want the command line arguments, you will have to save them from main, and re-apply them when launching.
You can see this by packing the following program into a jar. I'm not actually sure what happens if you have jars inside the executable jar file. They probably show up in the classpath.
public class TestJarPath {
public static void main(String args[]) throws Exception {
for (String s : args)
System.out.print("[" + s + "] ");
System.out.println();
String cp = System.getProperty("java.class.path");
for (String s : cp.split(";"))
System.out.println(s);
}
}
For java -jar ..\tst.jar X, you get output like:
[X]
..\tst.jar
If all else fails, try writing a batch/shell script to launch your app. In windows you can pass %CmdCmdLine% to Java to get the entire command line.
See http://www.robvanderwoude.com/parameters.php
As far as I know is there no portable way to get this info. I found a property in the gcj runtime but I doubt this will cover a large percentage of the users.
I think the accepted practice is "Try and Pray" :
Hope it is on the path, (the path IS available, so that can be checked)
if not, check if JAVA_HOME is defined, and use that to find java.
if not check in the most likely places on all OS's you have received bug reports for.
Well, it is messy... porbably best to check for JAVA_HOME and the path and ask the user to configure a JVL explicitely if that fails.