Запуск команд Bash в Java

У меня есть следующий класс. Это позволяет мне выполнять команды через java.

public class ExecuteShellCommand {

public String executeCommand(String command) {

    StringBuffer output = new StringBuffer();

    Process p;
    try {
        p = Runtime.getRuntime().exec(command);
        p.waitFor();
        BufferedReader reader = 
                        new BufferedReader(new InputStreamReader(p.getInputStream()));

        String line = "";           
        while ((line = reader.readLine())!= null) {
            output.append(line + "\n");
        }

    } catch (Exception e) {
        e.printStackTrace();
    }

    return output.toString();

}

}

Когда я запускаю команды, результат предыдущей команды не сохраняется. Например:

public static void main(String args[]) {

    ExecuteShellCommand com = new ExecuteShellCommand();
    System.out.println(com.executeCommand("ls"));
    System.out.println(com.executeCommand("cd bin"));
    System.out.println(com.executeCommand("ls"));

}

Выдает вывод:

bin
src


bin
src

Почему вторая команда 'ls' не показывает содержимое каталога bin?

Ответ 1

Вы начинаете новый процесс с Runtime.exec(command). У каждого процесса есть рабочий каталог. Обычно это каталог, в котором запущен родительский процесс, но вы можете изменить каталог, в котором запущен ваш процесс.

Я бы порекомендовал использовать ProcessBuilder

ProcessBuilder pb = new ProcessBuilder("ls");
pb.inheritIO();
pb.directory(new File("bin"));
pb.start();

Если вы хотите выполнить несколько команд в оболочке, лучше создать временный сценарий оболочки и запустить его.

public void executeCommands() throws IOException {

    File tempScript = createTempScript();

    try {
        ProcessBuilder pb = new ProcessBuilder("bash", tempScript.toString());
        pb.inheritIO();
        Process process = pb.start();
        process.waitFor();
    } finally {
        tempScript.delete();
    }
}

public File createTempScript() throws IOException {
    File tempScript = File.createTempFile("script", null);

    Writer streamWriter = new OutputStreamWriter(new FileOutputStream(
            tempScript));
    PrintWriter printWriter = new PrintWriter(streamWriter);

    printWriter.println("#!/bin/bash");
    printWriter.println("cd bin");
    printWriter.println("ls");

    printWriter.close();

    return tempScript;
}

Конечно, вы также можете использовать любой другой скрипт в вашей системе. Генерация скрипта во время выполнения имеет смысл, например, если выполняемые команды должны измениться. Но сначала вы должны попытаться создать один сценарий, который можно вызывать с аргументами, а не генерировать его динамически во время выполнения.

Также может быть разумно использовать шаблонизатор, такой как скорость, если генерация скрипта сложна.

EDIT

Вам также следует подумать о том, чтобы скрыть всю сложность компоновщика процессов за простым интерфейсом.

Отделите то, что вам нужно (интерфейс) от того, как это делается (реализация).

public interface FileUtils {
    public String[] listFiles(String dirpath);
}

Затем вы можете предоставить реализации, которые используют построитель процессов или, возможно, собственные методы для выполнения работы, и вы можете предоставить различные реализации для разных сред, таких как Linux или Windows.

Наконец, такой интерфейс также проще для проверки в модульных тестах.

Ответ 2

Каждый вызов выполняет в своей собственной оболочке. Таким образом, "cd" второго вызова не рассматривается третьим.

Смотрите: https://docs.oracle.com/javase/7/docs/api/java/lang/Runtime.html#exec(java.lang.String).

Это означает, что команда запускается в отдельном процессе. Таким образом, вы создали 3 процесса.

Если вы хотите, чтобы все 3 в одном и том же процессе, попробуйте следующее:

com.executeCommand("ls; cd bin; ls");

Ответ 3

Вы можете создать одну сложную команду bash, которая делает все: "ls; cd bin; ls". Для выполнения этой работы вам необходимо явно вызвать bash. Этот подход должен дать вам всю мощь командной строки bash (обработка цитаты, расширение $, трубы и т.д.).

