【问题标题】:Running Bash commands in Java在 Java 中运行 Bash 命令
【发布时间】:2014-11-09 16:51:16
【问题描述】:

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

【问题讨论】:

  • 另请参阅When Runtime.exec() won't,了解有关正确创建和处理流程的许多好技巧。然后忽略它引用exec 并使用ProcessBuilder 创建进程。
  • 你好。我尝试使用您的类在我的 Java 应用程序中执行 Bash 命令,但是当我运行命令“cat”时它返回 null...

标签: java bash runtime.exec


【解决方案1】:

您使用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 你是对的。我添加了关闭PrintWriter 的行。因此,底层阅读器和流也将关闭。
  • 我可以在不创建临时文件的情况下执行多个命令吗?
  • 我需要在 throws 中添加一个InterruptedException 以满足编译器(Java 8)的要求
  • 我应该看到ls 命令的标准错误吗?我没有。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    每个调用都在其自己的 shell 中执行。因此,第二次调用的“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");
    

    【讨论】:

    • 有没有办法在同一个上下文中执行一系列命令?
    • 让我重新表述我的问题.. 有没有办法在同一上下文中一个接一个地执行一系列命令?例如,是否有可能获取一系列用户输入并将它们传递给 shell 进程?
    • com.executeCommand("ls; cd bin; ls"); 导致 IOException 引发:Cannot run program "ls;": error=2, No such file or directory
    【解决方案4】:

    您正在运行的每个命令都有自己的 bash shell,因此一旦您 cd 到该目录并为下一个命令打开新的 bash shell

    尝试将您的命令更改为

    ls bin
    

    【讨论】:

    • 有没有办法在同一个上下文中执行一系列命令?
    • 您可以将它们包装在 bash 脚本中并调用它,也可以使用 ProcessBuilder 而不是 Runtime
    • 轻微更正,如问题中所述,命令在没有外壳的情况下执行。指定的程序只是在一个分叉的进程中执行。
    【解决方案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】:

      供将来参考:在 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);
              }
          }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-05-14
        • 2021-08-31
        • 1970-01-01
        • 2016-10-13
        • 1970-01-01
        • 2012-03-07
        相关资源
        最近更新 更多