Предыстория

Я пишу программу на Java, которая запускает файл .sh, указанный в файлах .properties, и получает его возвращаемое значение. Путь к файлу .sh задается из файлов .properties следующим образом:

command=C:/tmp/hoge.sh
args[0]=foo
args[1]=bar
args[2]=baz

А программа на Java читает это так:

ClassLoader cl = Thread.currentThread().getContextClassLoader();
InputStream is = cl.getResourceAsStream("resources/command.properties");
Properties prop = new Properties();
prop.load(is);

List list = new ArrayList();
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();

Эта программа работает на Linux в производственной среде. Но из-за необходимости управления средой разработки мы используем Windows в качестве среды разработки.

Пререквизиты

  • В нашей системе очень много файлов .sh и .properties. Поэтому я хочу использовать одни и те же файлы .properties в среде разработки и в производственной среде, или, по крайней мере, я хочу использовать файлы .properties с простой заменой строк (в приведенном примере мне нужно просто заменить command=C: на command=). Я хочу избежать подготовки специальных файлов .properties для среды разработки и производственной среды по отдельности.
  • Мы используем Windows-версию Java в основном по двум причинам: 1) промежуточное ПО, которое мы вызываем из Java-программы (оно поддерживает только Windows и RHEL, но не поддерживает среду WSL), и 2) набор навыков членов проекта (есть только несколько членов, которые могут свободно использовать как Windows, так и Linux).
  • Мы хотим, чтобы у нас было больше возможностей для работы с Java.
  • Мы хотим избежать дополнительного кода, который используется только в среде разработки, насколько это возможно, из-за измерения покрытия кода и управления качеством.

Что я пробовал и подтвердил

Для тестирования Java-программы и файла .sh в среде Windows я решил использовать WSL2.

Для запуска .sh в Windows я настроил WSL2 и изменил ассоциацию файлов для файлов .sh. Я добавил следующее значение по умолчанию в ключ HKEY_LOCAL_MACHINE\SOFTWARE\Classes\sh_auto_file\shell\open\command для запуска .sh на 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"

В командной строке я подтвердил, что могу запустить .sh с Bash на WSL2 через ассоциацию файлов и получить возвращаемое значение .sh должным образом.

C:\tmp>hoge.sh

C:\tmp>echo %ERRORLEVEL%
5

Содержание hoge.sh таково:

sleep 1
выход 5

Проблема

Программа Java правильно запускает файл .sh в Linux. Но на Windows она вызывает ошибку следующего вида:

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)
Вызвано: java.io.IOException: CreateProcess error=193, %1 is not a valid Win32 application
    at java.lang.ProcessImpl.create(Native Method)
    at java.lang.ProcessImpl.(ProcessImpl.java:386)
    at java.lang.ProcessImpl.start(ProcessImpl.java:137)
    at java.lang.ProcessBuilder.start(ProcessBuilder.java:1029)
    ... еще 1

Вопрос

Как можно запустить .sh из Java с ассоциацией файлов на WSL2? Или нет способа запускать программы в соответствии с ассоциацией файлов и получать возвращаемое значение в Java?

Отметим, что вы можете запускать программы с ассоциацией файлов через java.awt.Desktop#open(), но у него нет метода для получения возвращаемого значения.

Отметим также, что подготовка файла .properties для среды разработки, как показано ниже, требует большой работы над нашими предварительными условиями.

command=wsl
args[0]=C:/tmp/hoge.sh
args[1]=foo
args[2]=bar
args[3]=baz

Ответы (1)

Вы можете заставить обе версии Windows и Linux запускать BASH -c "yourcommand", чтобы упростить различия в работе с ОС. WSL устанавливает BASH.EXE, подтвердите его наличие с помощью CMD.EXE:

> где bash
C:\Windows\System32\bash.exe

Если ваш PATH установлен правильно в обеих ОС, вы можете просто использовать "bash" в качестве команды:

String [] cmd = new String[] { "bash" , "-c", "Вставить команду 'args[0]' 'args[1]' ... сюда"};

Чтобы вышеописанное сработало, вам нужно построить cmd[2] как одну строку, содержащую значения args[0..N], разделенные пробелом и заключенные в кавычки, если они также содержат пробелы, чтобы скрипту были переданы правильные параметры. Что-то вроде:

cmd[2] = prop.getProperty("command")+" "+ prop.getProperty("arg[0]") ...

Если ваш Path не настроен, вы можете изменить код запуска, чтобы выбрать bash.exe или /bin/bash на основе 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" ; // Может понадобиться полный путь, указанный там, где bash.EXE
String [] cmd = new String[] { bash , "-c", "Вставьте команду args[0] args[1] ... сюда"};

2022 WebDevInsider