Java Language
Werkwijze
Zoeken…
Opmerkingen
Merk op dat de API aanbeveelt dat, vanaf versie 1.5, ProcessBuilder.start()
de beste manier is om een proces te maken.
Een andere belangrijke opmerking is dat de exit-waarde geproduceerd door waitFor
afhankelijk is van het programma / script dat wordt uitgevoerd. De exitcodes die worden geproduceerd door calc.exe zijn bijvoorbeeld anders dan notepad.exe .
Eenvoudig voorbeeld (Java-versie <1.5)
In dit voorbeeld wordt de Windows-rekenmachine opgeroepen. Het is belangrijk op te merken dat de exitcode zal variëren afhankelijk van het programma / script dat wordt aangeroepen.
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();
}
}
}
De klasse ProcessBuilder gebruiken
De klasse ProcessBuilder maakt het gemakkelijk om een opdracht via de opdrachtregel te verzenden. Het enige dat nodig is, is een lijst met tekenreeksen waaruit de in te voeren opdrachten bestaan. U roept eenvoudig de methode start () op uw ProcessBuilder-instantie aan om de opdracht uit te voeren.
Als u een programma met de naam Add.exe hebt dat twee argumenten accepteert en toevoegt, ziet de code er ongeveer zo uit:
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();
}
Enkele dingen om in gedachten te houden:
- De array met opdrachten moet allemaal een String-array zijn
- De opdrachten moeten in de volgorde (in de array) staan zoals ze zouden zijn als u het programma op de opdrachtregel zelf aanroept (d.w.z. de naam van de .exe kan niet achter het eerste argument komen)
- Bij het instellen van de werkmap moet u een File-object doorgeven en niet alleen de bestandsnaam als een String
Blokkerende versus niet-blokkerende oproepen
Over het algemeen zal het programma de opdracht verzenden en vervolgens doorgaan met de uitvoering wanneer de opdrachtregel wordt opgeroepen.
U kunt echter wachten tot het opgeroepen programma is voltooid voordat u doorgaat met uw eigen uitvoering (bijv. Het opgeroepen programma schrijft gegevens naar een bestand en uw programma heeft dat nodig om toegang te krijgen tot die gegevens.)
Dit kan eenvoudig worden gedaan door de methode waitFor()
aan te roepen vanuit de geretourneerde Process
instantie.
Gebruiksvoorbeeld:
//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
Externe processen starten vanuit Java met behulp van de raw java.lang.ProcessBuilder API direct kan een beetje omslachtig zijn. De Apache Commons Exec-bibliotheek maakt het een beetje eenvoudiger. De bibliotheek ch.vorburger.exec breidt Commons Exec verder uit om het echt handig te maken:
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();
Valkuil: Runtime.exec, Process en ProcessBuilder begrijpen shell-syntaxis niet
Met de Runtime.exec(String ...)
en Runtime.exec(String)
kunt u een opdracht uitvoeren als een extern proces 1 . In de eerste versie geeft u de opdrachtnaam en de opdrachtargumenten op als afzonderlijke elementen van de stringarray en vraagt de Java-runtime aan het OS-runtime-systeem om de externe opdracht te starten. De tweede versie is bedrieglijk eenvoudig in gebruik, maar heeft enkele valkuilen.
Allereerst is hier een voorbeeld van veilig gebruik van exec(String)
:
Process p = Runtime.exec("mkdir /tmp/testDir");
p.waitFor();
if (p.exitValue() == 0) {
System.out.println("created the directory");
}
Spaties in padnamen
Stel dat we het bovenstaande voorbeeld generaliseren zodat we een willekeurige map kunnen maken:
Process p = Runtime.exec("mkdir " + dirPath);
// ...
Dit werkt meestal, maar het zal mislukken als dirPath
(bijvoorbeeld) "/ home / user / My Documents" is. Het probleem is dat exec(String)
de string splitst in een opdracht en argumenten door eenvoudigweg naar witruimte te zoeken. De opdrachtreeks:
"mkdir /home/user/My Documents"
zal worden opgesplitst in:
"mkdir", "/home/user/My", "Documents"
en dit zal ervoor zorgen dat het "mkdir" commando faalt omdat het één argument verwacht, niet twee.
Sommige programmeurs proberen hiermee aanhalingstekens rond de padnaam toe te voegen. Dit werkt ook niet:
"mkdir \"/home/user/My Documents\""
zal worden opgesplitst in:
"mkdir", "\"/home/user/My", "Documents\""
De extra dubbele aanhalingstekens die zijn toegevoegd in een poging om de spaties te "citeren" worden behandeld als alle andere niet-witruimte-tekens. Inderdaad, alles wat we citeren of ontsnappen aan de ruimtes zal mislukken.
De manier om met dit specifieke probleem om te gaan, is door de overbelasting exec(String ...)
.
Process p = Runtime.exec("mkdir", dirPath);
// ...
Dit werkt als dirpath
witruimte-tekens bevat omdat deze overbelasting van exec
niet probeert de argumenten te splitsen. De tekenreeksen worden doorgegeven aan de OS- exec
systeemoproep zoals deze is.
Omleiding, pijpleidingen en andere shell-syntaxis
Stel dat we de invoer of uitvoer van een externe opdracht willen omleiden of een pijplijn willen uitvoeren. Bijvoorbeeld:
Process p = Runtime.exec("find / -name *.java -print 2>/dev/null");
of
Process p = Runtime.exec("find source -name *.java | xargs grep package");
(Het eerste voorbeeld toont de namen van alle Java-bestanden in het bestandssysteem en het tweede voorbeeld drukt de package
2 af in de Java-bestanden in de "bron" -structuur.)
Deze zullen niet werken zoals verwacht. In het eerste geval wordt de opdracht "find" uitgevoerd met "2> / dev / null" als opdrachtargument. Het zal niet worden geïnterpreteerd als een omleiding. In het tweede voorbeeld worden het pijpteken ("|") en de daaropvolgende werken gegeven aan de opdracht "find".
Het probleem is dat de exec
methoden en ProcessBuilder
geen shell-syntaxis begrijpen. Dit omvat omleidingen, pijpleidingen, variabele expansie, globbing, enzovoort.
In enkele gevallen (bijvoorbeeld eenvoudige omleiding) kunt u eenvoudig het gewenste effect bereiken met ProcessBuilder
. Dit is echter in het algemeen niet waar. Een alternatieve benadering is om de opdrachtregel in een shell uit te voeren; bijvoorbeeld:
Process p = Runtime.exec("bash", "-c",
"find / -name *.java -print 2>/dev/null");
of
Process p = Runtime.exec("bash", "-c",
"find source -name \\*.java | xargs grep package");
Maar merk op dat we in het tweede voorbeeld moesten ontsnappen aan het jokerteken ("*") omdat we willen dat het jokerteken wordt geïnterpreteerd door "zoeken" in plaats van de shell.
Ingebouwde Shell-opdrachten werken niet
Stel dat de volgende voorbeelden niet werken op een systeem met een UNIX-achtige shell:
Process p = Runtime.exec("cd", "/tmp"); // Change java app's home directory
of
Process p = Runtime.exec("export", "NAME=value"); // Export NAME to the java app's environment
Er zijn een aantal redenen waarom dit niet werkt:
Op "cd" en "export" commando's zijn shell ingebouwde commando's. Ze bestaan niet als afzonderlijke uitvoerbare bestanden.
Als shell-builders moeten doen wat ze moeten doen (bijvoorbeeld de werkmap wijzigen, de omgeving bijwerken), moeten ze de plaats wijzigen waar die status zich bevindt. Voor een normale applicatie (inclusief een Java-applicatie) wordt de status geassocieerd met het applicatieproces. Dus het onderliggende proces dat de opdracht "cd" zou uitvoeren, kon bijvoorbeeld de werkmap van het bovenliggende "java" -proces niet wijzigen. Evenzo kan één
exec
de werkmap niet wijzigen voor een proces dat erop volgt.
Deze redenering is van toepassing op alle ingebouwde shell-opdrachten.
1 - U kunt ook ProcessBuilder
gebruiken, maar dat is niet relevant voor het punt van dit voorbeeld.
2 - Dit is een beetje ruw en klaar ... maar nogmaals, de tekortkomingen van deze benadering zijn niet relevant voor het voorbeeld.