パイプをRuntime.exec()で動作させる方法は?


104

次のコードを検討してください。

String commandf = "ls /etc | grep release";

try {

    // Execute the command and wait for it to complete
    Process child = Runtime.getRuntime().exec(commandf);
    child.waitFor();

    // Print the first 16 bytes of its output
    InputStream i = child.getInputStream();
    byte[] b = new byte[16];
    i.read(b, 0, b.length); 
    System.out.println(new String(b));

} catch (IOException e) {
    e.printStackTrace();
    System.exit(-1);
}

プログラムの出力は次のとおりです。

/etc:
adduser.co

もちろん、シェルから実行すると、期待どおりに動作します。

poundifdef@parker:~/rabbit_test$ ls /etc | grep release
lsb-release

インターネットによると、パイプの動作はクロスプラットフォームではないため、Javaを生成するJavaファクトリーで働いている優秀な頭脳は、パイプが動作することを保証できないということです。

これどうやってするの?

grepand ではなく、Javaコンストラクトを使用してすべての解析を行うわけではありません。sed言語を変更したい場合、その言語で解析コードを書き直すことを強いられます。

シェルコマンドを呼び出すときにJavaにパイプ処理とリダイレクトを行わせるにはどうすればよいですか?


私はそれを次のように見ています:ネイティブJava文字列処理でそれを行う場合、JavaアプリのすべてのプラットフォームJavaサポートへの移植性が保証されます。OTOH、シェルコマンドを使用すると、Java から言語を変更する方が簡単ですが、POSIXプラットフォームを使用している場合にのみ機能します。アプリが実行されているプラ​​ットフォームではなく、アプリの言語を変更する人はほとんどいません。だから私はあなたの推論が少し好奇心が強いと思います。
Janus Troelsen 2013

command | grep fooあなたのような単純なものの特定のケースではcommand、Javaでネイティブにフィルタリングを実行して行うほうがはるかに良いです。これにより、コードが多少複雑になりますが、全体的なリソース消費と攻撃面が大幅に削減されます。
Tripleee

回答:


182

スクリプトを記述し、個別のコマンドの代わりにスクリプトを実行します。

パイプはシェルの一部なので、次のようなこともできます:

String[] cmd = {
"/bin/sh",
"-c",
"ls /etc | grep release"
};

Process p = Runtime.getRuntime().exec(cmd);

@Kajあなたがlsie にオプションを追加したい場合はどうなりますls -lrtか?
kaustav datta 2013

8
@Kaj -cを使用してコマンド文字列をシェルに指定しようとしているようですが、これを単一の文字列ではなく文字列配列にする必要がある理由がわかりません。
David Doria

2
誰かがandroidここでバージョンを探している場合は、/system/bin/sh代わりに使用してください
Nexen

25

Linuxでも同様の問題が発生しましたが、「ps -ef | grep someprocess」でした。
少なくとも "ls"を使用すると、言語に依存しない(低速ではありますが)Javaを置き換えることができます。例えば。:

File f = new File("C:\\");
String[] files = f.listFiles(new File("/home/tihamer"));
for (String file : files) {
    if (file.matches(.*some.*)) { System.out.println(file); }
}

「ps」を使用すると、JavaにはAPIがないように見えるため、少し難しくなります。

Sigarが私たちを助けることができるかもしれないと聞いた:https : //support.hyperic.com/display/SIGAR/Home

ただし、(Kajが指摘したように)最も単純な解決策は、パイプコマンドを文字列配列として実行することです。ここに完全なコードがあります:

try {
    String line;
    String[] cmd = { "/bin/sh", "-c", "ps -ef | grep export" };
    Process p = Runtime.getRuntime().exec(cmd);
    BufferedReader in =
            new BufferedReader(new InputStreamReader(p.getInputStream()));
    while ((line = in.readLine()) != null) {
        System.out.println(line); 
    }
    in.close();
} catch (Exception ex) {
    ex.printStackTrace();
}

なぜString配列がパイプで機能するのか、1つの文字列では機能しないのかについては...それは宇宙の謎の1つです(特にソースコードを読んでいない場合)。これは、execに単一の文字列が指定された場合、最初にそれが(好ましくない方法で)解析されるためだと思います。対照的に、execに文字列配列が渡されると、解析せずにオペレーティングシステムに渡されます。

実際、忙しい一日の時間を割いてソースコードを見ると(http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/lang/ Runtime.java#Runtime.exec%28java.lang.String%2Cjava.lang.String []%2Cjava.io.File%29)、それがまさに何が起こっているのかがわかります:

public Process  [More ...] exec(String command, String[] envp, File dir) 
          throws IOException {
    if (command.length() == 0)
        throw new IllegalArgumentException("Empty command");
    StringTokenizer st = new StringTokenizer(command);
    String[] cmdarray = new String[st.countTokens()];
    for (int i = 0; st.hasMoreTokens(); i++)
        cmdarray[i] = st.nextToken();
    return exec(cmdarray, envp, dir);
}

リダイレクトでも同じ動作、つまり「>」文字が表示されます。文字列配列に関するこの追加の分析は啓発的です
kris

忙しいスケジュールの
費やす

6

各プロセスを実行するランタイムを作成します。最初のランタイムからOutputStreamを取得し、2番目のランタイムからInputStreamにコピーします。


1
Javaプロセスのアドレス空間にメモリをコピーしてから、後続のプロセスに戻すため、これはos-nativeパイプよりもはるかに効率が悪いことを指摘する価値があります。
Tim Boudreau 2017

0

@Kaj承認済みの回答はLinux向けです。これは、Windowsの場合と同等です。

String[] cmd = {
"cmd",
"/C",
"dir /B | findstr /R /C:"release""
};
Process p = Runtime.getRuntime().exec(cmd);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.