【问题标题】:java.lang.Runtime exception "Cannot run program"java.lang.Runtime 异常“无法运行程序”
【发布时间】:2017-11-14 00:23:13
【问题描述】:

我在执行下面的命令时遇到了类似java.io.IOException: Cannot run program cat /home/talha/* | grep -c TEXT_TO_SEARCH": error=2, No such file or directory 的异常,尽管当我通过终端执行相同的命令时没有问题。我需要执行并返回下面的命令:

cat /home/talha/* | grep -c TEXT_TO_SEARCH

下面是使用Runtime类执行命令的方法:

public static 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();
}

【问题讨论】:

  • 指定cat & grep 命令的完整路径。
  • 但是该命令可以通过终端 (bash) 正常工作
  • 好吧,那是因为 $PATH 已定义,所以它知道要沿着 /usr/bin:/sbin:/usr/sbin:/usr/local/bin 寻找 cat。在 Java 运行时的上下文中,它没有那个环境。
  • Runtime.exec 不使用 shell(例如,/bin/bash);它将命令直接传递给操作系统。这意味着像 * 和管道 (|) 这样的通配符将不会被理解,因为 cat(像所有 Unix 命令一样)不会对这些字符进行任何解析。你需要使用p = new ProcessBuilder("bash", "-c", command).start(); 之类的东西,或者,如果出于某种奇怪的原因你需要坚持使用过时的 Runtime.exec 方法,p = Runtime.getRuntime().exec(new String[] { "bash", "-c", command });
  • 您能发表您的评论作为答案吗?然后,我将能够接受您的回答作为问题的解决方案。 @VGR

标签: java linux unix cat runtime.exec


【解决方案1】:

Runtime.exec 不使用 shell(例如,/bin/bash);它将命令直接传递给操作系统。这意味着像 * 和管道 (|) 这样的通配符将不会被理解,因为 cat(像所有 Unix 命令一样)不会对这些字符进行任何解析。你需要使用类似的东西

p = new ProcessBuilder("bash", "-c", command).start();

或者,如果出于某种奇怪的原因,您需要坚持使用过时的 Runtime.exec 方法:

p = Runtime.getRuntime().exec(new String[] { "bash", "-c", command });

如果您只运行 cat/grep 命令,则应考虑放弃使用外部进程,因为 Java 代码可以轻松遍历目录,从每个文件中读取行,并将它们与正则表达式匹配:

Pattern pattern = Pattern.compile("TEXT_TO_SEARCH");
Charset charset = Charset.defaultCharset();

long count = 0;

try (DirectoryStream<Path> dir =
    Files.newDirectoryStream(Paths.get("/home/talha"))) {

    for (Path file : dir) {
        count += Files.lines(file, charset).filter(pattern.asPredicate()).count();
    }
}

更新:要递归读取树中的所有文件,请使用Files.walk

try (Stream<Path> tree =
    Files.walk(Paths.get("/home/talha")).filter(Files::isReadable)) {

    Iterator<Path> i = tree.iterator();
    while (i.hasNext()) {
        Path file = i.next();
        try (Stream<String> lines = Files.lines(file, charset)) {
            count += lines.filter(pattern.asPredicate()).count();
        }
    };
}

【讨论】:

  • 如何递归查找根目录下的文件?
  • 更新了答案。使用 Files.walk 方法而不是 DirectoryStream。
  • 得到这个异常:java.nio.file.FileSystemException: Too many open files
  • Runtime.exec(还有ProcessBuilder)不会运行shell,除非您明确说明,所以默认情况下glob、管道和其他重定向(&gt; &gt;&gt; &lt; &lt;&lt; &lt;&gt;等)不起作用,也像&amp; &amp;&amp; || if while 等进行变量或进程替换或流控制。但exec(String)exec(String[]) 相比执行 空格标记命令,因此发布的代码不应给出发布的特定异常。 (PS:几个 Unix 实用程序自己做通配符,例如findrsync,甚至还有自己的管道,例如awk。)
  • @talha06 再次更新了答案。自动关闭 Files.lines 返回的 Stream 应该会立即关闭每个文件,并且应该防止“打开的文件太多”错误。
【解决方案2】:

$PATH 是一个环境变量,它告诉系统在哪里搜索可执行程序(它是一个由冒号分隔的目录列表)。它通常在你的.bashrc.cshrc文件中设置,但只有在你登录时才会加载。Java运行时,$PATH可能没有设置,因为rc文件不会自动执行,所以系统可以'如果没有明确指定它们的位置,就找不到程序。尝试使用/bin/cat/usr/bin/cat 而不是仅使用cat,看看它是否有效。如果是这样,$PATH 就是你的问题。您可以将$PATH=/bin:/usr/bin 添加到您的脚本中,或者只保留指定的目录名称(例如/bin/cat)。

仅仅因为您可以在登录会话中执行它并不意味着当像您的 Java 程序这样的守护程序运行时它会以相同的方式工作。你必须知道你的.bashrc.cshrc 文件中有什么,甚至有时系统文件是如何编写的(/etc/bashrc),才能知道如何编写在守护进程下运行的脚本。另一个考虑因素是守护程序通常在不同用户的上下文中运行,这也会使事情变得混乱。

【讨论】:

    猜你喜欢
    • 2014-01-13
    • 2014-02-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-16
    • 1970-01-01
    • 2017-02-08
    • 1970-01-01
    相关资源
    最近更新 更多