简体   繁体   中英

ProcessBuilder does something to my command line arguments on Windows

What can I do on Windows to stop it from re-tokenizing my command line arguments?

It works just fine on Linux and OS X.

Here's the code:

import java.lang.*;
import java.io.*;
import java.util.*;
public class test {
    public static void main (String[] args) throws IOException, InterruptedException {
        if (args.length == 0) {
            ArrayList<String> childargs = new ArrayList<String>();
            childargs.add("java");
            childargs.add("test");
            childargs.add("--p='sprintf(\"%8d %13s\")'");
            for (String s : childargs)
                System.out.println(String.format("Parent: arg=<%s>", s));
            ProcessBuilder pb = new ProcessBuilder(childargs);
            pb.inheritIO();
            Process p = pb.start();
            int rc = p.waitFor();
            System.out.println("Parent: child rc=" + rc);
        } else {
            for (int i=0; i<args.length; i++)
                System.out.println(String.format("Child:  arg=<%s>", args[i]));
            System.exit(3);
        }
    }
}

On Linux and OS X this is what it prints.

$ java test
Parent: arg=<java>
Parent: arg=<test>
Parent: arg=<--p='sprintf("%8d %13s")'>
Child:  arg=<--p='sprintf("%8d %13s")'>
Parent: child rc=3

On Windows 7 Professional it prints this

> java test
Parent: arg=<java>
Parent: arg=<test>
Parent: arg=<--p='sprintf("%8d %13s")'>
Child:  arg=<--p='sprintf(%8d>
Child:  arg=<%13s)'>
Parent: child rc=3

What I want is for the child's command line arguments to be the same as those given to it by the parent. I do not want the arguments to be broken up into multiple pieces. And I prefer to not have to write platform-dependent code.

Backslashing works. From the description of CommandLineToArgvW :

CommandLineToArgvW has a special interpretation of backslash characters when they are followed by a quotation mark character ("), as follows:

  • 2n backslashes followed by a quotation mark produce n backslashes followed by a quotation mark.
  • (2n) + 1 backslashes followed by a quotation mark again produce n backslashes followed by a quotation mark.
  • n backslashes not followed by a quotation mark simply produce n backslashes.

Net is that using backslashes preserves the double-quotes in your arguments. So, a simpler solution is to conditionally backslash your double quotes if on Windows:

import java.lang.*;
import java.io.*;
import java.util.*;
public class test {
    public static void main (String[] args) throws IOException, InterruptedException {
        if (args.length == 0) {
            ArrayList<String> childargs = new ArrayList<String>();
            childargs.add("java");
            childargs.add("test");
            String quotedArg = "--p='sprintf(\"%8d %13s\")'";
            //
            // HERE'S THE KEY CODE FRAGMENT
            //
            if (isWin()) {
                quotedArg = quotedArg.replace("\"", "\\\"");
            }
            childargs.add(quotedArg);
            for (String s : childargs)
                System.out.println(String.format("Parent: arg=<%s>", s));
            ProcessBuilder pb = new ProcessBuilder(childargs);
            pb.inheritIO();
            Process p = pb.start();
            int rc = p.waitFor();
            System.out.println("Parent: child rc=" + rc);
        } else {
            for (int i=0; i<args.length; i++)
                System.out.println(String.format("Child:  arg=<%s>", args[i]));
            System.exit(3);
        }
    }

    static boolean isWin() {
        return System.getProperty("os.name").toLowerCase().indexOf("win") >= 0;
    }
}

This produces the following output on *nix:

Parent: arg=<java>
Parent: arg=<test>
Parent: arg=<--p='sprintf("%8d %13s")'>
Child:  arg=<--p='sprintf("%8d %13s")'>
Parent: child rc=3

And on Windows:

Parent: arg=<java>
Parent: arg=<test>
Parent: arg=<--p='sprintf(\"%8d %13s\")'>
Child:  arg=<--p='sprintf("%8d %13s")'>
Parent: child rc=3

I have not found an alternative to using ProcessBuilder. I believe it calls the kernel32 library function CreateProcess, which takes only an entire command line.

If I must write platform-dependent code, the thing that might be the least amount of code might be to filter the command line tokens so that when they are concatenated and retokenized, the program will receive what was originally intended:

  1. if the token begins with ", then return the unadulterated token
  2. if the token has no whitespace, then return the unadulterated token
  3. change all " characters to \\" and then return the whole thing enclosed in "

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM