在Java中运行Bash命令

33

我有以下的类。它允许我通过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”目录的内容?

请参阅当Runtime.exec()无法使用时,以获取有关正确创建和处理进程的许多好提示。然后忽略它所指的exec,并使用ProcessBuilder来创建进程。 - Andrew Thompson
你好。我尝试使用你的类在我的Java应用程序中执行Bash命令,但当我运行"cat"命令时返回null... - Zanidd
8个回答

29

使用Runtime.exec(command)可以开始一个新的进程。每个进程都有一个工作目录,通常是父进程启动时的目录,但你可以更改你的进程启动时的目录。

我建议使用ProcessBuilder

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

如果您想在Shell中运行多个命令,最好创建一个临时Shell脚本并运行它。

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

当然,您也可以在系统上使用任何其他脚本。有时在运行时生成脚本是有意义的,例如,如果要执行的命令必须更改。但在动态生成脚本之前,应首先尝试创建一个可以带参数调用的脚本。

如果脚本生成过程很复杂,使用类似于Velocity的模板引擎也可能是合理的选择。

编辑

您还应考虑将进程构建器的复杂性隐藏在简单的界面后面。

将所需内容(接口)与实现方式(实现)分开。

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

你可以提供使用进程生成器或本地方法来完成工作的实现,并且您可以为不同环境(如Linux或Windows)提供不同的实现。

最后,这样的接口也更容易在单元测试中进行模拟。


在 createTempScriptYou() 函数中,在返回文件之前需要关闭它。 - Fabien
@Fabien 你说得对。我添加了一行关闭 PrintWriter 的代码。因此,底层的读取器和流也将被关闭。 - René Link
我能否执行多个命令而不创建临时文件? - Vova Yatsyk
我需要添加 InterruptedException 到 throws 中以满足编译器的要求(Java 8)。 - Michael
我应该看到“ls”命令的标准错误吗?但是我没有看到。 - the_prole

12

您可以组成一个复杂的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;
}

5

有没有一种方法可以在同一上下文中执行一系列命令? - mickzer
让我重新表述我的问题。有没有一种方式可以在相同的上下文中依次执行一系列命令?例如,是否可以将一系列用户输入传递到 shell 进程中? - mickzer
com.executeCommand("ls; cd bin; ls"); 导致 IOException 抛出:Cannot run program "ls;": error=2, No such file or directory - djthoms

2

每个执行的命令都有它自己的bash shell,因此一旦你cd到那个目录并执行下一个命令,你就会打开一个新的bash shell。

尝试将你的命令更改为:

ls bin

1
有没有一种方法可以在同一上下文中执行一系列命令? - mickzer
1
你可以将它们包装在一个bash脚本中并调用它,也可以使用ProcessBuilder代替Runtime - jmj
1
小修正,如问题所述,命令是在没有 shell 的情况下执行的。指定的程序只是在一个分叉的进程中执行。 - Dev

1

供将来参考:在子目录中cd后运行bash命令:

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

1
每个命令都是单独执行的,它们不共享上下文。

1
有没有一种方法可以在同一上下文中执行一系列命令? - mickzer

1
您可以使用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('%')));
}

0

这对我有用。

public static void executeBashCommand(String command) throws IOException, InterruptedException
{
    new ProcessBuilder("/bin/bash", "-c", command).inheritIO().start().waitFor();
}

导入以下包。

import java.io.IOException;

并将主函数更改为以下内容。

public static void main(String args[]) throws Exception

例子:

executeBashCommand("ls");

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接