/**
 * Execute a bash command. We can handle complex bash commands including
 * multiple executions (; | && ||), quotes, expansions ($), escapes (\), e.g.:
 *     "cd /abc/def; mv ghi 'older ghi '$(whoami)"
 * @param command
 * @return true if bash got started, but your command may have failed.
 */
public static boolean executeBashCommand(String command) {
    boolean success = false;
    System.out.println("Executing BASH command:\n   " + command);
    Runtime r = Runtime.getRuntime();
    // Use bash -c so we can handle things like multi commands separated by ; and
    // things like quotes, $, |, and \. My tests show that command comes as
    // one argument to bash, so we do not need to quote it to make it one thing.
    // Also, exec may object if it does not have an executable file as the first thing,
    // so having bash here makes it happy provided bash is installed and in path.
    String[] commands = {"bash", "-c", command};
    try {
        Process p = r.exec(commands);

        p.waitFor();
        BufferedReader b = new BufferedReader(new InputStreamReader(p.getInputStream()));
        String line = "";

        while ((line = b.readLine()) != null) {
            System.out.println(line);
        }

        b.close();
        success = true;
    } catch (Exception e) {
        System.err.println("Failed to execute bash with command: " + command);
        e.printStackTrace();
    }
    return success;
}

Ответ 4

каждая выполняемая вами команда имеет свою собственную оболочку bash, поэтому, как только вы подключитесь к этому каталогу, и для следующей команды вы откроете новую оболочку bash

попробуйте изменить команду на

ls bin

Ответ 5

Каждая команда выполняется индивидуально. Они не разделяют контекст.

Ответ 6

Вы можете использовать команду bash "pmset -g batt", как в методе ниже, ведьма возвращает процент заряда батареи

public int getPercentage() {
    Process process = null;
    try {
        process = Runtime.getRuntime().exec("pmset -g batt");
    } catch (IOException e) {
        e.printStackTrace();
    }
    BufferedReader reader = new BufferedReader(new InputStreamReader(
            process.getInputStream()));
    String s = null;
    String y = "";
    while (true) {
        try {
            if (!((s = reader.readLine()) != null)) break;
        } catch (IOException e) {
            e.printStackTrace();
        }
        y += s;
        System.out.println("Script output: " + s);
    }
    return Integer.parseInt(y.substring(y.indexOf(')') + 2, y.indexOf('%')));
}

Ответ 7

для справок в будущем: запуск bash команд после cd, в подкаталоге:

import java.io.BufferedReader;
import java.io.InputStreamReader;

/*

$ ( D=somewhere/else ; mkdir -p $D ; cd $D ; touch file1 file2 ; )
$ javac BashCdTest.java && java BashCdTest
 .. stdout: -rw-r--r-- 1 ubuntu ubuntu 0 Dec 28 12:47 file1
 .. stdout: -rw-r--r-- 1 ubuntu ubuntu 0 Dec 28 12:47 file2
 .. stderr: /bin/ls: cannot access isnt_there: No such file or directory
 .. exit code:2

*/
class BashCdTest
    {
    static void execCommand(String[] commandArr)
        {
        String line;
        try
            {
            Process p = Runtime.getRuntime().exec(commandArr);
            BufferedReader stdoutReader = new BufferedReader(new InputStreamReader(p.getInputStream()));
            while ((line = stdoutReader.readLine()) != null) {
                // process procs standard output here
                System.out.println(" .. stdout: "+line);
                }
            BufferedReader stderrReader = new BufferedReader(new InputStreamReader(p.getErrorStream()));
            while ((line = stderrReader.readLine()) != null) {
                // process procs standard error here
                System.err.println(" .. stderr: "+line);
                }
            int retValue = p.waitFor();
            System.out.println(" .. exit code:"+Integer.toString(retValue));
            }
        catch(Exception e)
            { System.err.println(e.toString()); }
        }

    public static void main(String[] args)
        {
        String flist = "file1 file2 isnt_there";
        String outputDir = "./somewhere/else";
        String[] cmd = {
            "/bin/bash", "-c",
            "cd "+outputDir+" && /bin/ls -l "+flist+" && /bin/rm "+flist
            };
        execCommand(cmd);
        }
    }