如何使管道与Runtime.exec()一起使用?


104

考虑以下代码:

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

当然,当我从外壳运行时,它可以按预期工作:

poundifdef@parker:~/rabbit_test$ ls /etc | grep release
lsb-release

互联网告诉我,由于管道行为不是跨平台的,因此在生产Java的Java工厂工作的才华横溢的人无法保证管道能够正常工作。

我怎样才能做到这一点?

我不会使用Java构造而不是grepand 来进行所有解析sed,因为如果要更改语言,我将被迫用该语言重新编写解析代码,这完全是徒劳的。

调用Shell命令时,如何使Java进行管道传输和重定向?


我会这样看:如果您使用本机Java字符串处理来实现,则可以确保Java应用程序可移植到Java支持的所有平台。OTOH,如果使用shell命令进行操作,则可以更轻松地 Java 更改语言,但仅在POSIX平台上有效。很少有人会更改应用程序的语言,而不是更改应用程序运行的平台。这就是为什么我认为您的推理有点好奇。
Janus Troelsen

在某些简单的特定情况下,command | grep foo您只需command在Java中本地运行并进行过滤就可以了。它使您的代码稍微复杂一些,但同时也减少了总体资源消耗和攻击面。
Tripleee '18

Answers:


182

编写脚本,然后执行脚本而不是单独的命令。

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

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

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

@Kaj如果要向lsie 添加选项ls -lrt怎么办?
kaustav datta

8
@Kaj我看到您正在尝试使用-c向外壳指定命令字符串,但是我不明白为什么您必须将其设置为字符串数组而不是单个字符串?
David Doria 2013年

2
如果有人android在这里寻找版本,请/system/bin/sh改用
Nexen

25

我在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。

我听说Sigar可以帮助我们:https : //support.hyperic.com/display/SIGAR/Home

但是,最简单的解决方案(由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();
}

至于为什么String数组可以与管道一起使用,而单个字符串却不行的原因……这是宇宙的奥秘之一(特别是如果您还没有阅读源代码的话)。我怀疑这是因为给exec一个字符串时,它首先解析了它(以我们不喜欢的方式)。相反,当给exec一个字符串数组时,它只是将其传递给操作系统而不进行解析。

实际上,如果我们抽出时间忙于时间,然后查看源代码(位于http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/lang/ Runtime.java#Runtime.exec%28java.lang.String%2Cjava.lang.String []%2Cjava.io.File%29),我们发现确实发生了什么:

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);
}

我在重定向中看到了相同的行为,即“>”字符。对String数组的这种额外分析很有启发性
kris

您无需花时间在繁忙的日程中-它写在您的眼前docs / api / java / lang / Runtime.html#exec(java.lang.String)
gpasch

6

创建一个运行时以运行每个过程。从第一个运行时获取OutputStream,然后从第二个运行时将其复制到InputStream中。


1
值得指出的是,这比使用本地管道效率要低得多,因为您是将内存复制到Java进程的地址空间中,然后再返回到后续进程。
Tim Boudreau

0

@Kaj接受的答案适用于Linux。这对于Windows是等效的:

String[] cmd = {
"cmd",
"/C",
"dir /B | findstr /R /C:"release""
};
Process p = Runtime.getRuntime().exec(cmd);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.