ScrappyDev
ScrappyDev

Reputation: 2778

Issue executing a piped ps unix command through Runtime.exec()

Issue:

When executing the following command through Runtime.exec(...), it fails with an unexpected EOF while looking for a matching quote character.

One oddity is that the error message has a grave character followed by two single quotes.

However, when I execute the command that prints out in the logs through putty, it works fine.

Command:

bin/sh -c 'ps -eo uname,pid,ppid,nlwp,pcpu,pmem,psr,start_time,tty,time,args | fgrep IAAPC | fgrep /f1/f2/a00-a/f3/server/server_1/env_1/javadriver | fgrep -v fgrep'

Resulting error:

-eo: -c: line 0: unexpected EOF while looking for matching `''
-eo: -c: line 1: syntax error: unexpected end of file

Java Code (Java 1.6 ... Don't Judge):

    String driverHome = trimToEmpty(System.getProperty("batchdriver.home"));
    String cmd = "/bin/sh -c 'ps -eo uname,pid,ppid,nlwp,pcpu,pmem,psr,start_time,tty,time,args | fgrep "+jobName+" | fgrep "+driverHome+" | fgrep -v fgrep'";
    String out = null, err = null;
    Process proc = null;
    try {
        proc = Runtime.getRuntime().exec(cmd);
        
        out = fullyRead(proc.getInputStream());
        err = fullyRead(proc.getErrorStream());
        
        int exitVal = proc.waitFor();
        
        if(logger.isDebugEnabled()) {
            logger.debug("Process Information: "+out);
        }
        if (isNotEmpty(err)) {
            logger.error(failedCommandMessage(cmd, out, err));
            this.processId = null;
            this.processDesc = PROCESS_NOT_FOUND;
            return;
        }
        
        String[] processes = StringUtils.split(out, "\r?\n");
        if (processes == null || processes.length == 0) {
            this.processDesc = PROCESS_NOT_FOUND;
        }
        else if (processes.length == 1) {
            String[] processInfo = processes[0].split("\\s+");
            this.processId = processInfo[1];
            if (!isNumeric(this.processId)) {
                this.processId = null;
            }
            this.processDesc = out;
        }
        else {
            this.processDesc = out;
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Call to the OS completed with exit value: " + exitVal);
        }
    } catch (Exception e) {
        try {out = fullyRead(proc.getInputStream());} catch (Exception e1) {}
        try {err = fullyRead(proc.getErrorStream());} catch (Exception e1) {}
        this.processId = null;
        this.processDesc = PROCESS_NOT_FOUND;
        logger.error(failedCommandMessage(cmd, out, err), e);
    }

Upvotes: 0

Views: 284

Answers (1)

dave_thompson_085
dave_thompson_085

Reputation: 38781

Related but not quite dupe: Pass a string with multiple contiguous spaces as a parameter to a jar file using Windows command prompt called from a java program

The Runtime.exec methods that take a String break it into tokens at whitespace only so this actually runs the program /bin/sh (a shell) with the following arguments:

 -c
 'ps
 -eo 
 uname,pid,ppid,nlwp,pcpu,pmem,psr,start_time,tty,time,args 
 |
 fgrep
 ...

The shell interprets these arguments like this:

 -c 'ps -- the script to run consists of the apostrophe character, p, s (and nothing more)
 -eo -- the name of the command being run is -eo 
 uname,pid,.... -- the first argument to the script is this
 | -- the second argument to the script is this
 fgrep -- the third argument to the script is this
 ... 
 -- but the script ignores the arguments and doesn't use them

Thus you get

-eo: -c: unexpected EOF while looking for matching `''
# the script named -eo, with the option -c having value 'ps,
# tried to find a closing ' to match the opening ' and it's not there

This shell is apparently (GNU) bash; many GNU programs that put a data string in an error message surround it by backquote and apostrophe because these were sort of matching quotes in one interpretation of ASCII popular decades ago.

Instead use the String[] overload of exec to give the shell the two arguments that it gets when your above command line is parsed by a shell instead of StringTokenizer:

 String[] cmdary = {"/bin/sh", "-c", "ps -eo stuff | fgrep this | fgrep that | fgrep -v fgrep"};
 ... Runtime.getRuntime().exec(cmdary);

But instead of running three fgrep's, you could just run the ps and read the inputstream as lines and test them in Java using String.contains or similar. Also most of the columns you ask ps for will never be used for either your matching nor result, so that's just a waste of effort and clutter.

Upvotes: 2

Related Questions