Java Language
Proces
Szukaj…
Uwagi
Zauważ, że API zaleca, aby od wersji 1.5 preferowanym sposobem tworzenia Procesu była ProcessBuilder.start()
.
Inną ważną uwagą jest to, że wartość wyjściowa wygenerowana przez waitFor
zależy od wykonywanego programu / skryptu. Na przykład kody wyjścia utworzone przez calc.exe różnią się od notatnika.exe .
Prosty przykład (wersja Java <1.5)
Ten przykład wywoła kalkulator systemu Windows. Należy zauważyć, że kod wyjścia będzie się różnić w zależności od wywoływanego programu / skryptu.
package process.example;
import java.io.IOException;
public class App {
public static void main(String[] args) {
try {
// Executes windows calculator
Process p = Runtime.getRuntime().exec("calc.exe");
// Wait for process until it terminates
int exitCode = p.waitFor();
System.out.println(exitCode);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Korzystanie z klasy ProcessBuilder
Klasa ProcessBuilder ułatwia wysyłanie poleceń za pomocą wiersza poleceń. Wszystko czego wymaga to Lista ciągów znaków, które składają się na polecenia, które należy wprowadzić. Wystarczy wywołać metodę start () w instancji ProcessBuilder, aby wykonać polecenie.
Jeśli masz program o nazwie Add.exe, który pobiera dwa argumenty i dodaje je, kod wyglądałby mniej więcej tak:
List<String> cmds = new ArrayList<>();
cmds.add("Add.exe"); //the name of the application to be run
cmds.add("1"); //the first argument
cmds.add("5"); //the second argument
ProcessBuilder pb = new ProcessBuilder(cmds);
//Set the working directory of the ProcessBuilder so it can find the .exe
//Alternatively you can just pass in the absolute file path of the .exe
File myWorkingDirectory = new File(yourFilePathNameGoesHere);
pb.workingDirectory(myWorkingDirectory);
try {
Process p = pb.start();
} catch (IOException e) {
e.printStackTrace();
}
Kilka rzeczy, o których należy pamiętać:
- Tablica poleceń musi być tablicą ciągów
- Polecenia muszą być w takiej kolejności (w tablicy), w jakiej byłyby, gdyby wywołano program w samym wierszu polecenia (tzn. Nazwa pliku .exe nie może iść za pierwszym argumentem
- Podczas ustawiania katalogu roboczego musisz przekazać obiekt File, a nie tylko nazwę pliku jako String
Blokowanie a nieblokowanie połączeń
Zasadniczo podczas wywoływania linii poleceń program wysyła polecenie, a następnie kontynuuje jego wykonywanie.
Możesz jednak poczekać na zakończenie wywoływanego programu przed kontynuowaniem własnego wykonywania (np. Wywoływany program zapisze dane w pliku, a Twój program będzie potrzebował dostępu do tych danych).
Można to łatwo zrobić, wywołując waitFor()
ze zwróconej instancji Process
.
Przykład użycia:
//code setting up the commands omitted for brevity...
ProcessBuilder pb = new ProcessBuilder(cmds);
try {
Process p = pb.start();
p.waitFor();
} catch (IOException e) {
e.printStackTrack();
} catch (InterruptedException e) {
e.printStackTrace();
}
//more lines of code here...
ch.vorburger.exec
Bezpośrednie uruchamianie zewnętrznych procesów z Javy przy użyciu surowego interfejsu API java.lang.ProcessBuilder może być nieco kłopotliwe. Biblioteka Apache Commons Exec sprawia, że jest to trochę łatwiejsze. Biblioteka ch.vorburger.exec rozszerza także na Commons Exec, dzięki czemu jest naprawdę wygodna:
ManagedProcess proc = new ManagedProcessBuilder("path-to-your-executable-binary")
.addArgument("arg1")
.addArgument("arg2")
.setWorkingDirectory(new File("/tmp"))
.setDestroyOnShutdown(true)
.setConsoleBufferMaxLines(7000)
.build();
proc.start();
int status = proc.waitForExit();
int status = proc.waitForExitMaxMsOrDestroy(3000);
String output = proc.getConsole();
proc.startAndWaitForConsoleMessageMaxMs("started!", 7000);
// use service offered by external process...
proc.destroy();
Pitfall: Runtime.exec, Process i ProcessBuilder nie rozumieją składni powłoki
Metody Runtime.exec(String ...)
i Runtime.exec(String)
pozwalają na wykonanie polecenia jako procesu zewnętrznego 1 . W pierwszej wersji podajesz nazwę polecenia i argumenty polecenia jako osobne elementy tablicy łańcuchów, a środowisko wykonawcze Java prosi system wykonawczy OS o uruchomienie polecenia zewnętrznego. Druga wersja jest zwodniczo łatwa w użyciu, ale ma pewne pułapki.
Po pierwsze, oto przykład bezpiecznego używania exec(String)
:
Process p = Runtime.exec("mkdir /tmp/testDir");
p.waitFor();
if (p.exitValue() == 0) {
System.out.println("created the directory");
}
Spacje w nazwach ścieżek
Załóżmy, że uogólniamy powyższy przykład, abyśmy mogli utworzyć dowolny katalog:
Process p = Runtime.exec("mkdir " + dirPath);
// ...
Zwykle będzie to działać, ale zakończy się niepowodzeniem, jeśli dirPath
to (na przykład) „/ home / user / My Documents”. Problem polega na tym, że exec(String)
dzieli ciąg na polecenie i argumenty, po prostu szukając białych znaków. Ciąg poleceń:
"mkdir /home/user/My Documents"
zostanie podzielony na:
"mkdir", "/home/user/My", "Documents"
a to spowoduje niepowodzenie polecenia „mkdir”, ponieważ oczekuje on jednego argumentu, a nie dwóch.
W obliczu tego niektórzy programiści próbują dodać cudzysłowy wokół nazwy ścieżki. To też nie działa:
"mkdir \"/home/user/My Documents\""
zostanie podzielony na:
"mkdir", "\"/home/user/My", "Documents\""
Dodatkowe znaki podwójnego cudzysłowu, które zostały dodane w celu „zacytowania” spacji, są traktowane jak inne znaki spoza białej litery. Rzeczywiście, wszystko, co robimy, cytując lub uciekając z kosmosu, zawiedzie.
Sposobem na rozwiązanie tego konkretnego problemu jest użycie przeciążenia exec(String ...)
.
Process p = Runtime.exec("mkdir", dirPath);
// ...
dirpath
to, jeśli dirpath
zawiera znaki spacji, ponieważ przeciążenie exec
nie próbuje podzielić argumentów. Ciągi są przekazywane do exec
systemowego exec
systemu operacyjnego w stanie, w jakim się znajduje.
Przekierowanie, potoki i inna składnia powłoki
Załóżmy, że chcemy przekierować dane wejściowe lub wyjściowe polecenia zewnętrznego lub uruchomić potok. Na przykład:
Process p = Runtime.exec("find / -name *.java -print 2>/dev/null");
lub
Process p = Runtime.exec("find source -name *.java | xargs grep package");
(Pierwszy przykład wyświetla nazwy wszystkich plików Java w systemie plików, a drugi drukuje instrukcje package
2 w plikach Java w drzewie „źródłowym”).
Nie będą działać zgodnie z oczekiwaniami. W pierwszym przypadku polecenie „find” zostanie uruchomione z argumentem „2> / dev / null”. Nie będzie interpretowane jako przekierowanie. W drugim przykładzie znak potoku („|”) i następujące po nim prace zostaną przekazane do polecenia „znajdź”.
Problem polega na tym, że metody exec
i ProcessBuilder
nie rozumieją żadnej składni powłoki. Obejmuje to przekierowania, potoki, zmienną ekspansję, globowanie i tak dalej.
W kilku przypadkach (na przykład proste przekierowanie) można łatwo osiągnąć pożądany efekt za pomocą ProcessBuilder
. Jednak ogólnie nie jest to prawdą. Alternatywnym podejściem jest uruchomienie wiersza poleceń w powłoce; na przykład:
Process p = Runtime.exec("bash", "-c",
"find / -name *.java -print 2>/dev/null");
lub
Process p = Runtime.exec("bash", "-c",
"find source -name \\*.java | xargs grep package");
Zauważ jednak, że w drugim przykładzie musieliśmy uciec od znaku wieloznacznego („*”), ponieważ chcemy, aby znak wieloznaczny był interpretowany raczej przez „find” niż przez powłokę.
Wbudowane polecenia powłoki nie działają
Załóżmy, że następujące przykłady nie będą działać w systemie z powłoką podobną do systemu UNIX:
Process p = Runtime.exec("cd", "/tmp"); // Change java app's home directory
lub
Process p = Runtime.exec("export", "NAME=value"); // Export NAME to the java app's environment
Jest kilka powodów, dla których to nie zadziała:
Polecenia „cd” i „export” są wbudowanymi poleceniami powłoki. Nie istnieją jako odrębne pliki wykonywalne.
Aby wbudowane powłoki mogły wykonać to, co powinny (np. Zmienić katalog roboczy, zaktualizować środowisko), muszą zmienić miejsce, w którym znajduje się ten stan. W przypadku normalnej aplikacji (w tym aplikacji Java) stan jest powiązany z procesem aplikacji. Na przykład proces potomny, który uruchomiłby polecenie „cd”, nie mógł zmienić katalogu roboczego nadrzędnego procesu „java”. Podobnie, jeden proces
exec
nie może zmienić katalogu roboczego dla następującego po nim procesu.
To rozumowanie dotyczy wszystkich poleceń wbudowanych w powłokę.
1 - Możesz również użyć ProcessBuilder
, ale nie dotyczy to tego przykładu.
2 - Jest to trochę szorstkie i gotowe ... ale ponownie, błędy tego podejścia nie mają związku z przykładem.