为什么runtime.exec(字符串)对某些命令有效,但不是对所有命令都有效?

w51jfk4q  于 2021-07-06  发布在  Java
关注(0)|答案(1)|浏览(257)

当我想跑的时候 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);
  }
}

如果将命令替换为 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

同时,复制粘贴到我的shell:

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

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

zengzsys

zengzsys1#

为什么有些命令会失败?

这是因为命令传递给 Runtime.exec(String) 不是在shell中执行的。shell为程序执行许多公共支持服务,当shell不在时,命令将失败。

什么时候命令失败?

只要命令依赖于shell特性,它就会失败。shell做了很多我们通常不会想到的常见的、有用的事情:
shell在引号和空格上正确拆分
这样可以确保文件名 "My File.txt" 仍然是一个单一的论点。 Runtime.exec(String) 简单地在空格上拆分,并将其作为两个单独的文件名传递。这显然是失败的。
shell扩展globs/通配符
当你跑的时候 ls *.doc ,shell将其重写为 ls letter.doc notes.doc . Runtime.exec(String) 没有,它只是把它们作为参数传递。 ls 不知道是什么 * 是,所以命令失败。
shell管理管道和重定向。
当你跑的时候 ls mydir > output.txt ,shell为命令输出打开“output.txt”,并将其从命令行中删除,给出 ls mydir . Runtime.exec(String) 不。它只是把它们当作论点来传递。 ls 不知道是什么 > 意思是,所以命令失败了。
shell展开变量和命令
当你跑的时候 ls "$HOME" 或者 ls "$(pwd)" ,shell将其重写为 ls /home/myuser . Runtime.exec(String) 没有,它只是把它们作为参数传递。 ls 不知道是什么 $ 意思是,所以命令失败了。

你能做些什么?

有两种方法可以执行任意复杂的命令:
简单而草率:委托给shell。
你可以用 Runtime.exec(String[]) (注意数组参数)并将命令直接传递给一个shell,该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 });

安全可靠:承担壳牌公司的责任。
这不是一个可以机械地应用的修复方法,而是需要了解unix执行模型、shell的作用以及如何做到这一点。但是,您可以通过从图中去掉外壳来获得可靠、安全和健壮的解决方案。这是由 ProcessBuilder .
上一个示例中需要有人处理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();

相关问题