39

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?

1

3 Answers 3

74

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.

Sign up to request clarification or add additional context in comments.

2 Comments

For some reason I also needed to do this for ls /dev/sd* IOW, ProcessBuilder pb = new ProcessBuilder("/bin/sh", "-c", "ls /dev/sd*"); works great, whereas ProcessBuilder pb = new ProcessBuilder("ls", "/dev/sd*"); did not work. Guessing it has something to do with vararg interpretation of the * in the string... Seems like a bug to me. Could also be because of the "special" /dev device, not sure.
It's the shell that expands the wildcard, not ls. If you typed ls '/dev/sd*' then that wouldn't work either.
9

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).

4 Comments

Great answer. However, I don't think you need to call redirectOutput(PIPE) as that is the default. Similarly, inheritIO() is also not necessary.
The purpose of inheritIO() at the first command, is to set stdin and stderr to INHERIT. Since it has the side effect of also setting stdout to INHERIT, the subsequent redirectOutput is used to set it back to PIPE. This is still simpler than .redirectInput(ProcessBuilder.Redirect.INHERIT) .redirectError(ProcessBuilder.Redirect.INHERIT)
Adding on to what @RajV said, inheritIO() does not work if you have multiple piped commands.
@AdrianBartyczak of course, you should call .inheritIO().redirectOutput(ProcessBuilder.Redirect.PIPE) only for the first command of the chain. And only if you really want to inherit stdin.
5

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.

4 Comments

Seems to be a good option, better than running several shells to get the proper output. Thanks!
@Jon Skeet could you give an example for your suggestion [read from the standard output of "ls -l", writing the data to the standard input of "grep"] ?
@DaveTheDane: No, I'm afraid I don't have time to write a full example for an answer that's over 13 years old. I suggest you try doing it yourself and ask a new question with how far you've got and where you've run into problems.
@Jon Skeet ok, I thought it would be a one-liner. Thanks anyway.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.