Proses Java dengan Arus Input / Output

90

Saya memiliki contoh kode berikut di bawah ini. Dimana Anda bisa memasukkan perintah ke bash shell ie echo testdan hasilnya akan di-echo kembali. Namun, setelah dibaca dulu. Aliran keluaran lainnya tidak berfungsi?

Mengapa ini atau saya melakukan sesuatu yang salah? Tujuan akhir saya adalah untuk membuat tugas terjadwal Berulir yang menjalankan perintah secara berkala ke / bash sehingga OutputStreamdan InputStreamharus bekerja bersama-sama dan tidak berhenti bekerja. Saya juga telah mengalami kesalahan java.io.IOException: Broken pipeide?

Terima kasih.

String line;
Scanner scan = new Scanner(System.in);

Process process = Runtime.getRuntime ().exec ("/bin/bash");
OutputStream stdin = process.getOutputStream ();
InputStream stderr = process.getErrorStream ();
InputStream stdout = process.getInputStream ();

BufferedReader reader = new BufferedReader (new InputStreamReader(stdout));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stdin));

String input = scan.nextLine();
input += "\n";
writer.write(input);
writer.flush();

input = scan.nextLine();
input += "\n";
writer.write(input);
writer.flush();

while ((line = reader.readLine ()) != null) {
System.out.println ("Stdout: " + line);
}

input = scan.nextLine();
input += "\n";
writer.write(input);
writer.close();

while ((line = reader.readLine ()) != null) {
System.out.println ("Stdout: " + line);
}
James moore
sumber
"Pipa rusak" mungkin berarti proses anak telah keluar. Belum sepenuhnya melihat sisa kode Anda untuk melihat apa saja masalah lainnya.
vanza
1
menggunakan utas terpisah, itu akan bekerja dengan baik
Johnydep

Jawaban:

139

Pertama, saya akan merekomendasikan mengganti baris

Process process = Runtime.getRuntime ().exec ("/bin/bash");

dengan garis

ProcessBuilder builder = new ProcessBuilder("/bin/bash");
builder.redirectErrorStream(true);
Process process = builder.start();

ProcessBuilder baru di Java 5 dan membuat proses eksternal berjalan lebih mudah. Menurut pendapat saya, peningkatan paling signifikannya Runtime.getRuntime().exec()adalah memungkinkan Anda untuk mengarahkan kesalahan standar dari proses anak ke dalam keluaran standarnya. Ini berarti Anda hanya memiliki satu InputStreamuntuk dibaca. Sebelum ini, Anda perlu memiliki dua Thread terpisah, satu membaca dari stdoutdan satu membaca dari stderr, untuk menghindari pengisian buffer kesalahan standar sementara buffer keluaran standar kosong (menyebabkan proses anak hang), atau sebaliknya.

Selanjutnya, loop (yang Anda miliki dua)

while ((line = reader.readLine ()) != null) {
    System.out.println ("Stdout: " + line);
}

hanya keluar ketika reader, yang membaca dari keluaran standar proses, mengembalikan akhir file. Ini hanya terjadi ketika bashproses keluar. Ini tidak akan mengembalikan akhir file jika saat ini tidak ada lagi output dari proses. Sebaliknya, ia akan menunggu baris keluaran berikutnya dari proses dan tidak kembali sampai ia memiliki baris berikutnya ini.

Karena Anda mengirim dua baris input ke proses sebelum mencapai loop ini, loop pertama dari dua loop ini akan hang jika proses belum keluar setelah dua baris input ini. Itu akan duduk di sana menunggu baris lain untuk dibaca, tetapi tidak akan pernah ada baris lain untuk dibaca.

Saya mengkompilasi kode sumber Anda (saya menggunakan Windows saat ini, jadi saya menggantinya /bin/bashdengan cmd.exe, tetapi prinsipnya harus sama), dan saya menemukan bahwa:

  • setelah mengetik dalam dua baris, output dari dua perintah pertama muncul, tetapi kemudian program hang,
  • jika saya mengetikkan, katakan,,, echo testdan kemudian exit, program membuatnya keluar dari loop pertama sejak cmd.exeproses telah keluar. Program kemudian meminta baris input lain (yang diabaikan), langsung melewati loop kedua karena proses turunan sudah keluar, dan kemudian keluar sendiri.
  • jika saya mengetik exitdan kemudian echo test, saya mendapatkan IOException yang mengeluh tentang pipa yang ditutup. Ini sudah biasa - baris pertama input menyebabkan proses keluar, dan tidak ada tempat untuk mengirim baris kedua.

