Here's a C code snippet that will cause segfault:

// segfault.c

#include <string.h>

int main()
    memset((char *)0x0, 1, 100);
    return 1;

Compile it with:

gcc segfault.c -o segfault

If executed from bash:

$ ./segfault
Segmentation fault (core dumped)

Now I wrapped the call inside a bash script. There are three consecutive attempts. I want to get the error output inside the variable ret and display it.


# segfault.sh

ret=`./segfault 2>&1`
echo "1) " $ret
ret=`eval ./segfault 2>&1`
echo "2) " $ret
ret=`eval ./segfault 2>&1 | cat`
echo "3) " $ret

If I execute the script from bash:

3)  ./segfault.sh: line 7: 28814 Segmentation fault (core dumped) ./segfault

Clearly, only the third form of invocation works. My question is, why couldn't the first two forms capture the error output?


It works for me with simplified bash script (only stderr):

$ cat seg.sh 
echo "Segfault" 1>&2
$ test=`./seg.sh`; echo "x$test"
$ test=`./seg.sh 2>&1`; echo "x$test"
$ test=`eval ./seg.sh 2>&1`; echo "x$test"

The problem in your case is caused by the fact that Segmentation fault (core dumped) is not written by your program (because it is killed by kernel), but by the parent process, who get the information about the death of his child. This effect is hidden by putting it into another process and pipe with cat in your last example. You should rather rely on the exit code, then on the stderr:

$ ./segfault; echo $?
Segmentation fault (core dumped)
| improve this answer | |

The message “Segmentation fault (core dumped)” is emitted by bash, not by the program that crashed (when the message is emitted, the program is already dead!). The redirection applies only to the program itself.

To redirect messages from the shell itself about the program, run the program inside a shell grouping construct, and redirect the output of the whole group. The most basic shell grouping construct, that does nothing but group, is braces.

ret=`{ ./segfault; } 2>&1`

The form ret=`eval ./segfault 2>&1` applies the redirection to the whole evaluation of the eval command, so in principle it should work, and it does in fact work on my machine with bash 4.3.30 and older versions. What may be happening (and I can reproduce it with ksh) is that your version of bash makes some optimizations to avoid forking subprograms when they are the last command in a subshell. The nominal way to execute the command ret=`eval ./segfault` is:

  • Create a pipe.
  • Fork, i.e. create a shell subprocess. In the subprocess (process 1):
    • Redirect the output to the pipe.
    • Execute the eval builtin.
    • Fork. In the subprocess (process 2):
      • Execute the file ./segfault, i.e. replace the shell program that's currently running in this process by the segfault program.
    • (In process 1) Wait for process 2 to finish.
    • Process 1 exits.
  • (In the original shell process) Read from the pipe and accumulate the data in the ret variable.
  • When the pipe is closed, continue execution.

As you can see, process 1 creates another process, then waits for it to finish, and immediately exits. It would be more efficient for process 1 to recycle itself. Some shells (and shell versions) are better than others at recognizing such situations and making a tail call optimization. However, in the case of ret=`{ ./segfault; } 2>&1`, process 2 has its standard error redirected to file descriptor 1, but process 1 doesn't. In the shell version you tried, the optimizer didn't recognize this situation (it could have performed a tail call, but it should have set up the redirection differently).

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.