user455889
user455889

Reputation: 803

Using Java ProcessBuilder to Execute a Piped Command

I'm trying to use Java's ProcessBuilder class to execute a command that has a pipe in it. For example:

ls -l | grep foo

However, I get an error:

ls: |: no such file or directory

Followed by:

ls: grep: no such file or directory

Even though that command works perfectly from the command line, I can not get ProcessBuilder to execute a command that redirects its output to another.

Is there any way to accomplish this?

Upvotes: 39

Views: 36023

Answers (3)

Holger
Holger

Reputation: 298113

Since Java 9, there’s genuine support for piplines in ProcessBuilder.

So you can use

List<String> result;
List<Process> processes = ProcessBuilder.startPipeline(List.of(
        new ProcessBuilder("ls", "-l")
            .inheritIO().redirectOutput(ProcessBuilder.Redirect.PIPE),
        new ProcessBuilder("grep", "foo")
            .redirectError(ProcessBuilder.Redirect.INHERIT)
    ));
try(Scanner s = new Scanner(processes.get(processes.size() - 1).getInputStream())) {
    result = s.useDelimiter("\\R").tokens().toList();
}

to get the matching lines in a list.

Or, for Windows

List<String> result;
List<Process> processes = ProcessBuilder.startPipeline(List.of(
        new ProcessBuilder("cmd", "/c", "dir")
            .inheritIO().redirectOutput(ProcessBuilder.Redirect.PIPE),
        new ProcessBuilder("find", "\"foo\"")
            .redirectError(ProcessBuilder.Redirect.INHERIT)
    ));
try(Scanner s = new Scanner(processes.get(processes.size() - 1).getInputStream())) {
    result = s.useDelimiter("\\R").tokens().toList();
}

These examples redirect stdin of the first process and all error streams to inherit, to use the same as the Java process.

You can also call .redirectOutput(ProcessBuilder.Redirect.INHERIT) on the ProcessBuilder of the last process, to print the results directly to the console (or wherever stdout has been redirected to).

Upvotes: 8

dogbane
dogbane

Reputation: 274532

This should work:

ProcessBuilder b = new ProcessBuilder("/bin/sh", "-c", "ls -l| grep foo");

To execute a pipeline, you have to invoke a shell, and then run your commands inside that shell.

Upvotes: 73

Jon Skeet
Jon Skeet

Reputation: 1499860

The simplest way is to invoke the shell with the command line as the parameter. After all, it's the shell which is interpreting "|" to mean "pipe the data between two processes".

Alternatively, you could launch each process separately, and read from the standard output of "ls -l", writing the data to the standard input of "grep" in your example.

Upvotes: 5

Related Questions