http://www.perlmonks.org?node_id=597613

Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

I have a piece of code provided below. The code calls a make command, and redirects STDOUT/STDERR to tee, which then sends it to the screen, and also to a log file. The problem is that I'm trying to capture the exit code for make. However, what happens when I run the command a cmd process starts up, and calls make and tee as peers, so make exits first, then tee, which returns the exit code of tee to cmd, and cmd returns the exit code to the script. What I need is the exit code from make, and not tee.

$command = "make -f <parameters>";
$command .= " 2>&1 | tee -a build.log";
system $command;
$exit_value = $? >> 8;
print "\nExit Value: $exit_value\n\n";

The exit value is always 0, because it's returning the tee value, and not make. Any and all help is very apprciated.

Replies are listed 'Best First'.
Re: Capture Exit Code Using Pipe & Tee
by merlyn (Sage) on Jan 31, 2007 at 19:50 UTC
    Do the tee yourself in Perl... that'd be a simple operation. Something like:
    open my $make, "make -f params |" or die; open my $logger, ">>build.log" or die; while (<$make>) { print $logger $_; print } close $make; # to get $? my $exit = $?; close $logger;
      Always check the return value of close. You might be e.g. sitting on a NFS share.

      --shmem

      _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                    /\_¯/(q    /
      ----------------------------  \__(m.====·.(_("always off the crowd"))."·
      ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
      I've tried your suggestion. I can't seem to get it to write anything anywhere now. Ideally I would like it to be real-time. I can't see in the example where the execution of make is taking place. Thanks for your help!!
        The make is invoked via open, which returns the process ID of the spawned command. merlyn's code should work alright, with a slight modification
        open my $make, "make -f params 2>&1 |" or die;
        to get make's STDERR into the spawning shell's STDOUT. Do you get any errors reported?

        --shmem

        _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                      /\_¯/(q    /
        ----------------------------  \__(m.====·.(_("always off the crowd"))."·
        ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
Re: Capture Exit Code Using Pipe & Tee
by shmem (Chancellor) on Jan 31, 2007 at 21:55 UTC
    You need a more elaborate construct to get the return value from make, if you want to execute it in a pipeline via /bin/sh. Try this (untested):
    chop (my $status = `((make -f $params 2>&1 3>&-; echo \$? >&3 ) | tee +-a build.log 1>&2 3>&- ) 3>&1`); print "make exit status: $status\n";

    update: added backslash to escape $?

    --shmem

    _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                  /\_¯/(q    /
    ----------------------------  \__(m.====·.(_("always off the crowd"))."·
    ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
Re: Capture Exit Code Using Pipe & Tee
by ambrus (Abbot) on Jan 31, 2007 at 22:14 UTC

    If your shell is bash, check out the PIPESTATUS shell parameter. For example, $command = "make -f <parameters> 2>&1 | tee -a build.log; let \\!PIPESTATUS"; might work, but I haven't tested it.

      merlyn's answer was dead on, for some reason it earlier it wasn't returning an error code, now with a minor tweak to merlyn's code I was able to get it working exactly like I wanted. THANKS A MILLION! This is a best place on the net for anything perl. I will be donating some $$$....

      my code for completeness:

      open MAKE, "make -f <params> 2>&1 |" or die; open (LOGFILE, ">>build.log") or die; while (<MAKE>) { print LOGFILE $_; print } close MAKE; # to get $? my $exit = $? >> 8; close LOGFILE;

      Edit: g0n - code tags

        the code wasn't actually returning the error code for the make file as far as i could tell. I used it to run any program, and had to add this at the end:
        exit $exit;
      PIPESTATUS does indeed work when using Bash. Nice trick!
      false | tee /dev/null [ $PIPESTATUS -eq 0 ] || exit $PIPESTATUS

      ---
      "A Jedi uses the Force for knowledge and defense, never for attack."
Re: Capture Exit Code Using Pipe & Tee
by ikegami (Patriarch) on Jan 31, 2007 at 20:55 UTC

    The exit value is always 0, because it's returning the tee value, and not make. Any and all help is very apprciated.

    Not quite. It's returning the error code of the shell invoked to execute the command. system sometimes, but not always, passes the command to /bin/sh. This is one of those times. See the docs for details.

      Both true and untrue misleading(?). It's getting the return/error code of the shell, yes. However, the shell is getting it from the tee command. If tee were to return 1, the shell would return 1, regardless of what make did.

      merlyn's answer is definitely the right track. Doing this in perl is actually not that bad, and though it may take more code than shmem's response, I'm not sure it'd be less readable ;-)

        No, it doesn't necessarily get it from the tee command, and it always gets it from the shell when the it is called. I can think of three circumstances where it does not.

        • If the shell can't execute tee, the error code would not be that of tee.
        • If the shell exits due to a signal or a core dump, the error code would not be that of tee. (Right?)
        • If the script runs under Windows and tee returns an error code greater than 1, the error code returned would be 1.
Re: Capture Exit Code Using Pipe & Tee
by ikegami (Patriarch) on Jul 08, 2010 at 22:21 UTC
    Instead of reimplementing one of the commands in Perl, you can be the shell!
    use strict; use warnings; use IPC::Open3 qw( open3 ); my $make_file = 'Makefile'; open(local *TO_MAKE, '<&', *STDIN) or die; *TO_MAKE if 0; # Avoid spurious warning. my $make_pid = open3( '<&TO_MAKE', local *FR_MAKE, undef, 'make', '-f', $make_file, ); *FR_MAKE if 0; # Avoid spurious warning. my $tee_pid = open3( '<&FR_MAKE', '>&STDOUT', undef, 'tee', '-a', 'build.log', ); waitpid($make_pid, 0); print("$?\n"); waitpid($tee_pid, 0); print("$?\n");

    Duplicating our STDIN instead of using '<&STDIN' is necessary since open3 closes the handle we pass for the child's STDIN.