Giriş / Çıkış Akışıyla Java Süreci


91

Aşağıdaki kod örneğine sahibim. Böylece, bash kabuğuna bir komut girebilir, yani echo testsonucun yankılanmasını sağlayabilirsiniz. Ancak ilk okumadan sonra. Diğer çıktı akışları çalışmıyor mu?

Neden bu ya da yanlış bir şey mi yapıyorum? Nihai hedefim, periyodik olarak / bash için bir komut yürüten OutputStreamve InputStreambirlikte çalışması ve çalışmayı durdurmaması gereken bir Kademeli zamanlanmış görev oluşturmaktır . Ayrıca java.io.IOException: Broken pipeherhangi bir fikirle ilgili hatayı yaşıyorum ?

Teşekkürler.

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

"Kırık boru" muhtemelen alt işlemin çıktığı anlamına gelir. Diğer sorunların ne olduğunu görmek için kodunuzun geri kalanına tam olarak bakmadım.
vanza 04

1
ayrı iş parçacıkları kullanın, gayet iyi çalışacaktır
Johnydep

Yanıtlar:


140

İlk olarak, satırı değiştirmenizi tavsiye ederim

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

çizgilerle

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

ProcessBuilder Java 5'te yenidir ve harici işlemlerin çalıştırılmasını kolaylaştırır. Kanımca, en önemli gelişmesi Runtime.getRuntime().exec(), alt sürecin standart hatasını standart çıktısına yeniden yönlendirmenize izin vermesidir. Bu InputStream, okuyabileceğiniz yalnızca bir tane var demektir . Bundan önce , standart çıktı arabelleği boşken (alt işlemin askıda kalmasına neden olur) standart hata arabelleği doldurmasını önlemek için stdoutbiri okuma diğeri de okuma olmak üzere iki ayrı İş Parçacığına sahip olmanız gerekiyordu stderr.

Ardından, döngüler (bunlardan iki tane var)

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

yalnızca readerişlemin standart çıktısını okuyan dosya sonu döndürdüğünde çık . Bu yalnızca bashişlem çıktığı zaman olur . Şu anda işlemden daha fazla çıktı alınmaması durumunda dosya sonu dönmeyecektir. Bunun yerine, işlemden sonraki çıktı satırını bekleyecek ve bu sonraki satırı alana kadar geri dönmeyecektir.

Bu döngüye ulaşmadan önce işleme iki satır girdi gönderdiğiniz için, bu iki satırdan sonra işlem çıkmadıysa bu iki döngüden ilki askıda kalacaktır. Orada oturacak başka bir satırın okunmasını bekleyecek, ancak okuyacağı başka bir satır asla olmayacak.

Ben kaynak kodu (yerime başkasının yüzden, şu anda Windows üzerinde olduğum derlenmiş /bin/bashile cmd.exe, ancak prensipler aynı olmalıdır) ve bunu buldum:

  • iki satır yazdıktan sonra, ilk iki komutun çıktısı görünür, ancak daha sonra program kilitlenir,
  • eğer yazarsam, diyelim echo testve sonra exit, program cmd.exeişlemden çıktığından beri ilk döngüden çıkarır . Program daha sonra başka bir girdi satırı ister (ki bu yok sayılır), alt süreç zaten çıktığı için ikinci döngü üzerinden düz bir şekilde atlar ve sonra kendisinden çıkar.
  • Ben yazarsanız exitve sonra echo test, bir IOException bir boru kapatılıyor şikayet olsun. Bu beklenen bir şeydir - ilk girdi satırı işlemin çıkmasına neden oldu ve ikinci satırı gönderecek yer yok.

Eskiden üzerinde çalıştığım bir programda, sizin istediğiniz gibi görünen şeye benzer bir şey yapan bir numara gördüm. Bu program bir dizi mermiyi tuttu, içlerinde komutlar çalıştırdı ve bu komutların çıktılarını okudu. Kullanılan hile, her zaman kabuk komutunun çıktısının sonunu işaretleyen 'sihirli' bir satır yazmak ve bunu, kabuğa gönderilen komutun çıktısının ne zaman bittiğini belirlemek için kullanmaktı.

Kodunuzu aldım ve atanan satırdan sonraki her şeyi writeraşağıdaki döngü ile değiştirdim:

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

Bunu yaptıktan sonra, birkaç komutu güvenilir bir şekilde çalıştırabilir ve her birinin çıktısının bana ayrı ayrı gelmesini sağlayabilirim.

echo --EOF--Kabuğa gönderilen satırdaki iki komut, komuttan --EOF--gelen bir hata sonucunda bile komuttan çıkışın sonlandırılmasını sağlamak için oradadır .

Elbette bu yaklaşımın sınırlamaları vardır. Bu sınırlamalar şunları içerir:

  • Kullanıcı girdisini bekleyen bir komut girersem (örneğin başka bir kabuk), program kilitleniyor gibi görünür,
  • Kabuk tarafından çalıştırılan her işlemin çıktısını bir satırsonu ile bitirdiğini varsayar,
  • Kabuk tarafından çalıştırılan komut bir satır yazarsa biraz karışır --EOF--.
  • bashbir sözdizimi hatası bildirir ve eşleşmeyen bir metin girerseniz çıkar ).

Zamanlanmış bir görev olarak çalıştırmayı düşündüğünüz her ne olursa olsun, bir komutla veya asla bu tür patolojik şekillerde davranmayacak küçük bir komutlar setiyle sınırlandırılacaksa, bu noktalar sizin için önemli olmayabilir.

DÜZENLEME : çıkış işlemeyi ve bunu Linux'ta çalıştırdıktan sonra diğer küçük değişiklikleri iyileştirin.


Kapsamlı yanıt için teşekkür ederim. Ancak sanırım sorunlarım için gerçek durumu belirledim. Gönderinizde dikkatimi çekti. stackoverflow.com/questions/3645889/… . Teşekkür ederim.
James moore

1
/ bin / bash'i cmd ile değiştirmek gerçekten aynı davranmadı, çünkü ben de aynı şeyi yapan ama bash ile sorunları olan bir program yaptım. Benim durumumda, her girdi / çıktı / hata için üç ayrı iş parçacığı açmak, uzun oturum etkileşimli komutları için en iyi şekilde sorunsuz çalışır.
Johnydep


@AlexMills: İkinci yorumunuz probleminizi çözdüğünüz anlamına mı geliyor? İlk yorumunuzda sorunun ne olduğunu ya da neden 'birdenbire' istisnalar aldığınızı söyleyecek kadar ayrıntı yok.
Luke Woodward

@Luke, yorumunuzun hemen üzerinde sağladığım bağlantı sorunumu çözüyor
Alexander Mills

4

Sanırım girişinizi okumak için demon-thread gibi thread kullanabilirsiniz ve çıktı okuyucunuz zaten while döngüsü içinde olacaktır, böylece aynı anda okuyup yazabilirsiniz.

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

ve okuyucu yukarıdakiyle aynı olacak yani

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

yazarınızı nihai yapın, aksi takdirde iç sınıf tarafından erişilemez.


1

You have writer.close();Kodunuzdaki. Yani bash, EOF'yi alır stdinve çıkar. Sonra , feshedilmiş bash'tan Broken pipeokumaya çalışırken alırsınız stdout.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.