Spacja w system properties, małpa i inne zawiłości komendy 'java’

terminal: spacja źle przekazana w system property

Jest to raczej rzadka sytuacja, ale może zdarzyć się, że potrzebujecie do Javy przekazać system property, w którym jest spacja. Niestety, może być z tym kłopot. Czasem na drodze stoją Dockery, zmienne środowiskowe i shellowe skrypty uruchamiające. Na wypadek tak niekorzystnych warunków zaprezentuję opcję @, która pozwala uciec od shellowych zawiłości i wczytać opcje Javy z pliku.

Podstawy

Jeśli mamy system property myopts, to wartość opt1 przekażemy tak:

java -Dmyopts=opt1 -jar myapp.jar

Przekazanie wartości ze spacją w środku (opt1 opt2) w korzystnych warunkach jest proste, można to zrobić na wiele sposobów. Poniższe 3 instrukcje działają i mają ten sam efekt:

java -Dmyopts='opt1 opt2' -jar myapp.jar
java -Dmyopts="opt1 opt2" -jar myapp.jar
java "-Dmyopts=opt1 opt2" -jar myapp.jar

Skrypt shellowy po drodze

Komplikacja pojawia się, gdy nie wołamy Javy bezpośrednio. Autorzy aplikacji dostarczają swój zawiły shellowy skrypt startowy. Do tego ktoś wymyślił, żeby całość zamknąć w obrazie dockerowym, przez co nie możemy tego skryptu łatwo modyfikować.

Skrypt w uproszczeniu będzie robił pewnie coś podobnego do:

#!/bin/env bash

java $JAVA_OPTS -jar myapp.jar

Skrypt dobrze działa wołany bez spacji:

JAVA_OPTS="-Dmyopts=opt1" ./starter.sh

Ale wywali się wołany tak:

JAVA_OPTS="-Dmyopts='opt1 opt2'" ./starter.sh
Error: Could not find or load main class opt2'
Caused by: java.lang.ClassNotFoundException: opt2'

Sytuacja zupełnie się zmieni, gdy w skrypcie autorzy zastosowali dobrą zasadę skryptów shellowych, by zmienne otaczać cudzysłowami (ShellCheck), czyli w skrypcie jest java "$JAVA_OPTS" i cała reszta tak samo. Niby drobna zmiana, ale zachowanie zupełnie inne. Wołamy wtedy:

JAVA_OPTS="-Dmyopts='opt1 opt2' -Dfoo=bar" ./starter.sh

a z opcjami dzieją się dziwne rzeczy. Wystarczy zmienić myopts na file.encoding, by zobaczyć, że różne system properties zostały zmieszane ze sobą, z dodatkiem pojedynczych cudzysłowów:

Caused by: java.nio.charset.IllegalCharsetNameException: 'opt1 opt2' -Dfoo=bar

Możecie się doktoryzować z Basha. Zakuwać scenariusze na wypadek skryptów, które otaczają zmienne cudzysłowami, oraz zupełnie inne na wypadek skryptów, które nie otaczają. Jeśli tak, to na StackOverflow jest pytanie passing Jvm properties (via -D) that contain spaces, miłego czytania. Możecie też podumać nad błędami w parserze opcji zastosowanym w poleceniu java, dyskusja w JENKINS-57271.

Jeśli jednak wolicie skupić się na stronie praktycznej, mam prostą poradę działającą niezależnie od tego, co wymyślili sobie autorzy skryptu kolejnej aplikacji.

java @plik

Przez @ wczytujemy opcje z pliku:

java @my.vmoptions -jar myapp.jar

W pliku podajemy system properties. Działają cudzysłowy. Możemy wszystkie opcje umieścić w jednej linijce albo rozdzielić każdą na osobną linię:

-Dmyopts='opt1 opt2'
-Dfoo=bar
-XshowSettings:properties

Przekazywanie przez @ jest dostępne w Javie 11 i nowszych.

Creative Commons License
Except where otherwise noted, the content by Piotr Kubowicz is licensed under a Creative Commons Attribution 4.0 International License.