Java 如何使管道与Runtime.exec()一起工作?

Java 如何使管道与Runtime.exec()一起工作?,java,exec,runtime.exec,Java,Exec,Runtime.exec,考虑以下代码: String commandf = "ls /etc | grep release"; try { // Execute the command and wait for it to complete Process child = Runtime.getRuntime().exec(commandf); child.waitFor(); // Print the first 16 bytes of its output InputS

考虑以下代码:

String commandf = "ls /etc | grep release";

try {

    // Execute the command and wait for it to complete
    Process child = Runtime.getRuntime().exec(commandf);
    child.waitFor();

    // Print the first 16 bytes of its output
    InputStream i = child.getInputStream();
    byte[] b = new byte[16];
    i.read(b, 0, b.length); 
    System.out.println(new String(b));

} catch (IOException e) {
    e.printStackTrace();
    System.exit(-1);
}
该程序的输出为:

/etc:
adduser.co
当然,当我从shell运行时,它会按预期工作:

poundifdef@parker:~/rabbit_test$ ls /etc | grep release
lsb-release
互联网告诉我,由于管道行为不是跨平台的,在生产Java的Java工厂工作的聪明人不能保证管道工作

我该怎么做

我不打算使用Java构造而不是
grep
sed
来完成所有解析,因为如果我想更改语言,我将被迫用该语言重新编写解析代码,这是完全不可能的


调用shell命令时,如何使Java执行管道和重定向?

创建一个运行时来运行每个进程。从第一个运行时获取OutputStream,并从第二个运行时将其复制到InputStream。

编写脚本,并执行脚本而不是单独的命令

管道是外壳的一部分,因此您也可以执行以下操作:

String[] cmd = {
"/bin/sh",
"-c",
"ls /etc | grep release"
};

Process p = Runtime.getRuntime().exec(cmd);

我在Linux中遇到了类似的问题,除了“ps-ef | grep someprocess”。
至少有了“ls”,您就有了一个独立于语言(尽管速度较慢)的Java替代品。例如:

File f = new File("C:\\");
String[] files = f.listFiles(new File("/home/tihamer"));
for (String file : files) {
    if (file.matches(.*some.*)) { System.out.println(file); }
}
使用“ps”会有点困难,因为Java似乎没有API

我听说西格尔可能会帮助我们:

然而,最简单的解决方案(正如Kaj所指出的)是以字符串数组的形式执行管道命令。以下是完整的代码:

try {
    String line;
    String[] cmd = { "/bin/sh", "-c", "ps -ef | grep export" };
    Process p = Runtime.getRuntime().exec(cmd);
    BufferedReader in =
            new BufferedReader(new InputStreamReader(p.getInputStream()));
    while ((line = in.readLine()) != null) {
        System.out.println(line); 
    }
    in.close();
} catch (Exception ex) {
    ex.printStackTrace();
}
至于为什么字符串数组与管道一起工作,而单个字符串不能。。。这是宇宙的奥秘之一(特别是如果你还没有读过源代码的话)。我怀疑这是因为当给exec一个字符串时,它首先解析它(以一种我们不喜欢的方式)。相反,当给exec一个字符串数组时,它只是将其传递给操作系统,而不进行解析

实际上,如果我们从繁忙的一天中抽出时间来看看源代码 (at),我们发现这正是正在发生的事情:

public Process  [More ...] exec(String command, String[] envp, File dir) 
          throws IOException {
    if (command.length() == 0)
        throw new IllegalArgumentException("Empty command");
    StringTokenizer st = new StringTokenizer(command);
    String[] cmdarray = new String[st.countTokens()];
    for (int i = 0; st.hasMoreTokens(); i++)
        cmdarray[i] = st.nextToken();
    return exec(cmdarray, envp, dir);
}

@Kaj接受的答案是linux。这是Windows的等效版本:

String[] cmd = {
"cmd",
"/C",
"dir /B | findstr /R /C:"release""
};
Process p = Runtime.getRuntime().exec(cmd);

我是这样看的:如果使用本机Java字符串处理,就可以保证Java应用程序可移植到所有Java支持的平台。OTOH,如果您使用shell命令,那么从Java更改语言就更容易了,但是只有在POSIX平台上才能工作。很少有人会改变应用程序的语言,而不是应用程序运行的平台。这就是为什么我觉得你的推理有点奇怪。在像
command | grep foo
这样简单的特定情况下,你最好只运行
command
,然后用Java本机进行过滤。这会使您的代码变得更复杂,但也会显著降低总体资源消耗和攻击面。@Kaj如果您想向
ls
添加选项,即
ls-lrt
,该怎么办?@Kaj我看到您正试图使用-c为shell指定一系列命令,但是我不明白为什么你必须把它变成一个字符串数组而不是一个字符串?如果有人在这里寻找
android
版本,那么使用
/system/bin/sh
来代替标题,我看到重定向也有同样的行为,即'>'字符。这个对字符串数组的额外分析启发了您,您不需要从繁忙的日程中抽出一天的时间——它是在您眼前编写的docs/api/java/lang/Runtime.html#exec(java.lang.String)值得指出的是,这远远低于操作系统本机管道的效率,因为您将内存复制到Java进程的地址空间,然后返回到后续进程。