[???] /
[Java FAQ] / [S103]
S103: 外部コマンド呼出し
[Q1]
どうしたら、Java プログラムから外部のコマンド(プログラム)を
呼び出せますか?
[A]
java.lang.Runtime クラスの exec メソッドを使います。
Runtime のインスタンスは、
スタティックメソッドの getRuntime() で取得します。
exec メソッドには、
コマンドと引数を文字列で渡すか配列で渡すかと、
環境を指定するかどうかの組み合わせで、
4つのバリエーションがあります。
状況に合わせて適当なものを選んで下さい。
[Q2]
外部コマンドの入出力(標準出力、エラー出力、標準入力)は、
どうしたら得られますか?
[A]
外部コマンドの標準出力の内容を得るには、
exec で得られる Process の getInputStream() を使います。
エラー出力は getErrorStream() を、
標準入力は getOutputStream() を使います。
例えば次のようにすると、標準出力が一行ずつ取り出せます。
String command = "ls -l";
Process process = Runtime.getRuntime().exec(command);
InputStream is = process.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String line;
while ((line = br.readLine()) != null) {
// line で何かする。
}
[Q3]
Runtime#exec で外部コマンドを実行したのですが、
何も出力されません。なぜですか?
[A]
exec を呼ぶだけではなく、
コマンドの出力を取り出すようにプログラムする必要が有ります。
Q2 を参照して下さい。
[Q4]
ファイルを操作するコマンドが正しく実行されません。
同じコマンドをシェルで実行すると動きます。なぜ?
[A]
引数に `*' や `?' といったワイルドカードが含まれていませんか?
ワイルドカードをシェルで解釈するシステムでは、
個々のコマンドはワイルドカードを認識しないためこの問題がおきます。
ワイルドカードを使わないようにするか、あるいは
シェルを介してコマンドを実行するようにすれば解決できます。
例:
String[] cmdarray = {"/bin/sh", "-c", "cp *.class dest"};
runtime.exec(cmdarray);
[Q5]
Runtime#exec で外部コマンドの出力をリダイレクトしようとしているのですが、
ファイルがまったく作成されません。なぜでしょうか?
[A]
Runtime#exec ではリダイレクトは使えません。
出力を取り出すように書くか、
シェルを介してコマンドを実行して下さい。
出力を取り出す方法は、Q2 を参照してください。
シェルを介する方法は、次のようにします。
String[] cmdarray = {"/bin/sh", "-c", "ls > list"};
runtime.exec(cmdarray);
[Q6]
Runtime#exec で DOS コマンドの dir を実行できません。
IOException が発生してしまいます。なぜ?
[A]
dir を exec で直接実行することは出来ません。
dir は独立したプログラム dir.exe ではなく、
command.com の内部のコマンドであるためです。
command.com の内部コマンドを利用したい場合は、
次のように /c オプションを使って実行させます。
String[] cmdarray = {"command.com", "/c", "dir"};
runtime.exec(cmdarray);
「set ...」という外部コマンドの呼び出しがNT上で動かないのも同様の理由です。
[Q7]
Runtime#exec(String) で IOException が発生して、
外部コマンドが実行されません。なぜでしょうか?
[A]
コマンドを指定するパスに空白文字が含まれていませんか?
空白文字は引数との区切り文字に解釈されてしまいます。
代わりに Runtime#exec(String[]) を使って下さい。
[C]
これは、たとえば、これ
String s = "sp included/touch foo";
を、こうしろ
String[] s = {"sp included/touch", "foo"};
って話ですが、なんと Windows NT では、
String[] s = {"sp", "included/touch", "foo"};
でも実行します。反則なんですが誰がいかんのでしょう...。
[Q8]
MacOSにおけるRuntime#exec()の引数は?
[A]
Mac OSにはコマンド行がありませんので、MRJはこれ
をAppleScriptの呼び出しに変換しています。アプリケーション
がまったくAppleScriptに対応していない場合には、入力ファイル
をオープンできません。
[j-h-b:21251]
[Q9]
Process#destroy() は process を使い終わったら
呼ぶ必要が有るのでしょうか?
[A]
必要有りません。
destroy() は強制的にプロセスを終了させたい場合に使います。
プロセスが既に終了している場合は呼ぶ必要は有りません。
[Q10]
Process#destroy() を呼びましたが、プロセスが終了しません。なぜ?
[A]
終了しない場合も有ります。
Solaris では destroy() は、プロセスに SIGTERM を送ります。
このため SIGTERM を catch するプログラムの場合には、
destroy で終了させることはできません。
[Q11]
外部コマンドの終了コードは、どうしたら得られますか?
[A11]
Runtime#exec が返す Process の exitValue() で得られます。
なお exitValue()は、メソッド呼び出し時に
対応するコマンドが終了していないと例外を投げますから、
通常は waitFor() で終了を待つ必要が有ります。
waitFor() で止まってしまう場合には、
「Runtime#exec()で実行した外部コマンドが動いていないようです。」
を参照してみて下さい。
[Q12]
Runtime#exec()で実行した外部コマンドが動いていないようです。
そのコマンドを他の方法で実行すると、きちんと動くのですがなぜでしょう?
[A]
Java プログラム側でコマンドの出力を読み出していないと、
バッファがいっぱいになり、外部コマンドがその出力待ちで
途中で止まることが有ります。
特に Sun の Windows版 JRE (執筆時の最新版は 1.3)では、
非常に少ない出力量でこの溢れが発生します。
Process#getInputStream() と Process#getErrorStream() で
得られるストリームからコマンドの出力を読み出すようにすることで
回避できます。一般的には、この読み出しは個別のスレッドで
行う必要が有ります。片方づつをストリームの終わりまで
読み出すのでは、読み出していない方が溢れるかもしれないからです。
[C]
java.lang.Process にはこの他にもいくつかの問題があり、
Bug Id 4109888
Semantics of external process is not defined in JLS.
にまとめて述べられています。
[Q13]
Windows で exec で外部コマンドが起動できません。
コマンドの引数にファイル名がうまく渡せません。
[A12]
コマンドのパスや引数の文字列中に、`\' が単独で含まれていませんか?
Java では文字列定数中の `\' はエスケープを意味するので、
`\' を文字列中に含めたい時は `\\' と書かなければなりません。
contributor: Norikazu Nakato
コメントの送り先 Java FAQ BBS