【发布时间】: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(),除了将源进程的输出重定向到文件,然后让接收进程从该文件重定向其输入之外,我想不出任何替代方法。这意味着两个管道连接的进程根本无法同时运行。
-
谢谢,我怀疑是这种情况,但我找不到其他可能有用的方法。这只是额外的学分,所以我会跳过它并询问我的教授。这项任务是今年的新任务,所以我想没有人真正检查过以确保它是可能的。