クライアントマシンでネットワークの問題が発生した場合、いくつかのコマンドラインを実行し、その結果を自分にメールで送信できるようにしたいと考えています。
Runtime.execを使用すると、任意のコマンドを実行できることがわかりましたが、結果を文字列に収集する方が興味深いです。
出力をファイルにリダイレクトしてからファイルから読み取ることができることに気づきましたが、私の気の毒な感覚は、もっとエレガントな方法があることを教えてくれます。
提案?
プロセスでstd outとstd errの両方をキャプチャする必要があります。その後、ファイル/メールなどにstdを書き込むことができます。
詳細については この記事 を参照してください。特に、個別のスレッドでstdout/errをキャプチャするStreamGobbler
メカニズムに注意してください。これはブロックを防ぐために不可欠であり、適切に行わないと多くのエラーの原因になります!
ProcessBuilder を使用します。 start()を呼び出した後、stderrおよびstdoutストリームを取得できる Process オブジェクトを取得します。
更新:ProcessBuilderにより、より詳細な制御が可能になります。使用する必要はありませんが、長い目で見れば簡単です。特に、stderrをstdoutにリダイレクトする機能は、1つのストリームのみを削除する必要があることを意味します。
Plexus Utils を使用します。すべての外部プロセスを実行するためにMavenによって使用されます。
Commandline commandLine = new Commandline();
commandLine.setExecutable(executable.getAbsolutePath());
Collection<String> args = getArguments();
for (String arg : args) {
Arg _arg = commandLine.createArg();
_arg.setValue(arg);
}
WriterStreamConsumer systemOut = new WriterStreamConsumer(console);
WriterStreamConsumer systemErr = new WriterStreamConsumer(console);
returnCode = CommandLineUtils.executeCommandLine(commandLine, systemOut, systemErr, 10);
if (returnCode != 0) {
// bad
} else {
// good
}
多くの出力を生成しないプロセスの場合、 Apache IOUtils を利用するこのシンプルなソリューションで十分だと思います。
Process p = Runtime.getRuntime().exec("script");
p.waitFor();
String output = IOUtils.toString(p.getInputStream());
String errorOutput = IOUtils.toString(p.getErrorStream());
警告:ただし、プロセスが大量の出力を生成する場合、 プロセスクラスJavaDoc で説明されているように、このアプローチは問題を引き起こす可能性があります。
作成されたサブプロセスには、独自の端末またはコンソールはありません。標準のio(つまり、stdin、stdout、stderr)操作はすべて、3つのストリーム(getOutputStream()、getInputStream()、getErrorStream())を介して親プロセスにリダイレクトされます。親プロセスはこれらのストリームを使用して、サブプロセスに入力を供給し、サブプロセスから出力を取得します。一部のネイティブプラットフォームは、標準の入力および出力ストリームに限られたバッファーサイズしか提供しないため、サブプロセスの入力ストリームの書き込みまたは出力ストリームの読み取りをすぐに失敗すると、サブプロセスがブロックされ、デッドロックさえ発生する場合があります。
これは長年使用してきた私のヘルパークラスです。 1つの小さなクラス。 JVMリソースリークを修正するJavaWorld streamgobblerクラスがあります。 JVM6およびJVM7でまだ有効かどうかはわかりませんが、害はありません。ヘルパーは、後で使用するために出力バッファーを読み取ることができます。
import Java.io.*;
/**
* Execute external process and optionally read output buffer.
*/
public class ShellExec {
private int exitCode;
private boolean readOutput, readError;
private StreamGobbler errorGobbler, outputGobbler;
public ShellExec() {
this(false, false);
}
public ShellExec(boolean readOutput, boolean readError) {
this.readOutput = readOutput;
this.readError = readError;
}
/**
* Execute a command.
* @param command command ("c:/some/folder/script.bat" or "some/folder/script.sh")
* @param workdir working directory or NULL to use command folder
* @param wait wait for process to end
* @param args 0..n command line arguments
* @return process exit code
*/
public int execute(String command, String workdir, boolean wait, String...args) throws IOException {
String[] cmdArr;
if (args != null && args.length > 0) {
cmdArr = new String[1+args.length];
cmdArr[0] = command;
System.arraycopy(args, 0, cmdArr, 1, args.length);
} else {
cmdArr = new String[] { command };
}
ProcessBuilder pb = new ProcessBuilder(cmdArr);
File workingDir = (workdir==null ? new File(command).getParentFile() : new File(workdir) );
pb.directory(workingDir);
Process process = pb.start();
// Consume streams, older jvm's had a memory leak if streams were not read,
// some other jvm+OS combinations may block unless streams are consumed.
errorGobbler = new StreamGobbler(process.getErrorStream(), readError);
outputGobbler = new StreamGobbler(process.getInputStream(), readOutput);
errorGobbler.start();
outputGobbler.start();
exitCode = 0;
if (wait) {
try {
process.waitFor();
exitCode = process.exitValue();
} catch (InterruptedException ex) { }
}
return exitCode;
}
public int getExitCode() {
return exitCode;
}
public boolean isOutputCompleted() {
return (outputGobbler != null ? outputGobbler.isCompleted() : false);
}
public boolean isErrorCompleted() {
return (errorGobbler != null ? errorGobbler.isCompleted() : false);
}
public String getOutput() {
return (outputGobbler != null ? outputGobbler.getOutput() : null);
}
public String getError() {
return (errorGobbler != null ? errorGobbler.getOutput() : null);
}
//********************************************
//********************************************
/**
* StreamGobbler reads inputstream to "gobble" it.
* This is used by Executor class when running
* a commandline applications. Gobblers must read/purge
* INSTR and ERRSTR process streams.
* http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html?page=4
*/
private class StreamGobbler extends Thread {
private InputStream is;
private StringBuilder output;
private volatile boolean completed; // mark volatile to guarantee a thread safety
public StreamGobbler(InputStream is, boolean readStream) {
this.is = is;
this.output = (readStream ? new StringBuilder(256) : null);
}
public void run() {
completed = false;
try {
String NL = System.getProperty("line.separator", "\r\n");
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line;
while ( (line = br.readLine()) != null) {
if (output != null)
output.append(line + NL);
}
} catch (IOException ex) {
// ex.printStackTrace();
}
completed = true;
}
/**
* Get inputstream buffer or null if stream
* was not consumed.
* @return
*/
public String getOutput() {
return (output != null ? output.toString() : null);
}
/**
* Is input stream completed.
* @return
*/
public boolean isCompleted() {
return completed;
}
}
}
次に、.vbsスクリプトからの出力を読み取る例を示しますが、Linux shスクリプトでも同様に機能します。
ShellExec exec = new ShellExec(true, false);
exec.execute("cscript.exe", null, true,
"//Nologo",
"//B", // batch mode, no prompts
"//T:320", // timeout seconds
"c:/my/script/test1.vbs", // unix path delim works for script.exe
"script arg 1",
"script arg 2",
);
System.out.println(exec.getOutput());
VerboseProcess
jcabi-log のユーティリティクラスを使用すると、次のことができます。
String output = new VerboseProcess(
new ProcessBuilder("executable with output")
).stdout();
必要な唯一の依存関係:
<dependency>
<groupId>com.jcabi</groupId>
<artifactId>jcabi-log</artifactId>
<version>0.7.5</version>
</dependency>
Runtime.exec()は、実行したコマンドの出力を抽出できるProcessオブジェクトを返します。
Runtime.execを使用すると、プロセスが提供されます。これらは getInputStream を使用してこのプロセスの標準出力を取得し、たとえばStringBufferを介してこの入力ストリームを文字列に入れることができます。