Вопрос-ответ

How to make pipes work with Runtime.exec()?

Как заставить каналы работать с 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

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

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

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

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

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

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

Переведено автоматически
Ответ 1

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

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

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 для этого.

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

Однако самым простым решением (как указал Kaj) является выполнение передаваемой по каналу команды в виде массива строк. Вот полный код:

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

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

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

Ответ 4

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

String[] cmd = {
"cmd",
"/C",
"dir /B | findstr /R /C:"release""
};
Process p = Runtime.getRuntime().exec(cmd);
java