4

I am using Groovy to execute commands on my Linux box and get back the output, but I am not able to use | pipes somehow (I think) or maybe it is not waiting for the command to finish.

What is wrong or what am I missing in my code?

My calling function:

def test()
{
    String result="N"

    HashMap<String,String> params = IntermediateResults.get("userparams")
    Map env=AppContext.get(AppCtxProperties.environmentVariables)

    def fClass = new GroovyClassLoader().parseClass( new File( 'plugins/infa9/Infa9CommandExecUtil.groovy' ) )
    List<String> frows=["uname -a",
                        "uname -a | awk '{print\$2}'",
                        "uname -a | cut -d ' ' -f 2"]
    List<String> resultRows = fClass.newInstance().fetchCommandOutput( params, env, frows )

    return result
}

Infa9CommandExecUtil.groovy file content (update: added exitVal println):

package infa9

import java.io.BufferedReader;

public class Infa9CommandExecUtil {
  StringBuffer result

  public Infa9CommandExecUtil() {
    result = new StringBuffer()
  }

  public List<String> fetchCommandOutput( Map<String,String> params, Map env, List<String> rows )
  {

        List<String> outputRows = new ArrayList<String>()
        try
        {
            for(item in rows)
            {
                String temp=item.toString()
                println "CMD:$temp"
                Process proc = Runtime.getRuntime().exec(temp);
                InputStream stdin = proc.getInputStream();
                InputStreamReader isr = new InputStreamReader(stdin);
                BufferedReader br = new BufferedReader(isr);
                String line = null;

                result = new StringBuffer()
                line=null

                int exitVal = proc.waitFor()    //do I need to wait for the thread/process to finish here?

                while ((line = br.readLine()) != null)
                {
                    result.append(line+System.getProperty("line.separator"))    //to maintain the format (newlines)
                }
                String tRes=result
                tRes=tRes.trim()
                println "OUTPUT:$tRes\nEXITVAL:$exitVal"

                outputRows.add(tRes)
            }
        }
        catch (IOException io)  {   io.printStackTrace();}
        catch (InterruptedException ie) {ie.printStackTrace();}
    return  outputRows
  }
}

My output (update: added exitVal value):

CMD:uname -a
OUTPUT:Linux estilo 2.6.18-128.el5 #1 SMP Wed Dec 17 11:41:38 EST 2008 x86_64 x86_64 x86_64 GNU/Linux
EXITVAL:0
CMD:uname -a | awk '{print$2}'
OUTPUT:
EXITVAL:1
CMD:uname -a | cut -d ' ' -f 2
OUTPUT:
EXITVAL:1

Note: I am internally using sh -c <command>.

6
  • So, you're still not doing it in a Groovy way (as I have shown you a good few times)? Commented Nov 8, 2011 at 11:47
  • @tim: I am behind schedule, I thought I'll first meet the requirements and then refactor the code. Sorry :) Commented Nov 8, 2011 at 11:48
  • I would read the error stream as well or use ProcessBuilder to direct error to output. I would copy the output to a ByteArrayOutputStream and converting this to a String. Commented Nov 8, 2011 at 11:48
  • @Peter Lawrey: Can you please give an example, it would be a great help. Thanks Commented Nov 8, 2011 at 11:49
  • I thought you weren't supposed to call non-Java programs from Java. Otherwise, why bother writing in Java in the first place? Commented Nov 8, 2011 at 11:54

3 Answers 3

12

You cannot do pipes or redirects using String.execute(). This doesn't work in Java, so it doesn't work in Groovy either...

You can use Process.pipeTo with Groovy to simplify things:

Process proca = 'uname -a'.execute()
Process procb = 'awk {print\$2}'.execute()

(proca | procb).text

A more generic version could be:

String process = 'uname -a | awk {print\$2}'

// Split the string into sections based on |
// And pipe the results together
Process result = process.tokenize( '|' ).inject( null ) { p, c ->
  if( p )
    p | c.execute()
  else
    c.execute()
}
// Print out the output and error streams
result.waitForProcessOutput( System.out, System.out )
Sign up to request clarification or add additional context in comments.

5 Comments

Thanks, also what is .inject(null) and p,c-> doing here?
@Ab The inject method starts with an initial accumulator value (in this case null), and then walks down the Collection applying the closure to each element in turn (the result of the Closure getting put into the accumulator for the next element). So in this case, we start with null, then the first iteration sets it to c.execute() (where c is our first element in the list), then the second loop, p is the resultant Process from this, and so it sets the accumulator to p | c.execute(). Hope this make sense?
How are you capturing the output? Like I showed you in one of your other questions?
@AbhishekSimon I don't understand the question... result.waitForProcessOutput( System.out, System.out ) redirects the output and error streams from the process chain to System.out. So everything gets shown on the same stream...
Thanks it worked like a charm. i'll post the full function in my question as an update for others too :)
4

The pipe | is a feature of a shell like bash. To use a pipe you need to run a shell, like

"/bin/bash", "-c", "uname -a | awk '{print $2}'"

To use ProcessBuilder with redirection you can do

public static void main(String... args) throws IOException, InterruptedException {
    final String cmd = "uname -a | awk '{print $1 \" \" $3}'";
    System.out.println(cmd + " => " + run(cmd));
}

private static String run(String cmd) throws IOException, InterruptedException {
    final ProcessBuilder pb = new ProcessBuilder("/bin/bash", "-c", cmd);
    pb.redirectErrorStream();
    final Process process = pb.start();
    final InputStream in = process.getInputStream();
    final byte[] bytes = new byte[1024];
    final ByteArrayOutputStream baos = new ByteArrayOutputStream();

    // you must read the output as the program runs or it can stall.
    for (int len; (len = in.read(bytes)) > 0;)
        baos.write(bytes, 0, len);
    process.waitFor();
    return baos.toString(); // assuming the default char encoding is ok.
}

prints

uname -a | awk '{print $1 " " $3}' => Linux 2.6.18-274.3.1.el5

Comments

1

Pipe is a feature of shell. So if you want pipes to be supported you have to run your command in shell context, i.e. your command line that you pass to exec in java should look like /bin/sh YOUR_COMMAND.

Comments

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.