Jak sprawić, by potoki działały z Runtime.exec ()?

104

Rozważ następujący kod:

String commandf = "ls /etc | grep release";

try {

    // Execute the command and wait for it to complete
    Process child = Runtime.getRuntime().exec(commandf);
    child.waitFor();

    // Print the first 16 bytes of its output
    InputStream i = child.getInputStream();
    byte[] b = new byte[16];
    i.read(b, 0, b.length); 
    System.out.println(new String(b));

} catch (IOException e) {
    e.printStackTrace();
    System.exit(-1);
}

Wynik programu to:

/etc:
adduser.co

Kiedy biegnę z powłoki, to oczywiście działa zgodnie z oczekiwaniami:

poundifdef@parker:~/rabbit_test$ ls /etc | grep release
lsb-release

Internety mówią mi, że ze względu na fakt, że zachowanie potoków nie jest wieloplatformowe, genialne umysły pracujące w fabryce Javy produkującej Javę nie mogą zagwarantować, że potoki będą działać.

W jaki sposób mogę to zrobić?

Nie zamierzam wykonywać całego analizowania przy użyciu konstrukcji Java zamiast grepised , bo jeśli chcę zmienić język, będę zmuszony do ponownego napisać kod parsowania w tym języku, który jest całkowicie nie-Go.

Jak sprawić, by Java wykonywała potoki i przekierowywanie podczas wywoływania poleceń powłoki?

poundifdef
źródło
Widzę to tak: jeśli robisz to z natywną obsługą ciągów Java, masz gwarancję przenoszenia aplikacji Java na wszystkie platformy obsługujące Java. OTOH, jeśli robisz to za pomocą poleceń powłoki, łatwiej jest zmienić język z Java, ale zadziała tylko wtedy, gdy jesteś na platformie POSIX. Niewiele osób zmienia język aplikacji zamiast platformy, na której działa aplikacja. Dlatego myślę, że twoje rozumowanie jest trochę ciekawe.
Janus Troelsen
W konkretnym przypadku czegoś prostego, takiego jak command | grep footy, znacznie lepiej jest po prostu uruchomić commandi wykonać filtrowanie natywnie w Javie. To sprawia, że ​​twój kod jest nieco bardziej złożony, ale także znacznie zmniejszasz ogólne zużycie zasobów i powierzchnię ataku.
tripleee

Odpowiedzi:

182

Napisz skrypt i wykonaj go zamiast oddzielnych poleceń.

Rura jest częścią powłoki, więc możesz też zrobić coś takiego:

String[] cmd = {
"/bin/sh",
"-c",
"ls /etc | grep release"
};

Process p = Runtime.getRuntime().exec(cmd);
Kaj
źródło
@Kaj Co jeśli chciał dodać opcje do lsIE ls -lrt?
kaustav datta
8
@Kaj Widzę, że próbujesz użyć -c, aby określić ciąg poleceń dla powłoki, ale nie rozumiem, dlaczego musisz uczynić to tablicą ciągów zamiast pojedynczego ciągu?
David Doria,
2
Jeśli ktoś szuka androidtutaj wersji, to użyj /system/bin/shzamiast tego
Nexen
25

Napotkałem podobny problem w Linuksie, z wyjątkiem tego, że był to „ps -ef | grep jakiś proces”.
Przynajmniej z "ls" masz niezależną od języka (choć wolniejszą) zamiennik Javy. Na przykład.:

File f = new File("C:\\");
String[] files = f.listFiles(new File("/home/tihamer"));
for (String file : files) {
    if (file.matches(.*some.*)) { System.out.println(file); }
}

Z "ps" jest trochę trudniej, ponieważ Java nie ma do tego API.

Słyszałem, że Sigar może nam pomóc: https://support.hyperic.com/display/SIGAR/Home

Jednak najprostszym rozwiązaniem (jak wskazał Kaj) jest wykonanie polecenia potokowego jako tablicy łańcuchowej. Oto pełny kod:

try {
    String line;
    String[] cmd = { "/bin/sh", "-c", "ps -ef | grep export" };
    Process p = Runtime.getRuntime().exec(cmd);
    BufferedReader in =
            new BufferedReader(new InputStreamReader(p.getInputStream()));
    while ((line = in.readLine()) != null) {
        System.out.println(line); 
    }
    in.close();
} catch (Exception ex) {
    ex.printStackTrace();
}

Co do tego, dlaczego tablica String działa z potokiem, podczas gdy pojedynczy ciąg nie ... to jedna z tajemnic wszechświata (zwłaszcza jeśli nie czytałeś kodu źródłowego). Podejrzewam, że dzieje się tak dlatego, że gdy exec otrzymuje pojedynczy ciąg, najpierw go analizuje (w sposób, który nam się nie podoba). W przeciwieństwie do tego, gdy exec otrzymuje tablicę ciągów, po prostu przekazuje ją do systemu operacyjnego bez jej analizowania.

Właściwie, jeśli poświęcimy trochę czasu z pracowitego dnia i spojrzymy na kod źródłowy (na http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/lang/ Runtime.java # Runtime.exec% 28java.lang.String% 2Cjava.lang.String []% 2Cjava.io.File% 29 ), okazuje się, że dokładnie to się dzieje:

public Process  [More ...] exec(String command, String[] envp, File dir) 
          throws IOException {
    if (command.length() == 0)
        throw new IllegalArgumentException("Empty command");
    StringTokenizer st = new StringTokenizer(command);
    String[] cmdarray = new String[st.countTokens()];
    for (int i = 0; st.hasMoreTokens(); i++)
        cmdarray[i] = st.nextToken();
    return exec(cmdarray, envp, dir);
}
Tihamer
źródło
Widzę to samo zachowanie z przekierowaniem, tj. Ze znakiem „>”. Ta dodatkowa analiza tablic String jest pouczająca
kris
Nie musisz
tracić
6

Utwórz środowisko wykonawcze, aby uruchomić każdy proces. Pobierz OutputStream z pierwszego Runtime i skopiuj go do InputStream z drugiego.

SJuan76
źródło
1
Warto zauważyć, że jest to znacznie mniej wydajne niż potok natywny dla systemu operacyjnego, ponieważ kopiujesz pamięć do przestrzeni adresowej procesu Java, a następnie wracasz do kolejnego procesu.
Tim Boudreau,
0

@Kaj zaakceptowana odpowiedź dotyczy linuxa. To jest odpowiednik dla systemu Windows:

String[] cmd = {
"cmd",
"/C",
"dir /B | findstr /R /C:"release""
};
Process p = Runtime.getRuntime().exec(cmd);
żółw
źródło