【问题标题】:Grep running within a piped ProcessBuilder never terminates在管道 ProcessBuilder 中运行的 Grep 永远不会终止
【发布时间】:2016-09-10 17:50:53
【问题描述】:

我正在尝试在 java 中实现一个 shell,作为要求的一部分,我只能使用 ProcessBuilder 类来重定向 IO。不允许线程或轮询进程 IO 流。我的一切工作正常,但我似乎无法让管道 (|) 正常工作。我目前实现它们的方式是为每个子命令创建一个不同的 ProcessBuilder,然后将每个子命令的输入设置为前一个的输出,管道的头部和尾部链接到 shell 继承的 IO 流.这可以正常工作,但某些进程(尤其是 grep)在以这种方式使用时不会终止并且永远不会输出任何内容。 Grep 在没有管道的情况下工作正常(grep -i),但是当在管道中运行时(ls | grep txt),尽管 ls 已经完成,它永远不会终止(这可以通过检查 shell 的进程树来验证)。

我知道通常的解决方案是轮询每个进程的 IO 流并在它们进入时复制字节,或者创建一个包装类来创建一个线程来为您执行此操作。不幸的是,我不允许使用任何一种方法来完成这项任务,所以我被 ProcessBuilder 卡住了。

谷歌搜索让我无处可去,所以任何帮助将不胜感激!以下是相关代码:

/*
  Runs a pipeline of commands.  Each String argument is a command to execute and pipe to the next.
*/
private static void runExternalMulti(String[] cmds) {
    //array of process builders in order of pipe
    ProcessBuilder[] builders = new ProcessBuilder[cmds.length];
    for (String cmd : cmds) {
        cmd = cmd.trim();
        for (int i = 0; i < builders.length; i++) {
            builders[i] = createProcessBuilder(cmd.split("\\s+"));
        }
    }

    //set ProcessBuilder pipes
    //skip last index
    for (int i = 0; i < builders.length - 1; i++) {
        ProcessBuilder first = builders[i];
        ProcessBuilder next = builders[i+1];
        first.redirectOutput(next.redirectInput());
        next.redirectInput(first.redirectOutput());
    }

    //start each process and hold in array
    Process[] processes = new Process[builders.length];
    for (int i = 0; i < builders.length; i++) {
        processes[i] = builders[i].start();
    }

    //loop until all process have finished
    boolean running = true;
    while (running) {
        running = false;
        for (Process process : processes) {
            if (process.isAlive()) {
                running = true;
            }
        }
        //pause to avoid churning CPU
        Thread.sleep(10);
    }
}

/*
  Creates a processBuilder for the command specified in parts.  Each string is a space-separated part of the command line, with the first being the executable to run.
*/
private static ProcessBuilder createProcessBuilder(String[] parts) {
    ProcessBuilder builder = new ProcessBuilder();
    builder.directory(currentDirectory);  //set working directory
    //set process to share IO streams with java shell
    builder.redirectInput(ProcessBuilder.Redirect.INHERIT);
    builder.redirectOutput(ProcessBuilder.Redirect.INHERIT);
    builder.redirectError(ProcessBuilder.Redirect.INHERIT);

    //parse each token and pull out IO redirection and background characters
    List<String> command = new ArrayList<>(parts.length);
    for (int i = 0; i < parts.length; i++) {
        //Ignore the background character, it has been read or ignored by the calling function
        if (i == parts.length - 1 && "&".equals(parts[i])) {
            continue; //don't parse an ending "&" as a command parameter
        }
        String str = parts[i];
        //redirect to a file
        if (">".equals(str)) {
            if (i < parts.length - 1) {
                File outFile = new File(currentDirectory, parts[i + 1]);
                builder.redirectOutput(outFile);
                builder.redirectError(outFile);
            }
            i++; //make sure to skip the redirected file name
        //read in from a file
        } else if ("<".equals(str)) {
            if (i < parts.length - 1) {
                File inFile = new File(currentDirectory, parts[i + 1]);
                if (inFile.isFile()) {
                    builder.redirectInput(inFile);
                }
            }
            i++; //make sure to skip the redirected file name
        } else {
            command.add(parts[i]);
        }
    }
    builder.command(command);
    return builder;
}

【问题讨论】:

  • 你可能已经猜到了,redirectInput() 和redirectOutput() 不返回可连接的管道。它们只返回描述输入/输出如何被重定向的对象。如果不允许使用 Process.getInputStream() 和 Process.getOutputStream(),除了将源进程的输出重定向到文件,然后让接收进程从该文件重定向其输入之外,我想不出任何替代方法。这意味着两个管道连接的进程根本无法同时运行。
  • 谢谢,我怀疑是这种情况,但我找不到其他可能有用的方法。这只是额外的学分,所以我会跳过它并询问我的教授。这项任务是今年的新任务,所以我想没有人真正检查过以确保它是可能的。

标签: java linux shell


【解决方案1】:

管道是命令行解释器的特性。所以你必须选择:

  1. 使用管道将命令包装到 bash -c 中(这样您将拥有带有 2 个参数的命令 bash
  2. 通过管道分割命令,逐行读取stdout并发送到第二个stdin。

如果我理解正确,您不能使用后者,但也许允许使用第一个?

【讨论】:

  • 我实际上已经实现了第二种方法,但我只能使用 ProcessBuilder 方法来连接进程。显然这是不可能的,但这只是额外的功劳,所以我会问我的教授这部分是否应该有例外。
猜你喜欢
  • 2014-05-29
  • 1970-01-01
  • 2014-05-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-08
  • 1970-01-01
相关资源
最近更新 更多