Java Runtime.getRuntime (): pobieranie danych wyjściowych z wykonania programu wiersza poleceń

155

Używam środowiska wykonawczego do uruchamiania poleceń wiersza polecenia z mojego programu Java. Jednak nie jestem świadomy tego, w jaki sposób mogę uzyskać dane wyjściowe zwracane przez polecenie.

Oto mój kod:

Runtime rt = Runtime.getRuntime();

String[] commands = {"system.exe", "-send" , argument};

Process proc = rt.exec(commands);

Próbowałem, System.out.println(proc);ale to nic nie zwróciło. Wykonanie tego polecenia powinno zwrócić dwie liczby oddzielone średnikiem. Jak mogę uzyskać to w zmiennej do wydrukowania?

Oto kod, którego teraz używam:

String[] commands = {"system.exe", "-get t"};

Process proc = rt.exec(commands);

InputStream stdIn = proc.getInputStream();
InputStreamReader isr = new InputStreamReader(stdIn);
BufferedReader br = new BufferedReader(isr);

String line = null;
System.out.println("<OUTPUT>");

while ((line = br.readLine()) != null)
     System.out.println(line);

System.out.println("</OUTPUT>");
int exitVal = proc.waitFor();
System.out.println("Process exitValue: " + exitVal);

Ale nie otrzymuję nic jako moje wyjście, ale kiedy uruchamiam to polecenie samodzielnie, działa dobrze.

user541597
źródło

Odpowiedzi:

244

Oto droga do zrobienia:

Runtime rt = Runtime.getRuntime();
String[] commands = {"system.exe", "-get t"};
Process proc = rt.exec(commands);

BufferedReader stdInput = new BufferedReader(new 
     InputStreamReader(proc.getInputStream()));

BufferedReader stdError = new BufferedReader(new 
     InputStreamReader(proc.getErrorStream()));

// Read the output from the command
System.out.println("Here is the standard output of the command:\n");
String s = null;
while ((s = stdInput.readLine()) != null) {
    System.out.println(s);
}

// Read any errors from the attempted command
System.out.println("Here is the standard error of the command (if any):\n");
while ((s = stdError.readLine()) != null) {
    System.out.println(s);
}

Przeczytaj Javadoc, aby uzyskać więcej informacji tutaj . ProcessBuilderbyłby dobrym wyborem.

Senthil
źródło
4
@AlbertChen pwd && lsnie tylko wykonuje pojedynczy plik, kiedy robisz to w powłoce, wykonuje zarówno pliki wykonywalne, jak /bin/pwdi /bin/ls. Jeśli chcesz robić takie rzeczy w Javie, musisz zrobić coś takiego {"/bin/bash","-c", "pwd && ls"}. Prawdopodobnie nie masz już tego pytania, ale inni ludzie mogą, więc pomyślałem, że mogę na nie odpowiedzieć.
735Tesla
3
Myślę, że odczyt dwóch strumieni musi odbywać się równolegle, ponieważ jeśli, tak jak w twoim przypadku, wyjście stdStream wypełni bufor, nie będziesz w stanie odczytać strumienia błędów.
Li3ro
3
Li3ro ma częściowo rację. Program, którego słuchasz, ma ograniczony bufor dla stdouti stderrwyjścia. Jeśli nie będziesz ich słuchać jednocześnie, jeden z nich zapełni się podczas czytania drugiego. Program, którego słuchasz, będzie wtedy blokował próby zapisu do wypełnionego bufora, podczas gdy na drugim końcu twój program będzie blokował próby odczytu z bufora, który nigdy nie wróci EOF. Państwo musi czytać z obu strumieni jednocześnie.
Gili
1
@Gili Więc dlaczego Li3ro "częściowo" ma rację? Czy Li3ro nie jest po prostu całkowicie i całkowicie w porządku? W tym przypadku nie rozumiem, dlaczego od 2011 roku kręci się tu zła odpowiedź i dlaczego ma ponad 200 głosów za ... To jest mylące.
Andrey Tyukin
2
@AndreyTyukin Masz rację. Wszystkie aktualne odpowiedzi są podatne na zakleszczenia. Zachęcam ich do odrzucenia ich, aby inne odpowiedzi stały się widoczne. Opublikowałem nową odpowiedź do Twojej recenzji: stackoverflow.com/a/57949752/14731 . Mam nadzieję, że dobrze mi się udało ...
Gili,
68

Szybszy sposób jest następujący:

public static String execCmd(String cmd) throws java.io.IOException {
    java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A");
    return s.hasNext() ? s.next() : "";
}

