ProcessBuilder: Ana iş parçacığını engellemeden başlatılan işlemlerin stdout ve stderr'lerini iletme


94

Aşağıdaki gibi ProcessBuilder kullanarak Java'da bir süreç oluşturuyorum:

ProcessBuilder pb = new ProcessBuilder()
        .command("somecommand", "arg1", "arg2")
        .redirectErrorStream(true);
Process p = pb.start();

InputStream stdOut = p.getInputStream();

Şimdi benim sorunum şudur: Bu sürecin stdout ve / veya stderr'inden geçen her şeyi yakalamak ve onu System.outasenkron olarak yeniden yönlendirmek istiyorum . İşlemin ve çıktı yeniden yönlendirmesinin arka planda çalışmasını istiyorum. Şimdiye kadar, bunu yapmanın tek yolu, sürekli olarak okuyacak stdOutve ardından uygun write()yöntemi çağıracak yeni bir iş parçacığını manuel olarak oluşturmaktı System.out.

new Thread(new Runnable(){
    public void run(){
        byte[] buffer = new byte[8192];
        int len = -1;
        while((len = stdOut.read(buffer)) > 0){
            System.out.write(buffer, 0, len);
        }
    }
}).start();

Bu yaklaşım işe yarasa da biraz kirli geliyor. Üstelik, doğru şekilde yönetip sonlandırmam için bana bir konu daha veriyor. Bunu yapmanın daha iyi bir yolu var mı?


3
Çağıran iş parçacığının bloke edilmesi bir seçenek olsaydı, Java 6'da bile çok basit bir çözüm olurdu:org.apache.commons.io.IOUtils.copy(new ProcessBuilder().command(commandLine) .redirectErrorStream(true).start().getInputStream(), System.out);
oberlies

Yanıtlar:


69

Yalnızca Java 6 veya önceki sürümlerde sözde StreamGobbler(oluşturmaya başladığınız):

StreamGobbler errorGobbler = new StreamGobbler(p.getErrorStream(), "ERROR");

// any output?
StreamGobbler outputGobbler = new StreamGobbler(p.getInputStream(), "OUTPUT");

// start gobblers
outputGobbler.start();
errorGobbler.start();

...

private class StreamGobbler extends Thread {
    InputStream is;
    String type;

    private StreamGobbler(InputStream is, String type) {
        this.is = is;
        this.type = type;
    }

    @Override
    public void run() {
        try {
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr);
            String line = null;
            while ((line = br.readLine()) != null)
                System.out.println(type + "> " + line);
        }
        catch (IOException ioe) {
            ioe.printStackTrace();
        }
    }
}

Java 7 için Evgeniy Dorofeev'in cevabına bakınız.


1
StreamGobbler tüm çıktıları yakalayacak mı? Çıktının bir kısmını kaçırma ihtimali var mı? Ayrıca StreamGobbler iş parçacığı, işlem durdurucu olduğunda kendi başına ölecek mi?
LordOfThePigs

1
InputStream sona erdiği andan itibaren, o da sona erecektir.
2013

7
Java 6 ve önceki sürümler için tek çözüm bu gibi görünüyor. Java 7 ve üstü için ProcessBuilder.inheritIO ()
LordOfThePigs

@asgoth İşleme girdi göndermenin herhangi bir yolu var mı? İşte sorum şu: stackoverflow.com/questions/28070841/… , biri sorunu çözmeme yardım ederse minnettar olacağım.
DeepSidhu1313

145

Kullanım ProcessBuilder.inheritIO, alt işlem standardı G / Ç için kaynağı ve hedefi mevcut Java işlemininkilerle aynı olacak şekilde ayarlar.

Process p = new ProcessBuilder().inheritIO().command("command1").start();

Java 7 bir seçenek değilse

public static void main(String[] args) throws Exception {
    Process p = Runtime.getRuntime().exec("cmd /c dir");
    inheritIO(p.getInputStream(), System.out);
    inheritIO(p.getErrorStream(), System.err);

}

