Skip to content
Advertisement

ProcessBuilder’s inputstream empty depending of OS

I made this simple piece of code to test ProcessBuilder:

@SpringBootApplication
public class TerminalDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(TerminalDemoApplication.class, args);
        try {
            System.out.println("hello");
            Process process = new ProcessBuilder("python", "--version").start();
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));

            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }

            int exitCode = process.waitFor();
            System.out.println("nExited with error code : " + exitCode);
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }

}

It works in Windows (returns python version of my system) but the same code in my macbook returns end of line, so basically empty. ¿This needs further configuration according to the OS? ¿why is this happening?

Advertisement

Answer

What error code are you getting?

There are (at least) two explanations; that error code would indicate which one it is.

You’re not running python, or running ‘the wrong’ python

This would mean you are getting an error code of some sort, or an exception.

The likely reason for this is a path issue.

Running python, just like that – as in, no path information at all, is nominally neccessarily broken: That’s just not how your OS works, it has no idea what to do with this path.

It’s a bashism (as in, the shell does it, not the OS) to interpret such a command as ‘oh, actually, go through each listed entry in the $PATH environment variable, and stick that path in front of this name, see if you find an executable there. If you do, run that and stop).

Java mostly doesn’t engage in any bashisms. But, in a few bizarre places, it does – it tries to do basic space splitting when you use the single-string version of new ProcessBuilder), which is a shellism, and it does attempt to do basic PATH lookup, but that’s about where it ends. It won’t do * unpacking, which on windows is an OS-level thing but on posix systems is a shellism.

I strongly, strongly advise you to avoid java’s basic shellisms. It’s unreliable and highly OS-specific.

So: Always pass arguments explicitly (good, you’re doing that), always use ProcessBuilder (good, you’re doing that), never use relative paths (that’s where you’re going wrong).

It’s going to the error stream instead

processes on OSes are generally hooked up to 3 pipes, not 2. There’s the ‘standard in’, the ‘standard out’ and the ‘standard err’. Your own java process exposes these as System.out, in, and err.

In linux in particular, it is common to redirect standard out of some process to a file or another process.

This means that standard err naturally has the property that it tends to emit to the console, even if you are redirecting things. In other words, the terms ‘standard out’ and ‘standard err’ are really stupid names on posix. The much better naming would be ‘standard process output’ and ‘standard process messages’.

Asking python to print its version is in a bit of a limbo scenario. The string “Python v3.0.1” or whatnot is certainly not an error, but it’s a bit dubious if one should consider this as ‘the output of the process’. It’s likely that the authors of the python tool consider it more ‘some information I should print to you, even if you are redirecting things.

Thus, my guess is that this version is heading out to standard err instead.

You can solve this in two ways: Either read from standard err as well, or, use process builder’s features: You can ask it to bundle up standard out and standard err into a single stream (.redirectErrorStream(true)).

I would expect the exit code to be 0 if this explanation is the correct one.

Advertisement