在管道ProcessBuilder中运行的Grep永远不会终止

时间:2016-09-10 17:50:53

标签: java linux shell

我正在尝试在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;
}

1 个答案:

答案 0 :(得分:0)

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

  1. 将带有管道的命令包装到bash -c中(因此您将拥有带有2个参数的命令bash
  2. 通过管道拆分命令,逐行读取stdout并将其发送到第二个stdin。
  3. 如果我理解正确你不能使用后者,但也许首先允许?