Co jest w zasadzie skondensowaną wersją tego:

public static String execCmd(String cmd) throws java.io.IOException {
    Process proc = Runtime.getRuntime().exec(cmd);
    java.io.InputStream is = proc.getInputStream();
    java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
    String val = "";
    if (s.hasNext()) {
        val = s.next();
    }
    else {
        val = "";
    }
    return val;
}

Wiem, że to pytanie jest stare, ale publikuję tę odpowiedź, ponieważ myślę, że może to być szybsze.

735Tesla
źródło
4
Dzięki za miłą odpowiedź. Dlaczego separatorem jest „\\ A”?
Gottfried
1
Nie pamiętam do końca, jaka była moja logika, kiedy to pisałem. Używam tego rozwiązania od jakiegoś czasu, ale myślę, że było tak, ponieważ \Aw wyrażeniu regularnym oznacza początek ciągu i musiałem uciec przed ukośnikiem.
735 Tesla
5
„\ A” to znak dzwonka. „^” jest początkiem ciągu w wyrażeniu regularnym, a „$” jest końcem ciągu w wyrażeniu regularnym. To postać, której nie spodziewałbyś się zobaczyć. Zgodnie z dokumentacją Java, domyślnym separatorem są białe znaki, więc prawdopodobnie spowodowałoby to wyplucie pełnego wyniku polecenia.
Hank Schultz
11

Oprócz używania ProcessBuilderzgodnie z sugestią Senthil, pamiętaj, aby przeczytać i zaimplementować wszystkie zalecenia When Runtime.exec () nie .

Andrew Thompson
źródło
Wygląda na to, że ten fragment kodu nie zużywa standardowego strumienia błędów (zgodnie z zaleceniami w powiązanym artykule). Nie używa również, ProcessBuilderponieważ teraz zaleca się dwukrotnie. Używając a ProcessBuilder, można połączyć strumienie wyjściowe i strumienie błędów, aby ułatwić jednoczesne wykorzystanie obu.
Andrew Thompson,
11

Jeśli użytkownik ma już Apache commons-io dostępny w ścieżce klas, można użyć:

Process p = new ProcessBuilder("cat", "/etc/something").start();
String stderr = IOUtils.toString(p.getErrorStream(), Charset.defaultCharset());
String stdout = IOUtils.toString(p.getInputStream(), Charset.defaultCharset());
wir
źródło
7

Możemy również użyć strumieni do uzyskania wyniku polecenia:

public static void main(String[] args) throws IOException {

        Runtime runtime = Runtime.getRuntime();
        String[] commands  = {"free", "-h"};
        Process process = runtime.exec(commands);

        BufferedReader lineReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
        lineReader.lines().forEach(System.out::println);

        BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
        errorReader.lines().forEach(System.out::println);
    }
Jackkobec
źródło
7

Wspomniane odpowiedzi @Senthil i @Arend ( https://stackoverflow.com/a/5711150/2268559 ) ProcessBuilder. Oto przykład użycia ProcessBuilderz określeniem zmiennych środowiskowych i folderu roboczego dla polecenia:

    ProcessBuilder pb = new ProcessBuilder("ls", "-a", "-l");

    Map<String, String> env = pb.environment();
    // If you want clean environment, call env.clear() first
    //env.clear();
    env.put("VAR1", "myValue");
    env.remove("OTHERVAR");
    env.put("VAR2", env.get("VAR1") + "suffix");

    File workingFolder = new File("/home/user");
    pb.directory(workingFolder);

    Process proc = pb.start();

    BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));

    BufferedReader stdError = new BufferedReader(new InputStreamReader(proc.getErrorStream()));

    // Read the output from the command:
    System.out.println("Here is the standard output of the command:\n");
    String s = null;
    while ((s = stdInput.readLine()) != null)
        System.out.println(s);

    // Read any errors from the attempted command:
    System.out.println("Here is the standard error of the command (if any):\n");
    while ((s = stdError.readLine()) != null)
        System.out.println(s);
nidalpres
źródło
6

W chwili pisania tego tekstu wszystkie inne odpowiedzi zawierające kod mogą spowodować zakleszczenie.

Procesy mają ograniczony bufor dla stdouti stderrwyjścia. Jeśli nie będziesz ich słuchać jednocześnie, jeden z nich zapełni się, gdy będziesz próbował czytać drugi. Na przykład możesz czekać na odczyt, stdoutpodczas gdy proces czeka na zapis stderr. Nie można czytać z stdoutbufora, ponieważ jest on pusty, a proces nie może zapisywać w stderrbuforze, ponieważ jest pełny. Czekacie na siebie wiecznie.

