Java 为什么 Runtime.exec(String) 对某些而不是所有命令有效?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/31776546/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 11:36:23  来源:igfitidea点击:

Why does Runtime.exec(String) work for some but not all commands?

javaruntime.exec

提问by that other guy

When I try to run Runtime.exec(String), certain commands work, while other commands are executed but fail or do different things than in my terminal. Here is a self-contained test case that demonstrates the effect:

当我尝试运行时Runtime.exec(String),某些命令可以工作,而其他命令会执行但失败或执行与我的终端不同的操作。这是一个自包含的测试用例,演示了效果:

public class ExecTest {
  static void exec(String cmd) throws Exception {
    Process p = Runtime.getRuntime().exec(cmd);

    int i;
    while( (i=p.getInputStream().read()) != -1) {
      System.out.write(i);
    }
    while( (i=p.getErrorStream().read()) != -1) {
      System.err.write(i);
    }
  }

  public static void main(String[] args) throws Exception {
    System.out.print("Runtime.exec: ");
    String cmd = new java.util.Scanner(System.in).nextLine();
    exec(cmd);
  }
}

The example works great if I replace the command with echo hello world, but for other commands -- especially those involving filenames with spaces like here -- I get errors even though the command is clearly being executed:

如果我将命令替换为echo hello world,该示例效果很好,但是对于其他命令——尤其是那些涉及像这里这样的带有空格的文件名的命令——即使命令显然正在执行,我也会收到错误:

myshell$ javac ExecTest.java && java ExecTest
Runtime.exec: ls -l 'My File.txt'
ls: cannot access 'My: No such file or directory
ls: cannot access File.txt': No such file or directory

meanwhile, copy-pasting to my shell:

同时,复制粘贴到我的外壳:

myshell$ ls -l 'My File.txt'
-rw-r--r-- 1 me me 4 Aug  2 11:44 My File.txt

Why is there a difference? When does it work and when does it fail? How do I make it work for all commands?

为什么有区别?什么时候有效,什么时候失败?如何使它适用于所有命令?

采纳答案by that other guy

Why do some commands fail?

为什么有些命令会失败?

This happens because the command passed to Runtime.exec(String)is not executed in a shell. The shell performs a lot of common support services for programs, and when the shell is not around to do them, the command will fail.

发生这种情况是因为传递给的命令Runtime.exec(String)不是在 shell 中执行的。shell 为程序执行很多常见的支持服务,当 shell 不在身边时,命令就会失败。

When do commands fail?

命令何时失败?

A command will fail whenever it depends on a shell features. The shell does a lot of common, useful things we don't normally think about:

只要命令依赖于 shell 功能,它就会失败。shell 做了很多我们通常不会想到的常见的、有用的事情:

  1. The shell splits correctly on quotes and spaces

    This makes sure the filename in "My File.txt"remains a single argument.

    Runtime.exec(String)naively splits on spaces and would pass this as two separate filenames. This obviously fails.

  2. The shell expands globs/wildcards

    When you run ls *.doc, the shell rewrites it into ls letter.doc notes.doc.

    Runtime.exec(String)doesn't, it just passes them as arguments.

    lshas no idea what *is, so the command fails.

  3. The shell manages pipes and redirections.

    When you run ls mydir > output.txt, the shell opens "output.txt" for command output and removes it from the command line, giving ls mydir.

    Runtime.exec(String)doesn't. It just passes them as arguments.

    lshas no idea what >means, so the command fails.

  4. The shell expands variables and commands

    When you run ls "$HOME"or ls "$(pwd)", the shell rewrites it into ls /home/myuser.

    Runtime.exec(String)doesn't, it just passes them as arguments.

    lshas no idea what $means, so the command fails.

  1. 外壳在引号和空格上正确拆分

    这确保文件名 in"My File.txt"保持单个参数。

    Runtime.exec(String)天真地分割空格并将其作为两个单独的文件名传递。这显然失败了。

  2. 外壳扩展球体/通配符

    当您运行时ls *.doc,shell 会将其重写为ls letter.doc notes.doc.

    Runtime.exec(String)没有,它只是将它们作为参数传递。

    ls不知道是什么*,所以命令失败。

  3. shell 管理管道和重定向。

    当您运行时ls mydir > output.txt,shell 打开“output.txt”以获取命令输出并将其从命令行中删除,给出ls mydir.

    Runtime.exec(String)没有。它只是将它们作为参数传递。

    ls不知道什么>意思,所以命令失败。

  4. shell 扩展变量和命令

    当您运行ls "$HOME"或 时ls "$(pwd)",shell 会将其重写为ls /home/myuser.

    Runtime.exec(String)没有,它只是将它们作为参数传递。

    ls不知道什么$意思,所以命令失败。

What can you do instead?

你能做什么呢?

There are two ways to execute arbitrarily complex commands:

有两种方法可以执行任意复杂的命令:

Simple and sloppy:delegate to a shell.

简单粗暴:委托给一个shell。

You can just use Runtime.exec(String[])(note the array parameter) and pass your command directly to a shell that can do all the heavy lifting:

您可以使用Runtime.exec(String[])(注意数组参数)并将您的命令直接传递给可以完成所有繁重工作的 shell:

// Simple, sloppy fix. May have security and robustness implications
String myFile = "some filename.txt";
String myCommand = "cp -R '" + myFile + "' $HOME 2> errorlog";
Runtime.getRuntime().exec(new String[] { "bash", "-c", myCommand });

Secure and robust:take on the responsibilities of the shell.

安全和健壮:承担外壳的责任。

This is not a fix that can be mechanically applied, but requires an understanding the Unix execution model, what shells do, and how you can do the same. However, you can get a solid, secure and robust solution by taking the shell out of the picture. This is facilitated by ProcessBuilder.

这不是可以机械应用的修复,但需要了解 Unix 执行模型、shell 的作用以及如何执行相同的操作。但是,您可以通过将外壳从图片中取出来获得可靠、安全和健壮的解决方案。这是由ProcessBuilder.

The command from the previous example that requires someone to handle 1. quotes, 2. variables, and 3. redirections, can be written as:

上一个示例中需要有人处理 1. 引号、2. 变量和 3. 重定向的命令可以写成:

String myFile = "some filename.txt";
ProcessBuilder builder = new ProcessBuilder(
    "cp", "-R", myFile,        // We handle word splitting
       System.getenv("HOME")); // We handle variables
builder.redirectError(         // We set up redirections
    ProcessBuilder.Redirect.to(new File("errorlog")));
builder.start();