Saya telah melihat trik yang melakukan sesuatu yang mirip dengan apa yang tampaknya Anda inginkan, dalam program yang dulu saya kerjakan. Program ini menyimpan sejumlah shell, menjalankan perintah di dalamnya dan membaca keluaran dari perintah ini. Trik yang digunakan adalah selalu menulis baris 'ajaib' yang menandai akhir keluaran perintah shell, dan menggunakannya untuk menentukan kapan keluaran dari perintah yang dikirim ke shell telah selesai.

Saya mengambil kode Anda dan saya mengganti semuanya setelah baris yang ditetapkan writerdengan loop berikut:

while (scan.hasNext()) {
    String input = scan.nextLine();
    if (input.trim().equals("exit")) {
        // Putting 'exit' amongst the echo --EOF--s below doesn't work.
        writer.write("exit\n");
    } else {
        writer.write("((" + input + ") && echo --EOF--) || echo --EOF--\n");
    }
    writer.flush();

    line = reader.readLine();
    while (line != null && ! line.trim().equals("--EOF--")) {
        System.out.println ("Stdout: " + line);
        line = reader.readLine();
    }
    if (line == null) {
        break;
    }
}

Setelah melakukan ini, saya dapat dengan andal menjalankan beberapa perintah dan keluaran dari masing-masing perintah kembali kepada saya secara individual.

Dua echo --EOF--perintah dalam baris yang dikirim ke shell ada di sana untuk memastikan bahwa keluaran dari perintah diakhiri dengan --EOF--bahkan akibat kesalahan dari perintah.

Tentu saja, pendekatan ini memiliki keterbatasan. Batasan ini meliputi:

  • jika saya memasukkan perintah yang menunggu input pengguna (mis. shell lain), program tampak hang,
  • itu mengasumsikan bahwa setiap proses yang dijalankan oleh shell mengakhiri outputnya dengan baris baru,
  • itu menjadi sedikit bingung jika perintah yang dijalankan oleh shell kebetulan menulis sebuah baris --EOF--.
  • bashmelaporkan kesalahan sintaks dan keluar jika Anda memasukkan beberapa teks dengan yang tidak cocok ).

Poin-poin ini mungkin tidak masalah bagi Anda jika apa pun yang Anda pikirkan untuk dijalankan sebagai tugas terjadwal akan dibatasi pada sebuah perintah atau sekumpulan kecil perintah yang tidak akan pernah berperilaku patologis seperti itu.

EDIT : tingkatkan penanganan keluar dan perubahan kecil lainnya setelah menjalankan ini di Linux.

Luke Woodward
sumber
Terima kasih atas jawaban lengkapnya Namun, saya rasa saya telah mengidentifikasi kasus sebenarnya untuk masalah saya. Menarik perhatian saya di postingan Anda. stackoverflow.com/questions/3645889/… . Terima kasih.
James moore
1
mengganti / bin / bash dengan cmd sebenarnya tidak berperilaku sama, karena saya membuat program yang melakukan hal yang sama tetapi bermasalah dengan bash. Di mycase, membuka tiga utas terpisah untuk setiap input / output / err bekerja paling baik tanpa masalah untuk perintah interaktif sesi panjang.
Johnydep
@AlexMills: apakah komentar kedua Anda berarti Anda telah memecahkan masalah Anda? Tidak ada cukup detail dalam komentar pertama Anda untuk mengatakan apa masalahnya, atau mengapa Anda 'tiba-tiba' mendapatkan pengecualian.
Luke Woodward
@ Luke, tautan yang saya berikan tepat di atas komentar Anda memecahkan masalah saya
Alexander Mills
4

Saya rasa Anda dapat menggunakan thread seperti demon-thread untuk membaca masukan Anda dan pembaca keluaran Anda sudah akan berada dalam loop sementara di thread utama sehingga Anda dapat membaca dan menulis pada saat yang sama. Anda dapat memodifikasi program Anda seperti ini:

Thread T=new Thread(new Runnable() {

    @Override
    public void run() {
        while(true)
        {
            String input = scan.nextLine();
            input += "\n";
            try {
                writer.write(input);
                writer.flush();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        }

    }
} );
T.start();

dan Anda bisa pembaca akan sama seperti di atas yaitu

while ((line = reader.readLine ()) != null) {
    System.out.println ("Stdout: " + line);
}

Jadikan penulis Anda final jika tidak maka tidak akan dapat diakses oleh kelas dalam.

Shashank Jain
sumber
1

Anda memiliki writer.close();kode Anda. Jadi bash menerima EOF saat stdinkeluar. Kemudian Anda dapatkan Broken pipesaat mencoba membaca dari stdoutpesta yang sudah tidak berfungsi.

gpeche.dll
sumber