ProcessBuilder: मुख्य थ्रेड को अवरुद्ध किए बिना शुरू की गई प्रक्रियाओं के अग्रेषण और स्टडर को अग्रेषित करना


93

मैं जावा में ProcessBuilder का उपयोग करके एक प्रक्रिया का निर्माण कर रहा हूं:

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

InputStream stdOut = p.getInputStream();

अब मेरी समस्या निम्नलिखित है: मैं उस प्रक्रिया के stdout और / या stderr के माध्यम से जो कुछ भी हो रहा है उस पर कब्जा करना चाहूंगा और इसे System.outअतुल्यकालिक रूप से पुनर्निर्देशित करूंगा । मैं पृष्ठभूमि में चलने के लिए प्रक्रिया और इसके आउटपुट पुनर्निर्देशन चाहता हूं। अब तक, मुझे ऐसा करने का एकमात्र तरीका मैन्युअल रूप से एक नया धागा स्पॉन करना है जो कि लगातार पढ़ा जाएगा stdOutऔर फिर उपयुक्त write()विधि को कॉल करेगा 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();

जबकि उस तरह का काम करता है, यह थोड़ा गंदा लगता है। और इसके शीर्ष पर, यह मुझे सही तरीके से प्रबंधित करने और समाप्त करने के लिए एक और धागा देता है। क्या ऐसा करने का कोई बेहतर तरीका है?


2
यदि कॉलिंग थ्रेड को अवरुद्ध करना एक विकल्प था, तो जावा 6 में भी एक बहुत ही सरल समाधान होगा:org.apache.commons.io.IOUtils.copy(new ProcessBuilder().command(commandLine) .redirectErrorStream(true).start().getInputStream(), System.out);
oberlies

जवाबों:


69

केवल जावा 6 या उससे पहले के तरीके में तथाकथित के साथ है StreamGobbler(जिसे आप बनाना शुरू कर रहे हैं):

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

जावा 7 के लिए, एवगेनी डोरोफिव का जवाब देखें।


1
क्या StreamGobbler सभी आउटपुट को पकड़ लेगा? क्या इसका कोई मौका है कि कुछ आउटपुट गायब है? प्रक्रिया डाट होने के बाद भी स्ट्रीमगोबलर धागा अपने आप ही मर जाएगा?
लॉर्डऑफइपल्स

1
फिलहाल, InputStream समाप्त हो गया है, यह भी समाप्त हो जाएगा।
असगथ

7
जावा 6 और उससे पहले के लिए, ऐसा लगता है कि यह एकमात्र समाधान है। जावा 7 और ऊपर के लिए, ProcessBuilder.inheritIO () के बारे में अन्य जवाब देखें
LordOfThePigs

@asgoth प्रक्रिया में इनपुट भेजने का कोई तरीका है? यहाँ मेरा सवाल है: stackoverflow.com/questions/28070841/… , यदि कोई व्यक्ति समस्या को हल करने में मेरी मदद करेगा, तो मैं आभारी रहूँगा।
दीपसिद्धु १३१३

144

उपयोग करें ProcessBuilder.inheritIO, यह उप-अतिरिक्त मानक I / O के लिए स्रोत और गंतव्य को वर्तमान जावा प्रक्रिया के समान ही सेट करता है।

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

अगर जावा 7 कोई विकल्प नहीं है

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


1
मैं देख रहा हूं कि जावा 7 ने स्टडआउट, स्टडर और स्टडिन को संभालने के लिए दिलचस्प तरीकों का एक गुच्छा जोड़ा है। बहुत अच्छा। मुझे लगता है कि मैं अगली बार inheritIO()उन आसान redirect*(ProcessBuilder.Redirect)तरीकों में से एक का उपयोग करूंगा या करूंगा , जो मुझे java 7 प्रोजेक्ट पर करने की आवश्यकता है। दुर्भाग्य से मेरी परियोजना जावा 6 है
लॉर्डऑफइग्स

आह, ओके ने मेरा 1.6 संस्करण
एवगेनी डोरोफेव

scबंद करने की आवश्यकता है?
होटोहोटो

क्या आप stackoverflow.com/questions/43051640/… के साथ मदद कर सकते हैं ?
gstackoverflow

ध्यान दें कि यह इसे माता-पिता JVM के OS दर्जकर्ता को सेट करता है, System.out स्ट्रीम को नहीं। इसलिए माता-पिता के कंसोल या शेल पुनर्निर्देशन के लिए लिखना ठीक है लेकिन यह लॉगिंग स्ट्रीम के लिए काम नहीं करेगा। उन्हें अभी भी एक पंप थ्रेड की आवश्यकता है (हालांकि आप
स्टड

20

जावा 8 लैम्ब्डा के साथ एक लचीला समाधान जो आपको एक प्रदान करता है Consumerजो आउटपुट को संसाधित करेगा (उदाहरण के लिए इसे लॉग करें) लाइन द्वारा लाइन। run()एक-लाइनर है, जिसमें कोई भी जाँच अपवाद नहीं है। वैकल्पिक रूप से लागू करने के लिए Runnable, यह Threadअन्य उत्तरों के सुझाव के बजाय विस्तार कर सकता है।

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

आप इसे इस तरह उदाहरण के लिए उपयोग कर सकते हैं:

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

यहां आउटपुट स्ट्रीम को रीडायरेक्ट किया जाता है System.outऔर त्रुटि स्ट्रीम को त्रुटि स्तर पर लॉग इन किया जाता है logger


क्या आप इस पर विस्तार कर सकते हैं कि आप इसका उपयोग कैसे करेंगे?
क्रिस टर्नर

@Robert संबंधित इनपुट / त्रुटि स्ट्रीम बंद होने पर थ्रेड्स स्वचालित रूप से बंद हो जाएंगे। forEach()में run()जब तक धारा खुला है विधि ब्लॉक, अगली पंक्ति का इंतजार कर रहा होगा। धारा बंद होने पर यह बाहर निकल जाएगी।
एडम मिशैलिक

13

यह निम्नलिखित के रूप में सरल है:

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

द्वारा .redirectErrorStream (सत्य) आप त्रुटि और आउटपुट स्ट्रीम को मर्ज करने की प्रक्रिया बताते हैं और फिर .redirectOutput (फ़ाइल) द्वारा आप किसी फ़ाइल में मर्ज किए गए आउटपुट को रीडायरेक्ट करते हैं।

अपडेट करें:

मैंने इसे इस प्रकार करने का प्रबंधन किया:

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
}

