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


Я вважаю це так: Якщо ви робите це з використанням нативного Java-рядка, ви гарантовано переносите додаток Java на всі платформи підтримки Java. Ото, якщо ви робите це з допомогою команд оболонки, це легше змінити LANGAUGE від Java, але буде працювати тільки тоді , коли ви знаходитесь на платформі POSIX. Мало хто змінює мову додатка, а не платформу, на якій працює програма. Тому я вважаю, що ваші міркування трохи цікаві.
Янус Трольсен

У конкретному випадку чогось такого простого, як command | grep fooвам, набагато краще просто запустити commandта виконати фільтрацію на Яві. Це робить ваш код дещо складнішим, але ви також значно скорочуєте загальне споживання ресурсів та атакуючу поверхню.
трійка

Відповіді:


182

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

Труба є частиною оболонки, тому ви також можете зробити щось подібне:

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

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

@Kaj Що робити, якщо ви хочете додати варіанти, lsтобто ls -lrt?
kaustav datta

8
@Kaj Я бачу, що ви намагаєтесь використовувати -c, щоб вказати рядок команд для оболонки, але я не розумію, чому ви повинні робити цей рядковий масив замість просто однієї струни?
Девід Дорія

2
Якщо хтось шукає androidверсію тут, то використовуйте /system/bin/shзамість цього
Nexen

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

Однак найпростішим рішенням (на що вказував 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 масив працює з трубою, тоді як жодна рядок не робить ... це одна з загадок Всесвіту (особливо якщо ви не читали вихідний код). Я підозрюю, що це тому, що коли 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-масивах є освічуючим
kris

Вам не потрібно приймати день зі свого насиченого розкладу - його написано перед вашими очима docs / api / java / lang / Runtime.html # exec (java.lang.String)
gpasch

6

Створіть час виконання для запуску кожного процесу. Отримайте OutputStream з першого виконання та скопіюйте його в InputStream з другого.


1
Варто зазначити, що це набагато менш ефективно, ніж основний канал, оскільки ви копіюєте пам'ять у адресний простір Java-процесу, а потім повертаєтесь до наступного процесу.
Тім Будро

0

@Kaj прийнята відповідь призначена для Linux. Це еквівалент для Windows:

String[] cmd = {
"cmd",
"/C",
"dir /B | findstr /R /C:"release""
};
Process p = Runtime.getRuntime().exec(cmd);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.