I am writing a Java program that runs a .sh
file specified in .properties
files and get its return value. The path of .sh
file is specified from .properties
files like:
command=C:/tmp/hoge.sh
args[0]=foo
args[1]=bar
args[2]=baz
And the Java program reads it like:
ClassLoader cl = Thread.currentThread().getContextClassLoader();
InputStream is = cl.getResourceAsStream("resources/command.properties");
Properties prop = new Properties();
prop.load(is);
List<String> list = new ArrayList<String>();
list.add(prop.getProperty("command"));
list.add(prop.getProperty("args[0]"));
list.add(prop.getProperty("args[1]"));
list.add(prop.getProperty("args[2]"));
ProcessBuilder pb = new ProcessBuilder(list);
Process p = pb.start();
int returnValue = p.exitValue();
This program runs on Linux in the production environment. But due to development environment management, we are using Windows as a development environment.
.sh
and .properties
files on our system. So I want to use the same .properties
files on the development environment and the production environment, or at least I want to use the .properties
files with simple string replacement (on the example above, what I have to do is just replace the command=C:
to command=
). I want to avoid to prepare the dedicated .properties
files for the development environment and the production environment separately.To test the Java program and .sh
file in the Windows environment, I have decided to use WSL2.
To run .sh
on Windows, I have set up WSL2 and modified file association for .sh
files. I have added following default value to the key HKEY_LOCAL_MACHINE\SOFTWARE\Classes\sh_auto_file\shell\open\command
to run .sh
on WSL2:
"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "$arg=\"%1\";wsl (\"/mnt/\"+[System.Char]::ToLower($arg[0])+$arg.Substring(2,$arg.Length-2) -replace '\\', '/'); exit $LASTEXITCODE"
On the command prompt, I have confirmed that I can run .sh
with Bash on WSL2 via the file association and get the return value of .sh
properly.
C:\tmp>hoge.sh
C:\tmp>echo %ERRORLEVEL%
5
The content of hoge.sh
is:
sleep 1
exit 5
The Java program runs .sh
file properly on Linux. But on Windows, it causes an error like this:
Exception in thread "main" java.io.IOException: Cannot run program "C:/tmp/hoge.sh": CreateProcess error=193, %1 is not a valid Win32 application
at java.lang.ProcessBuilder.start(ProcessBuilder.java:1048)
at org.example.Main.main(Main.java:16)
Caused by: java.io.IOException: CreateProcess error=193, %1 is not a valid Win32 application
at java.lang.ProcessImpl.create(Native Method)
at java.lang.ProcessImpl.<init>(ProcessImpl.java:386)
at java.lang.ProcessImpl.start(ProcessImpl.java:137)
at java.lang.ProcessBuilder.start(ProcessBuilder.java:1029)
... 1 more
How can I run .sh
from Java with file association on WSL2? Or, is there no way to launch programs according to file association and get the return value in Java?
Note that you can launch programs with the file association via java.awt.Desktop#open()
, but it doesn't have a method to get the return value.
Note also that preparing a .properties
file for the development environment like below needs very hard work on our prerequisites.
command=wsl
args[0]=C:/tmp/hoge.sh
args[1]=foo
args[2]=bar
args[3]=baz
You could make both Windows and Linux versions run BASH -c "yourcommand" to simplify the differences in OS handling. WSL installs BASH.EXE, confirm it is there with CMD.EXE:
> where bash
C:\Windows\System32\bash.exe
If your PATH is set correctly on both OS you can just use "bash" as the command:
String [] cmd = new String[] { "bash" , "-c", "Insert command 'args[0]' 'args[1]' ... here"};
For the above to work though you'd need to construct cmd[2]
as one string containing the args[0..N] values separated by space AND escaped with quotes if they also contained spaces so that the correct parameters are given to your script. Something like:
cmd[2] = prop.getProperty("command")+" "+ prop.getProperty("arg[0]") ...
If your Path is not setup you can adjust your launch code to pick bash.exe
or /bin/bash
based on os.name
:
final String OS = System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
//final boolean IS_WINDOWS = OS.startsWith("windows");
final boolean IS_LINUX = OS.startsWith("linux");
String bash = IS_LINUX ? "/bin/bash" : "bash.exe" ; // May need full path shown by where bash.EXE
String [] cmd = new String[] { bash , "-c", "Insert command args[0] args[1] ... here"};