Как заставить трубы работать с Runtime.exec ()? - PullRequest
96 голосов
/ 08 мая 2011

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

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 выполнять конвейерную передачу и перенаправление при вызове команд оболочки?

Ответы [ 3 ]

169 голосов
/ 08 мая 2011

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

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

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

Process p = Runtime.getRuntime().exec(cmd);
24 голосов
/ 15 октября 2013

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

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

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

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);
}
6 голосов
/ 08 мая 2011

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

...