अब आप System.out में मुख्य और asyncOut थ्रेड से दोनों आउटपुट देख सकते हैं


यह इस सवाल का जवाब नहीं देता: मैं उस प्रक्रिया के stdout और / या के माध्यम से जो कुछ भी हो रहा है उस पर कब्जा करना चाहूंगा और इसे System.out पर अतुल्यकालिक रूप से पुनर्निर्देशित करूंगा। मैं पृष्ठभूमि में चलने के लिए प्रक्रिया और इसके आउटपुट पुनर्निर्देशन चाहता हूं।
एडम मिशालिक

@AadMichalik, आप सही कह रहे हैं - मुझे पहली बार नहीं मिला। दिखाने के लिए धन्यवाद।
nike.laos

यह इनहेरिटियो () के रूप में एक ही समस्या है, यह PoV के JVMs FD1 को लिखेगा, लेकिन किसी भी सिस्टम System.out OutputStreams (लकड़हारा एडॉप्टर की तरह) को प्रतिस्थापित नहीं करेगा।
जूल

3

सरल java8 समाधान दोनों आउटपुट और प्रतिक्रियाशील प्रसंस्करण का उपयोग करने पर कब्जा करने के साथ 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);
        }
    });
}

और उपयोग:

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

यह समाधान बहुत सीधा है। यह अप्रत्यक्ष रूप से यह भी दिखाता है कि एक लकड़हारे को कैसे पुनर्निर्देशित किया जाए। एक उदाहरण के लिए मेरे जवाब पर एक नज़र है।
केकोरा

3

एक पुस्तकालय है जो एक बेहतर ProcessBuilder, zt-exec प्रदान करता है। यह लाइब्रेरी वही कर सकती है जो आप और अधिक के लिए पूछ रहे हैं।

यहाँ आपका कोड प्रक्रिया के बजाय zt-exec के साथ कैसा दिखेगा:

निर्भरता जोड़ें:

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

कोड :

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

पुस्तकालय का दस्तावेज़ीकरण यहाँ है: https://github.com/zeroturnaround/zt-exec/


2

मैं भी केवल जावा 6 का उपयोग कर सकता हूं। मैंने @ एवियोरियोरोफीव के थ्रेड स्कैनर कार्यान्वयन का उपयोग किया। मेरे कोड में, एक प्रक्रिया समाप्त होने के बाद, मुझे तुरंत दो अन्य प्रक्रियाओं को निष्पादित करना होगा जो प्रत्येक पुनर्निर्देशित आउटपुट की तुलना करते हैं (स्टडआउट और स्टेडर सुनिश्चित करने के लिए एक अलग-आधारित इकाई परीक्षण धन्य लोगों के समान हैं)।

स्कैनर थ्रेड्स जल्द ही समाप्त नहीं होते हैं, भले ही मैं प्रतीक्षा करता हूं () को पूरा करने की प्रक्रिया। कोड को सही ढंग से काम करने के लिए, मुझे यह सुनिश्चित करना होगा कि प्रक्रिया समाप्त होने के बाद थ्रेड्स जुड़ जाते हैं।

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 उत्तर के अतिरिक्त मैं निम्नलिखित कोड ब्लॉक जोड़ना चाहूंगा:

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

यह प्रक्रिया के इनपुट स्ट्रीम (stdout, stderr) को कुछ अन्य उपभोक्ता को पुनर्निर्देशित करने की अनुमति देता है। यह System.out :: Println या किसी अन्य चीज के तार हो सकते हैं।

उपयोग:

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

आपका कस्टम कोड इसके बजाय जाता है ...


-2

डिफ़ॉल्ट रूप से, निर्मित उपप्रकार का अपना टर्मिनल या कंसोल नहीं है। इसके सभी मानक I / O (यानी स्टड, स्टडआउट, स्टादर) के संचालन को मूल प्रक्रिया में पुनर्निर्देशित किया जाएगा, जहां उन्हें getOutputStream (), getInputStream () और getErrorStream () विधियों का उपयोग करके प्राप्त धाराओं के माध्यम से पहुँचा जा सकता है। मूल प्रक्रिया इन धाराओं का उपयोग सबप्रोसेस से इनपुट प्राप्त करने और आउटपुट प्राप्त करने के लिए करती है। क्योंकि कुछ देशी प्लेटफ़ॉर्म केवल मानक इनपुट और आउटपुट स्ट्रीम के लिए सीमित बफर आकार प्रदान करते हैं, इनपुट स्ट्रीम को तुरंत लिखने में विफलता या उपप्रकार के आउटपुट स्ट्रीम को पढ़ने के लिए सबप्रोसेस को ब्लॉक, या यहां तक ​​कि गतिरोध का कारण हो सकता है।

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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.