Jak wywołać polecenie powłoki systemu Linux z języka Java

93

Próbuję wykonać niektóre polecenia Linuksa z Javy za pomocą przekierowania (> &) i potoków (|). W jaki sposób Java może wywoływać cshlub bashpolecenia?

Próbowałem tego użyć:

Process p = Runtime.getRuntime().exec("shell command");

Ale nie jest kompatybilny z przekierowaniami ani potokami.

Narek
źródło
2
cati cshnie mają ze sobą nic wspólnego.
Bombe
4
Rozumiem pytanie dotyczące innych poleceń, ale dla kota: dlaczego do cholery nie czytasz po prostu w pliku?
Atmocreations
8
Każdy popełnia ten błąd za pierwszym razem - metoda exec () w Javie nie używa powłoki systemu bazowego do wykonania polecenia (jak wskazuje kts). Przekierowanie i potoki są cechami prawdziwej powłoki i nie są dostępne z funkcji exec () języka Java.
SteveD,
stevendick: Dziękuję bardzo, miałem problemy z przekierowaniem i potokami!
Narek
System.exit (0) nie znajduje się wewnątrz warunkowego sprawdzania, czy proces jest zakończony, więc zawsze kończy pracę bez wyświetlania błędów. Nigdy nie pisz warunkowych bez nawiasów klamrowych, aby uniknąć dokładnie tego rodzaju błędów.

Odpowiedzi:

97

exec nie wykonuje polecenia w twojej powłoce

próbować

Process p = Runtime.getRuntime().exec(new String[]{"csh","-c","cat /home/narek/pk.txt"});

zamiast.

EDYCJA :: Nie mam csh w moim systemie, więc zamiast tego użyłem basha. Poniższe zadziałały dla mnie

Process p = Runtime.getRuntime().exec(new String[]{"bash","-c","ls /home/XXX"});
KitsuneYMG
źródło
@Narek. Przepraszam za to. Naprawiłem to, usuwając dodatkowe \ ". Najwyraźniej nie są potrzebne. Nie mam csh w moim systemie, ale działa z bash.
KitsuneYMG
3
Jak wspominali inni, powinno to być niezależne, czy masz csh czy bash, prawda?
Narek
@Narek. Powinien, ale ja nie wiem, jak csh obsługuje argumenty.
KitsuneYMG
1
Dziękuję Ci. To zadziałało. Właściwie użyłem „sh” zamiast „csh”.
Farshid
5
Ostrzeżenie: to rozwiązanie najprawdopodobniej napotka typowy problem z zawieszaniem się, ponieważ nie odczytałeś strumieni wyjściowych i strumieni błędów. Na przykład: stackoverflow.com/questions/8595748/java-runtime-exec
Evgeni Sergeev
32

Użyj ProcessBuilder, aby oddzielić polecenia i argumenty zamiast spacji. To powinno działać niezależnie od używanej powłoki:

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

public class Test {

    public static void main(final String[] args) throws IOException, InterruptedException {
        //Build command 
        List<String> commands = new ArrayList<String>();
        commands.add("/bin/cat");
        //Add arguments
        commands.add("/home/narek/pk.txt");
        System.out.println(commands);

        //Run macro on target
        ProcessBuilder pb = new ProcessBuilder(commands);
        pb.directory(new File("/home/narek"));
        pb.redirectErrorStream(true);
        Process process = pb.start();

        //Read output
        StringBuilder out = new StringBuilder();
        BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
        String line = null, previous = null;
        while ((line = br.readLine()) != null)
            if (!line.equals(previous)) {
                previous = line;
                out.append(line).append('\n');
                System.out.println(line);
            }

        //Check result
        if (process.waitFor() == 0) {
            System.out.println("Success!");
            System.exit(0);
        }

        //Abnormal termination: Log command parameters and output and throw ExecutionException
        System.err.println(commands);
        System.err.println(out.toString());
        System.exit(1);
    }
}
Tim
źródło
Nawet po wykonaniu java.util. *; jest poprawnie zaimportowany Nie mogę uruchomić powyższego przykładu.
Steve K,
@Stephan Zaktualizowałem powyższy przykładowy kod, aby usunąć instrukcje logowania i zmienne, które zostały zadeklarowane poza wklejonym kodem, i powinien teraz skompilować i uruchomić. Zapraszam do spróbowania i daj mi znać, jak to działa.
Tim,
15

Opierając się na przykładzie @Tima, aby utworzyć samodzielną metodę:

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.util.ArrayList;

public class Shell {

    /** Returns null if it failed for some reason.
     */
    public static ArrayList<String> command(final String cmdline,
    final String directory) {
        try {
            Process process = 
                new ProcessBuilder(new String[] {"bash", "-c", cmdline})
                    .redirectErrorStream(true)
                    .directory(new File(directory))
                    .start();

            ArrayList<String> output = new ArrayList<String>();
            BufferedReader br = new BufferedReader(
                new InputStreamReader(process.getInputStream()));
            String line = null;
            while ( (line = br.readLine()) != null )
                output.add(line);

            //There should really be a timeout here.
            if (0 != process.waitFor())
                return null;

            return output;

        } catch (Exception e) {
            //Warning: doing this is no good in high quality applications.
            //Instead, present appropriate error messages to the user.
            //But it's perfectly fine for prototyping.

            return null;
        }
    }

    public static void main(String[] args) {
        test("which bash");

        test("find . -type f -printf '%T@\\\\t%p\\\\n' "
            + "| sort -n | cut -f 2- | "
            + "sed -e 's/ /\\\\\\\\ /g' | xargs ls -halt");

    }

    static void test(String cmdline) {
        ArrayList<String> output = command(cmdline, ".");
        if (null == output)
            System.out.println("\n\n\t\tCOMMAND FAILED: " + cmdline);
        else
            for (String line : output)
                System.out.println(line);

    }
}

(Przykładem testowym jest polecenie, które wyświetla rekursywnie listę wszystkich plików w katalogu i jego podkatalogach w porządku chronologicznym ).

Swoją drogą, jeśli ktoś może mi powiedzieć, dlaczego potrzebuję tam czterech i ośmiu ukośników, zamiast dwóch i czterech, mogę się czegoś nauczyć. Jest jeszcze jeden poziom nieuniknionego wydarzenia niż to, co liczę.

Edycja: właśnie wypróbowałem ten sam kod w systemie Linux i okazało się, że potrzebuję o połowę mniej ukośników odwrotnych w poleceniu test! (To znaczy: oczekiwana liczba dwa i cztery.) Teraz to już nie jest tylko dziwne, to problem z przenośnością.

Evgeni Sergeev
źródło
Powinieneś zadać swoje pytanie jako nowe pytanie StackOverflow, a nie jako część odpowiedzi.
vog
Działa z dwoma i czterema na OSX / Oracle java8. Wygląda na to, że wystąpił problem z oryginalnym środowiskiem Java (o którym nie wspominasz)
TheMadsen