Bagaimana cara membuat pipa berfungsi dengan Runtime.exec ()?

104

Perhatikan kode berikut:

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);
}

Keluaran program adalah:

/etc:
adduser.co

Ketika saya lari dari shell, tentu saja, ini berfungsi seperti yang diharapkan:

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

Internet memberi tahu saya bahwa, karena perilaku pipa tidak lintas platform, para pemikir brilian yang bekerja di pabrik Jawa yang memproduksi Java tidak dapat menjamin bahwa pipa berfungsi.

Bagaimana saya bisa melakukan ini?

Saya tidak akan melakukan semua parsing saya menggunakan konstruksi Java daripada grepdan sed, karena jika saya ingin mengubah bahasa, saya akan dipaksa untuk menulis ulang kode parsing saya dalam bahasa itu, yang sama sekali tidak boleh digunakan.

Bagaimana saya bisa membuat Java melakukan piping dan redirection saat memanggil perintah shell?

poundifdef.dll
sumber
Saya melihatnya seperti ini: Jika Anda melakukannya dengan penanganan string Java asli, Anda dijamin dapat membawa aplikasi Java ke semua platform yang mendukung Java. OTOH, jika Anda melakukannya dengan perintah shell, akan lebih mudah untuk mengubah bahasa dari Java, tetapi hanya akan bekerja ketika Anda menggunakan platform POSIX. Hanya sedikit orang yang mengubah bahasa aplikasi daripada platform tempat aplikasi berjalan. Itu sebabnya saya pikir alasan Anda agak aneh.
Janus Troelsen
Dalam kasus khusus sesuatu yang sederhana seperti command | grep fooAnda jauh lebih baik dengan hanya menjalankan commanddan melakukan pemfilteran secara native di Java. Itu membuat kode Anda agak lebih kompleks tetapi Anda juga mengurangi konsumsi sumber daya secara keseluruhan dan permukaan serangan secara signifikan.
tripleee

Jawaban:

182

Tulis skrip, dan jalankan skrip, bukan perintah terpisah.

Pipa adalah bagian dari cangkang, jadi Anda juga bisa melakukan sesuatu seperti ini:

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

Process p = Runtime.getRuntime().exec(cmd);
Kaj
sumber
@Kaj Bagaimana jika Anda ingin menambahkan opsi ke lsie ls -lrt?
kaustav datta
8
@Kaj Saya melihat bahwa Anda mencoba menggunakan -c untuk menentukan string perintah ke shell, tetapi saya tidak mengerti mengapa Anda harus membuat ini array string, bukan hanya satu string?
David Doria
2
Jika seseorang mencari androidversi di sini, gunakan /system/bin/shsaja
Nexen
25

Saya mengalami masalah serupa di Linux, kecuali itu adalah "ps -ef | grep someprocess".
Setidaknya dengan "ls" Anda memiliki pengganti Java yang tidak bergantung pada bahasa (meskipun lebih lambat). Misalnya.:

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); }
}

Dengan "ps", ini sedikit lebih sulit, karena Java tampaknya tidak memiliki API untuk itu.

Saya pernah mendengar bahwa Sigar mungkin dapat membantu kami: https://support.hyperic.com/display/SIGAR/Home

Solusi paling sederhana, bagaimanapun, (seperti yang ditunjukkan oleh Kaj) adalah dengan menjalankan perintah piped sebagai string array. Ini kode lengkapnya:

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();
}

Tentang mengapa array String bekerja dengan pipa, sementara satu string tidak ... itu salah satu misteri alam semesta (terutama jika Anda belum membaca kode sumbernya). Saya curiga itu karena ketika exec diberi satu string, itu menguraikannya terlebih dahulu (dengan cara yang tidak kita sukai). Sebaliknya, ketika exec diberi larik string, ia hanya meneruskannya ke sistem operasi tanpa menguraikannya.

Sebenarnya, jika kita meluangkan waktu dari hari yang sibuk dan melihat kode sumbernya (di 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 ), kami menemukan bahwa itulah yang terjadi:

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);
}
Tihamer
sumber
Saya melihat perilaku yang sama dengan pengalihan, yaitu karakter '>'. Analisis ekstra pada array String ini mencerahkan
keris
Anda tidak perlu menyisihkan satu hari dari jadwal sibuk Anda - sudah tertulis di depan mata Anda docs / api / java / lang / Runtime.html # exec (java.lang.String)
gpasch
6

Buat Runtime untuk menjalankan setiap proses. Dapatkan OutputStream dari Runtime pertama dan salin ke InputStream dari yang kedua.

SJuan76
sumber
1
Perlu diperhatikan bahwa ini jauh kurang efisien daripada pipa os-native, karena Anda menyalin memori ke ruang alamat proses Java dan kemudian kembali ke proses berikutnya.
Tim Boudreau
0

Jawaban diterima @kaj adalah untuk linux. Ini yang setara untuk Windows:

String[] cmd = {
"cmd",
"/C",
"dir /B | findstr /R /C:"release""
};
Process p = Runtime.getRuntime().exec(cmd);
yelliver
sumber