web-dev-qa-db-ja.com

Runtime.exec(String)がすべてのコマンドではなく一部のコマンドで機能するのはなぜですか?

Runtime.exec(String)を実行しようとすると、特定のコマンドは機能しますが、他のコマンドは実行されますが、失敗するか、端末とは異なる動作をします。次に、効果を実証する自己完結型のテストケースを示します。

public class ExecTest {
  static void exec(String cmd) throws Exception {
    Process p = Runtime.getRuntime().exec(cmd);

    int i;
    while( (i=p.getInputStream().read()) != -1) {
      System.out.write(i);
    }
    while( (i=p.getErrorStream().read()) != -1) {
      System.err.write(i);
    }
  }

  public static void main(String[] args) throws Exception {
    System.out.print("Runtime.exec: ");
    String cmd = new Java.util.Scanner(System.in).nextLine();
    exec(cmd);
  }
}

コマンドをecho hello worldに置き換えると、この例はうまく機能しますが、他のコマンド、特に次のようなスペースを含むファイル名が関係するコマンドについては、コマンドが明確に実行されていてもエラーが発生します。

myshell$ javac ExecTest.Java && Java ExecTest
Runtime.exec: ls -l 'My File.txt'
ls: cannot access 'My: No such file or directory
ls: cannot access File.txt': No such file or directory

一方、私のシェルにコピー&ペースト:

myshell$ ls -l 'My File.txt'
-rw-r--r-- 1 me me 4 Aug  2 11:44 My File.txt

なぜ違いがあるのですか?いつ機能し、いつ失敗しますか?すべてのコマンドで機能させるにはどうすればよいですか?

35
that other guy

一部のコマンドが失敗するのはなぜですか?

これは、Runtime.exec(String)に渡されたコマンドがシェルで実行されないために発生します。シェルは、プログラムに対して多くの一般的なサポートサービスを実行します。シェルがそれらを実行できない場合、コマンドは失敗します。

コマンドはいつ失敗しますか?

コマンドは、シェル機能に依存するたびに失敗します。シェルは、私たちが通常考えない多くの一般的で有用なことを行います:

  1. シェルは引用符とスペースで正しく分割されます

    これにより、_"My File.txt"_のファイル名が単一の引数のままになります。

    Runtime.exec(String)は単純にスペースで分割し、これを2つの別個のファイル名として渡します。これは明らかに失敗します。

  2. シェルはグロブ/ワイルドカードを展開します

    _ls *.doc_を実行すると、シェルはそれを_ls letter.doc notes.doc_に書き換えます。

    Runtime.exec(String)はそうではなく、単に引数として渡します。

    lsは_*_が何であるか分からないため、コマンドは失敗します。

  3. シェルはパイプとリダイレクトを管理します。

    _ls mydir > output.txt_を実行すると、シェルはコマンド出力用に「output.txt」を開き、コマンドラインから削除して_ls mydir_を指定します。

    Runtime.exec(String)はサポートしていません。引数として渡すだけです。

    lsは_>_の意味がわからないため、コマンドは失敗します。

  4. シェルは変数とコマンドを展開します

    _ls "$HOME"_またはls "$(pwd)"を実行すると、シェルはそれを_ls /home/myuser_に書き換えます。

    Runtime.exec(String)はそうではなく、単に引数として渡します。

    lsは_$_の意味がわからないため、コマンドは失敗します。

代わりに何ができますか?

任意の複雑なコマンドを実行するには、2つの方法があります。

単純でずさんな:シェルに委任します。

単にRuntime.exec(String[])(配列パラメーターに注意)を使用し、コマンドを直接すべての面倒な作業を実行できるシェルに渡すことができます。

_// Simple, sloppy fix. May have security and robustness implications
String myFile = "some filename.txt";
String myCommand = "cp -R '" + myFile + "' $HOME 2> errorlog";
Runtime.getRuntime().exec(new String[] { "bash", "-c", myCommand });
_

安全で堅牢:シェルの責任を引き受けます。

これは機械的に適用できる修正ではありませんが、Unix実行モデル、シェルの機能、および同じ方法を理解する必要があります。ただし、シェルを見えなくすることで、強固で安全で堅牢なソリューションを得ることができます。これはProcessBuilderによって促進されます。

1.引用符、2。変数、および3.リダイレクトを処理する必要がある前の例のコマンドは、次のように記述できます。

_String myFile = "some filename.txt";
ProcessBuilder builder = new ProcessBuilder(
    "cp", "-R", myFile,        // We handle Word splitting
       System.getenv("HOME")); // We handle variables
builder.redirectError(         // We set up redirections
    ProcessBuilder.Redirect.to(new File("errorlog")));
builder.start();
_
79
that other guy