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

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

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

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

[email protected]:~/rabbit_test$ ls /etc | grep release
lsb-release

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

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

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

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

Ответ 1

Напишите script и выполните script вместо отдельных команд.

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

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

Process p = Runtime.getRuntime().exec(cmd);

Ответ 2

Я столкнулся с аналогичной проблемой в 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 для него.

Я слышал, что Сигар мог бы нам помочь: https://support.hyperic.com/display/SIGAR/Home

Простейшее решение, однако, (как указано Kaj) заключается в том, чтобы выполнить команду piped как строковый массив. Вот полный код:

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 работает с pipe, в то время как одна строка не... это одна из загадок вселенной (особенно если вы не читали исходный код). Я подозреваю, что это потому, что, когда 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);
}

Ответ 3

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