private static void inheritIO(final InputStream src, final PrintStream dest) {
    new Thread(new Runnable() {
        public void run() {
            Scanner sc = new Scanner(src);
            while (sc.hasNextLine()) {
                dest.println(sc.nextLine());
            }
        }
    }).start();
}

srcEOF olacağı için alt işlem bittiğinde dişler otomatik olarak ölecektir .


1
Java 7'nin stdout, stderr ve stdin'i işlemek için bir dizi ilginç yöntem eklediğini görüyorum. Bayağı güzel. Bir dahaki sefere bunu bir java 7 projesinde yapmam gerektiğinde kullanacağım inheritIO()veya bu kullanışlı redirect*(ProcessBuilder.Redirect)yöntemlerden birini kullanacağım . Maalesef benim projem Java 6.
LordOfThePigs

Ah, Tamam 1.6
sürümümü

sckapatılması mı gerekiyor?
hotohoto


Bunu System.out akışlarına değil, üst JVM'nin işletim sistemi dosya tanımlayıcısına ayarladığını unutmayın. Bu nedenle bu, konsola veya üst öğenin kabuk yeniden yönlendirmesine yazmak için uygundur, ancak akışları günlüğe kaydetmek için çalışmayacaktır. Bunların hala bir pompa iş parçacığına ihtiyacı var (ancak en azından
stderr'i stdin'e

20

ConsumerÇıktıyı satır satır işleyecek (örn. Günlüğe kaydetme) sağlamanıza izin veren Java 8 lambda ile esnek bir çözüm . run()kontrol edilen istisnaların atılmadığı tek satırlık bir üründür. Uygulamaya alternatif olarak Runnable, bunun Threadyerine diğer yanıtların önerdiği gibi genişletilebilir .

class StreamGobbler implements Runnable {
    private InputStream inputStream;
    private Consumer<String> consumeInputLine;

    public StreamGobbler(InputStream inputStream, Consumer<String> consumeInputLine) {
        this.inputStream = inputStream;
        this.consumeInputLine = consumeInputLine;
    }

    public void run() {
        new BufferedReader(new InputStreamReader(inputStream)).lines().forEach(consumeInputLine);
    }
}

Daha sonra örneğin şu şekilde kullanabilirsiniz:

public void runProcessWithGobblers() throws IOException, InterruptedException {
    Process p = new ProcessBuilder("...").start();
    Logger logger = LoggerFactory.getLogger(getClass());

    StreamGobbler outputGobbler = new StreamGobbler(p.getInputStream(), System.out::println);
    StreamGobbler errorGobbler = new StreamGobbler(p.getErrorStream(), logger::error);

    new Thread(outputGobbler).start();
    new Thread(errorGobbler).start();
    p.waitFor();
}

Burada çıktı akışı yeniden yönlendirilir System.outve hata akışı hata düzeyinde logger.


Lütfen bunu nasıl kullanacağınızı açıklar mısınız?
Chris Turner

@Robert İlgili giriş / hata akışı kapatıldığında iş parçacıkları otomatik olarak duracaktır. forEach()İçinde run()akış açık kadar yöntemi aşağıdaki satırı bekleyen engeller. Akış kapatıldığında çıkacaktır.
Adam Michalik

16

Aşağıdakiler kadar basit:

    File logFile = new File(...);
    ProcessBuilder pb = new ProcessBuilder()
        .command("somecommand", "arg1", "arg2")
    processBuilder.redirectErrorStream(true);
    processBuilder.redirectOutput(logFile);

.redirectErrorStream (true) ile işleme hata ve çıktı akışını birleştirmesini söylersiniz ve ardından .redirectOutput (dosya) ile birleştirilmiş çıktıyı bir dosyaya yönlendirirsiniz.

Güncelleme:

Bunu şu şekilde yapmayı başardım:

