« How does each-both (aka multivalent each) work? | Main | Why can't I index some elements of a dictionary? »

How can I capture STDOUT, STDERR and the exit status of a system command invoked from q?

Short answer: 2>&1 (i.e., redirect STDERR to STDOUT), echo $?, and add parentheses.

Note: This applies to bash, and has not been tested in csh, ksh, etc.

We can invoke arbitrary external programs from q using the system command. For example:

q)system "echo Hello, world!"
"Hello, world!"

External programs often signal errors by returning a non-zero exit code, which is stored in the shell variable ?:

$ ls nonexistent_file
ls: nonexistent_file: No such file or directory
$ echo $?
$ foo
-bash: foo: command not found
$ echo $?

When an external program called from system in q runs into a situation like this, q detects the non-zero exit code from the child process and raises a signal, 'os:

q)system "ls nonexistent_file"
ls: nonexistent_file: No such file or directory

When trying out ideas at the console as in the above example, the full details of the error are available on the screen. Getting those error details programmatically is a bit trickier, however. Consider the following function, in which we use protected execution to invoke an error handler when the 'os signal is raised:

use_protected_execution: {[]
    : @[system;
        "ls nonexistent_file";
        {[error_info] -1 "error is ", error_info;}];

The error handler in use_protected_execution knows only that an operating system error occurred. None of the error details are available within the code:

ls: nonexistent_file: No such file or directory  // invisible
error is os

We can improve the situation somewhat by appending "; echo $?" to the command:

q)result: system "ls nonexistent_file; echo $?"
,"1"    // actually a list of strings

This enables us to distinguish error codes as follows:

distinguish_error_codes: {[]
    result: system "ls nonexistent_file; echo $?";
    exit_code: "I" $ last result;
    $[0   = exit_code;
        ; // everything's alright
      1   = exit_code;
        ; // minor problem
      2   = exit_code;
        ; // major problem
      127 = exit_code;
        ; // command not found
    / else
        // unhandled exit code

What we really want, though, is to capture the output sent by the failed command to STDERR. You might expect the usual shell redirection syntax to do the job:

q)result: system "ls nonexistent_file 2>&1; echo $?"
ls: nonexistent_file: No such file or directory

No such luck. The trick is to put parentheses around the whole thing:

q)result: system "(ls nonexistent_file 2>&1; echo $?)"
"ls: nonexistent_file: No such file or directory"

We can wrap all this up in a handy function that, instead of raising os, raises the descriptive message output to STDERR by the child process:

call_external: {[command]
    result: system "(", command, " 2>&1; echo $?)";
    exit_code: "I" $ last result;
    if [0 < exit_code;
        ' raze -1 _ result];
    -1 _ result}

PrintView Printer Friendly Version

EmailEmail Article to Friend

Reader Comments

There are no comments for this journal entry. To create a new comment, use the form below.

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
All HTML will be escaped. Textile formatting is allowed.