Oto możliwy sposób odczytu danych wyjściowych procesu bez ryzyka zakleszczenia:

public final class Processes
{
    private static final String NEWLINE = System.getProperty("line.separator");

    /**
     * @param command the command to run
     * @return the output of the command
     * @throws IOException if an I/O error occurs
     */
    public static String run(String... command) throws IOException
    {
        ProcessBuilder pb = new ProcessBuilder(command).redirectErrorStream(true);
        Process process = pb.start();
        StringBuilder result = new StringBuilder(80);
        try (BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream())))
        {
            while (true)
            {
                String line = in.readLine();
                if (line == null)
                    break;
                result.append(line).append(NEWLINE);
            }
        }
        return result.toString();
    }

    /**
     * Prevent construction.
     */
    private Processes()
    {
    }
}

Kluczem jest użycie, ProcessBuilder.redirectErrorStream(true)które przekieruje stderrdo stdoutstrumienia. Pozwala to na odczytanie pojedynczego strumienia bez konieczności przełączania się między stdouti stderr. Jeśli chcesz zaimplementować to ręcznie, będziesz musiał zużywać strumienie w dwóch różnych wątkach, aby upewnić się, że nigdy nie będziesz blokować.

Gili
źródło
Och, wow! Nie spodziewałem się, że tak szybko odpowiesz na komentarz, nie mówiąc już o odpowiedzi na to wiecznie stare pytanie! :) Rozważam teraz rozpoczęcie nagrody. Przyjrzymy się twojej odpowiedzi później. Dzięki!
Andrey Tyukin
1

Jeśli piszesz na Kotlinie, możesz użyć:

val firstProcess = ProcessBuilder("echo","hello world").start()
val firstError = firstProcess.errorStream.readBytes().decodeToString()
val firstResult = firstProcess.inputStream.readBytes().decodeToString()
programista Androida
źródło
0

Na podstawie poprzedniej odpowiedzi:

public static String execCmdSync(String cmd, CmdExecResult callback) throws java.io.IOException, InterruptedException {
    RLog.i(TAG, "Running command:", cmd);

    Runtime rt = Runtime.getRuntime();
    Process proc = rt.exec(cmd);

    //String[] commands = {"system.exe", "-get t"};

    BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));
    BufferedReader stdError = new BufferedReader(new InputStreamReader(proc.getErrorStream()));

    StringBuffer stdOut = new StringBuffer();
    StringBuffer errOut = new StringBuffer();

    // Read the output from the command:
    System.out.println("Here is the standard output of the command:\n");
    String s = null;
    while ((s = stdInput.readLine()) != null) {
        System.out.println(s);
        stdOut.append(s);
    }

    // Read any errors from the attempted command:
    System.out.println("Here is the standard error of the command (if any):\n");
    while ((s = stdError.readLine()) != null) {
        System.out.println(s);
        errOut.append(s);
    }

    if (callback == null) {
        return stdInput.toString();
    }

    int exitVal = proc.waitFor();
    callback.onComplete(exitVal == 0, exitVal, errOut.toString(), stdOut.toString(), cmd);

    return stdInput.toString();
}

public interface CmdExecResult{
    void onComplete(boolean success, int exitVal, String error, String output, String originalCmd);
}
Derek Zhu
źródło
0

Prawie to samo, co inne fragmenty na tej stronie, ale po prostu organizując rzeczy według funkcji , zaczynamy ...

String str=shell_exec("ls -l");

Funkcja Class:

public String shell_exec(String cmd)
       {
       String o=null;
       try
         {
         Process p=Runtime.getRuntime().exec(cmd);
         BufferedReader b=new BufferedReader(new InputStreamReader(p.getInputStream()));
         String r;
         while((r=b.readLine())!=null)o+=r;
         }catch(Exception e){o="error";}
       return o;
       }
PYK
źródło
-1

Spróbuj przeczytać InputStreamczęść środowiska wykonawczego:

Runtime rt = Runtime.getRuntime();
String[] commands = {"system.exe", "-send", argument};
Process proc = rt.exec(commands);
BufferedReader br = new BufferedReader(
    new InputStreamReader(proc.getInputStream()));
String line;
while ((line = br.readLine()) != null)
    System.out.println(line);

Może być również konieczne odczytanie strumienia błędów ( proc.getErrorStream()), jeśli proces drukuje wyjście błędu. Jeśli używasz, możesz przekierować strumień błędów do strumienia wejściowego ProcessBuilder.

brahmananda Kar
źródło