public static void main(String[] args) {
    // Async part
    Runnable r = () -> {
        ProcessBuilder pb = new ProcessBuilder().command("...");
        // Merge System.err and System.out
        pb.redirectErrorStream(true);
        // Inherit System.out as redirect output stream
        pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
        try {
            pb.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    };
    new Thread(r, "asyncOut").start();
    // here goes your main part
}

Artık System.out'ta hem ana hem de asyncOut iş parçacıklarının çıktılarını görebilirsiniz.


Bu soruya cevap vermiyor: Bu sürecin stdout ve / veya stderr'inden geçen her şeyi yakalamak ve onu System.out'a eşzamansız olarak yeniden yönlendirmek istiyorum. İşlemin ve çıktı yeniden yönlendirmesinin arka planda çalışmasını istiyorum.
Adam Michalik

@AdamMichalik, haklısın - İlk başta özü anlamadım. Gösterdiğiniz için teşekkürler.
nike.laos

Bu, inheritIO () ile aynı soruna sahiptir, zayıf JVM'lere FD1 yazacaktır, ancak System.out OutputStreams'in (logger bağdaştırıcısı gibi) yerini almayacaktır.
18-18

4

Aşağıdakileri kullanarak hem çıktıları hem de reaktif işlemeyi yakalayan basit java8 çözümü CompletableFuture:

static CompletableFuture<String> readOutStream(InputStream is) {
    return CompletableFuture.supplyAsync(() -> {
        try (
                InputStreamReader isr = new InputStreamReader(is);
                BufferedReader br = new BufferedReader(isr);
        ){
            StringBuilder res = new StringBuilder();
            String inputLine;
            while ((inputLine = br.readLine()) != null) {
                res.append(inputLine).append(System.lineSeparator());
            }
            return res.toString();
        } catch (Throwable e) {
            throw new RuntimeException("problem with executing program", e);
        }
    });
}

Ve kullanım:

Process p = Runtime.getRuntime().exec(cmd);
CompletableFuture<String> soutFut = readOutStream(p.getInputStream());
CompletableFuture<String> serrFut = readOutStream(p.getErrorStream());
CompletableFuture<String> resultFut = soutFut.thenCombine(serrFut, (stdout, stderr) -> {
         // print to current stderr the stderr of process and return the stdout
        System.err.println(stderr);
        return stdout;
        });
// get stdout once ready, blocking
String result = resultFut.get();

Bu çözüm çok basittir. Ayrıca dolaylı olarak örneğin bir kaydediciye nasıl yönlendirileceğini de gösterir. Bir örnek için cevabıma bir göz atın.
keocra

3

Daha iyi bir ProcessBuilder, zt-exec sağlayan bir kitaplık var. Bu kütüphane tam olarak istediğiniz şeyi ve daha fazlasını yapabilir.

ProcessBuilder yerine zt-exec ile kodunuz şöyle görünecektir:

bağımlılığı ekleyin:

<dependency>
  <groupId>org.zeroturnaround</groupId>
  <artifactId>zt-exec</artifactId>
  <version>1.11</version>
</dependency>

Kod:

new ProcessExecutor()
  .command("somecommand", "arg1", "arg2")
  .redirectOutput(System.out)
  .redirectError(System.err)
  .execute();

Kütüphanenin dokümantasyonu burada: https://github.com/zeroturnaround/zt-exec/


2

Ben de yalnızca Java 6 kullanabilirim. @ EvgeniyDorofeev'in iş parçacığı tarayıcı uygulamasını kullandım. Kodumda, bir işlem bittikten sonra, her biri yeniden yönlendirilen çıktıyı karşılaştıran diğer iki işlemi hemen yürütmem gerekiyor (stdout ve stderr'in kutsanmış olanlarla aynı olduğundan emin olmak için diff tabanlı bir birim testi).

İşlemin tamamlanmasını beklesem bile, tarayıcı iş parçacıkları yeterince çabuk bitmiyor. Kodun doğru çalışmasını sağlamak için, işlem bittikten sonra iş parçacıklarının birleştirildiğinden emin olmalıyım.

