27/01/2009

capture piped command status in ksh

The explanations I found are not so clear so I will write my own ... Consider the following script fail.sh:
echo "this is a failing script"
echo "this will fail after this line"
return 1
Now here is the problem, suppose this script needs to be called in the following form :
fail.sh | tee fail.log 
What happens if you check the return code ( $? ) of this command is that you will get a nice 0. The return code only gives you the return of the last command which was executed by the shell. In our example that would be tee and it worked fine, but we want to capture the failure in the middle ... The following is by no means original, I built it from various sources on the net:
exec 3>&1
status=`((fail.sh 2>&1 ; echo $? >&4)| tee fail.log >&3) 4>&1`
echo $status
Alright, so I assume you (like me) know that 1 is the stdout file descriptor and that 2 is the stderr file descriptor. But what the hell are 3 and 4 ?!
  • 3 is a copy of the stdout file descriptor.This copy is done with the initial exec command.Throughout the execution of the line, anything directed to it will get on the stdout of the wrapping script. Thus the tee output is redirected to it so we see the output of fail.sh on the terminal.
  • 4 is a file descriptor to a new buffer which receives the value of the exit code and is merged in stdout at the end of the command. Since the line is executed in backticks the stdout is stored in the status variable.
  • The 2>&1 is the classic merge of stderr in stdout for the fail command. this is useless here as our script doesn't output anything on stderr but it can come in handy with other scripts.

1 commentaire:

Thomas Spalinger a dit…

Hi Jean

thanks for sharing this. It helped me a lot.
But I had a problem that some commands for your fail.sh would hang/freeze the whole script.

I use it to work mainly with SQLPlus for Oracle and normaly sql-script won't do any problems. But as i used a script that restarts the database, sqlplus has something done to hold the script.
After many hours of debugging, if found out, that the file descriptor that redirect the output from the stuff between backsticks could wait for ever and dont stop waiting with read.

i found a workaround that solved this problem. it seams that the direct call of read is more robust than the use of backsticks in ksh.

when i use the follwing code for your example, i din't had a problem:
exec 3>&1
{
{
fail.sh 2>&1
echo ${?} >&4
} | tee fail.log >&3
} 4>&1 | read status


also i rewrote the code a bit to looks cleaner (for my opinion ;)
{
{
{
fail.sh 2>&1
echo ${?} >&4
} | tee fail.log >&3
} 4>&1 | read status
} 3>&1


i never found out the root cause of this problem, but maybe someone can use this too.

kind regards
Thomas