Как заставить трубы работать с Runtime.exec ()?

104

Рассмотрим следующий код:

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

Вывод программы:

/etc:
adduser.co

Когда я бегу из оболочки, конечно, все работает как положено:

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

Интернет сообщает мне, что из-за того, что поведение конвейера не является кроссплатформенным, блестящие умы, работающие на фабрике Java, производящей Java, не могут гарантировать, что конвейеры работают.

Как я могу это сделать?

Я не собираюсь выполнять весь свой синтаксический анализ, используя конструкции Java, а не grepи sed, потому что, если я захочу изменить язык, мне придется переписать код синтаксического анализа на этом языке, что совершенно недопустимо.

Как я могу заставить Java выполнять конвейерную обработку и перенаправление при вызове команд оболочки?

poundifdef
источник
Я вижу это так: если вы сделаете это с собственной обработкой строк Java, вам будет гарантирована переносимость приложения Java на все платформы, поддерживаемые Java. OTOH, если вы делаете это с помощью команд оболочки, легче изменить язык с Java, но это будет работать только тогда, когда вы используете платформу POSIX. Мало кто меняет язык приложения, а не платформу, на которой оно работает. Вот почему я думаю, что ваши рассуждения немного любопытны.
Янус Троелсен
В конкретном случае чего-то простого, например, command | grep fooвам, гораздо лучше просто запустить commandи выполнить фильтрацию непосредственно на Java. Это несколько усложняет ваш код, но вы также значительно сокращаете общее потребление ресурсов и поверхность атаки.
тройняшек

Ответы:

182

Напишите сценарий и выполняйте его вместо отдельных команд.

Труба является частью оболочки, поэтому вы также можете сделать что-то вроде этого:

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

Process p = Runtime.getRuntime().exec(cmd);
Кай
источник
@Kaj Что, если вы хотите добавить параметры в lsie ls -lrt?
Каустав Датта
8
@Kaj Я вижу, что вы пытаетесь использовать -c для указания строки команд для оболочки, но я не понимаю, почему вам нужно сделать это строковым массивом, а не одной строкой?
Дэвид Дориа
2
Если кто-то ищет androidздесь версию, то используйте /system/bin/shвместо
нее
25

Я столкнулся с аналогичной проблемой в Linux, за исключением "ps -ef | grep someprocess".
По крайней мере, с «ls» у вас есть независимая от языка (хотя и более медленная) замена Java. Например.:

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

С «ps» это немного сложнее, потому что Java, похоже, не имеет для этого API.

Я слышал, что Sigar может нам помочь: https://support.hyperic.com/display/SIGAR/Home

Однако самое простое решение (как указал Кай) - выполнить команду, переданную по конвейеру, как массив строк. Вот полный код:

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

Что касается того, почему массив String работает с конвейером, а отдельная строка - нет ... это одна из загадок вселенной (особенно если вы не читали исходный код). Я подозреваю, что это потому, что когда exec дается одна строка, он сначала разбирает ее (что нам не нравится). Напротив, когда exec предоставляется массив строк, он просто передает его в операционную систему, не анализируя его.

На самом деле, если мы выделим время после напряженного дня и посмотрим на исходный код ( 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 ), мы обнаруживаем, что именно это и происходит:

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);
}
Тихамер
источник
Я наблюдаю такое же поведение с перенаправлением, то есть с символом '>'. Этот дополнительный анализ массивов String поучителен
Крис
Вам не нужно выделять день из своего плотного графика - он написан у вас на глазах docs / api / java / lang / Runtime.html # exec (java.lang.String)
gpasch
6

Создайте среду выполнения для запуска каждого процесса. Получите OutputStream из первой среды выполнения и скопируйте его в InputStream из второй.

SJuan76
источник
1
Стоит отметить, что это намного менее эффективно, чем собственный конвейер для ОС, поскольку вы копируете память в адресное пространство процесса Java, а затем обратно в следующий процесс.
Тим Будро
0

@Kaj принятый ответ предназначен для Linux. Это эквивалент для Windows:

String[] cmd = {
"cmd",
"/C",
"dir /B | findstr /R /C:"release""
};
Process p = Runtime.getRuntime().exec(cmd);
кричащий
источник