public static int runRedirect (String[] args, String stdout_redirect_to, String stderr_redirect_to) throws IOException, InterruptedException {
    ProcessBuilder b = new ProcessBuilder().command(args);
    Process p = b.start();
    Thread ot = null;
    PrintStream out = null;
    if (stdout_redirect_to != null) {
        out = new PrintStream(new BufferedOutputStream(new FileOutputStream(stdout_redirect_to)));
        ot = inheritIO(p.getInputStream(), out);
        ot.start();
    }
    Thread et = null;
    PrintStream err = null;
    if (stderr_redirect_to != null) {
        err = new PrintStream(new BufferedOutputStream(new FileOutputStream(stderr_redirect_to)));
        et = inheritIO(p.getErrorStream(), err);
        et.start();
    }
    p.waitFor();    // ensure the process finishes before proceeding
    if (ot != null)
        ot.join();  // ensure the thread finishes before proceeding
    if (et != null)
        et.join();  // ensure the thread finishes before proceeding
    int rc = p.exitValue();
    return rc;
}

private static Thread inheritIO (final InputStream src, final PrintStream dest) {
    return new Thread(new Runnable() {
        public void run() {
            Scanner sc = new Scanner(src);
            while (sc.hasNextLine())
                dest.println(sc.nextLine());
            dest.flush();
        }
    });
}

1

Msangel cevabına ek olarak aşağıdaki kod bloğunu eklemek istiyorum:

private static CompletableFuture<Boolean> redirectToLogger(final InputStream inputStream, final Consumer<String> logLineConsumer) {
        return CompletableFuture.supplyAsync(() -> {
            try (
                InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
                BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            ) {
                String line = null;
                while((line = bufferedReader.readLine()) != null) {
                    logLineConsumer.accept(line);
                }
                return true;
            } catch (IOException e) {
                return false;
            }
        });
    }

İşlemin giriş akışının (stdout, stderr) başka bir tüketiciye yeniden yönlendirilmesine izin verir. Bu, System.out :: println veya dizeleri tüketen başka herhangi bir şey olabilir.

Kullanım:

...
Process process = processBuilder.start()
CompletableFuture<Boolean> stdOutRes = redirectToLogger(process.getInputStream(), System.out::println);
CompletableFuture<Boolean> stdErrRes = redirectToLogger(process.getErrorStream(), System.out::println);
System.out.println(stdOutRes.get());
System.out.println(stdErrRes.get());
System.out.println(process.waitFor());

0
Thread thread = new Thread(() -> {
      new BufferedReader(
          new InputStreamReader(inputStream, 
                                StandardCharsets.UTF_8))
              .lines().forEach(...);
    });
    thread.start();

Özel kodunuz yerine ...


-1
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Main {

    public static void main(String[] args) throws Exception {
        ProcessBuilder pb = new ProcessBuilder("script.bat");
        pb.redirectErrorStream(true);
        Process p = pb.start();
        BufferedReader logReader = new BufferedReader(new InputStreamReader(p.getInputStream()));
        String logLine = null;
        while ( (logLine = logReader.readLine()) != null) {
           System.out.println("Script output: " + logLine);
        }
    }
}

Bu satırı kullanarak: pb.redirectErrorStream(true);InputStream ve ErrorStream'i birleştirebiliriz


Bu, söz konusu kodda kelimenin tam anlamıyla zaten kullanılmış ...
LordOfThePigs

-2

Varsayılan olarak, oluşturulan alt işlemin kendi terminali veya konsolu yoktur. Tüm standart G / Ç işlemleri (yani stdin, stdout, stderr) işlemleri, getOutputStream (), getInputStream () ve getErrorStream () yöntemleri kullanılarak elde edilen akışlar aracılığıyla erişilebilecekleri üst işleme yönlendirilecektir. Üst süreç, bu akışları alt işlemle girdi beslemek ve alt işlemden çıktı almak için kullanır. Bazı yerel platformlar, standart giriş ve çıkış akışları için yalnızca sınırlı arabellek boyutu sağladığından, alt işlemin giriş akışının hemen yazılmaması veya çıkış akışının okunmaması, alt işlemin engellenmesine ve hatta kilitlenmesine neden olabilir.

https://www.securecoding.cert.org/confluence/display/java/FIO07-J.+Do+not+let+external+processes+block+on+IO+buffers

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.