Search code examples
javaprocessbuilder

How do I run Java ProcessBuilder with a "Pipe" to a second exe-file?


If I run this command:

raster2pgsql.exe -I -C -s 4326 -t 256x256 -l 4,16 C:\raster\sthlm.tif public.stockholm | psql -U postgresuser -d rasterdata -h localhost -p 5432 in the windows command line interface, the command executes ok. The raster file is imported to PostgreSQL database "rasterdata". But I can't get it to work with Java ProcessBuilder. In the command above there are two exe files (raster2pgsql.exe and psql.exe) and I don't know how to define the command for the ProcessBuilder with 2 exe-files.

The syntax for the two programs is:

RASTER2PGSQL:

-s use srid 4326

-I create spatial index

-C use standard raster constraints

-t tile the output 256x256

sthlm.tif input file

-t tile the output 256x256*

public.stockholm load into this table

raster2pgsql -s 4326 -I -C -t 256x256 sthlm.tif public.stockholm > elev.sql

PSQL:

-d connect to this database

-u database user

-h host

-p port

-f read this file after connecting (SQL-output from raster2pgsql)

psql -u postgres -d rasterdata -h localhost -p 5432 -f elev.sql

I don't want to run the programs separately (first storing the converted rasterdata on disk (elev.sql) and then insert the data in elev.sql into the database. Hence I would like to pipe psql to raster2pgsql like Postgis as PostGIS examplifies (https://postgis.net/docs/using_raster_dataman.html#RT_Raster_Loader): "A conversion and upload can be done all in one step using UNIX pipes: raster2pgsql -s 4326 -I -C -M .tif -F -t 100x100 public.demelevation | psql -d gisdb"

This is an example of the outprints when I run my code with raster2pgsql | psql:

INSERT INTO "public"."stockholm | psql -u postgresuser -d rasterdata -h localhost 5432" ("rast") VALUES ('01000... etc..

it seems that only raster2pgsql is executed my code:

ArrayList<String> cmd = new ArrayList<String>();
cmd.add("raster2pgsql.exe");
cmd.add("-I");
cmd.add("-C");
cmd.add("-s");
cmd.add("4326");
cmd.add("-t");
cmd.add("256x256");
cmd.add("-l");
cmd.add("4,16");
cmd.add(c:\raster\sthlm.tif");
cmd.add("public.stockholm | psql -U postgresuser -d rasterdata -h localhost -p 5432);
ProcessBuilder b = MainDialog.gdalProcess; //pre created
b.command(cmd);
Process p=null;
try {
  p = b.start();
} catch (IOException e) {
  e.printStackTrace();
}
try (InputStream processOutput = p.getInputStream()) {
   BufferedReader bufIn = new BufferedReader(new InputStreamReader(processOutput));
   String str1 = null;
   while ((str1 = bufIn.readLine()) != null) {
       System.out.println(str1 + "\n");
   }
   bufIn.close();
} catch (IOException e) {
   e.printStackTrace();
}

Solution

  • You want ProcessBuilder.startPipeline:

    ProcessBuilder raster2pgsql = new ProcessBuilder("raster2pgsql.exe",
        "-I", "-C", "-s", "4326", "-t", "256x256", "-l", "4,16",
        "C:\\raster\\sthlm.tif", "public.stockholm");
    ProcessBuilder psql = new ProcessBuilder("psql.exe",
            "-h", "localhost", "-p", "5432", "-d", "rasterdata",
            "-U", "postgresuser");
    
    raster2pgsql.redirectError(ProcessBuilder.Redirect.INHERIT);
    psql.redirectError(ProcessBuilder.Redirect.INHERIT);
    psql.redirectOutput(ProcessBuilder.Redirect.INHERIT);
    
    List<Process> pipeline = ProcessBuilder.startPipeline(
        List.of(raster2pgsql, psql));
    
    for (Process process : pipeline) {
        int exitCode = process.waitFor();
        if (exitcode != 0) {
            Optional<String> command = process.info().commandLine();
            if (!command.isPresent()) {
                command = process.info().command();
            }
    
            throw new IOException("Process exited with code " + exitCode
                + command.map(c -> ": " + c).orElse(""));
        }
    }
    

    You cannot pass | directly to a process, since programs never process that character; it is normally the shell (like bash or cmd.exe) which does that, making the piping of output and input transparent to the programs. As others have mentioned, a workaround is to pass the entire command to an explicit call to bash or cmd.exe, but then you will have the task of escaping command arguments yourself. It’s better to let ProcessBuilder.startPipeline take care of everything by passing the sequence of commands directly to the system.