11

I am having trouble capturing output and exit codes inside a shell.

I need to compare exit codes from 2 scripts and if they don't match I want to echo the output of my 2 scripts.

What I currently have:

#!/bin/bash

resultA=$(./a.out 2>&1)
exitA=$?
resultB=$(./b.out 2>&1)
exitB=$?

Problem is a possible Segmentation Fault message is not captured, because it is directed to the error output of my current shell, but I need to capture everything including something like Segmentation Faults.

What is kind of a workaround and not as detailed as the real message:

#!/bin/bash

resultA=$(./a.out 2>&1)
exitA=$?
resultB=$(./b.out 2>&1)
exitB=$?
if [ $exitA == 139 ]; then
    resultA=$resultA"Segmentation Fault"
fi

This makes the words segmentation fault at least appear in my result variables.

Michael Jaros
  • 4,586
  • 1
  • 22
  • 39
Flo
  • 171
  • 1
  • 1
  • 8
  • 1
    I am a little confused: Your second example does not contain more subshells than the first one. – Michael Jaros Mar 06 '15 at 15:53
  • Is the problem here that you want to capture the segfault message in the variable and that isn't working? – Etan Reisner Mar 06 '15 at 15:57
  • Sorry @MichaelJaros I reedited my post. – Flo Mar 06 '15 at 16:03
  • @EtanReisner Yes I want to capture the segmentation fault message in the variables 'resultA' and 'resultB' as well. – Flo Mar 06 '15 at 16:04
  • You can't :) If you really need that text (e.g. for a report) best you could do is generate it in your script if your exit code indicates a `SIGSEGV` (see my answer below). – Michael Jaros Mar 06 '15 at 16:14
  • A couple of errors with your `if`. First, if you use the `test` builtin, `[` the there must be a space following the `[`. Second, there is a missing $. You are doing a textual comparison, an arithmetic comparison might be more appropriate: `if (( $exitA == 139 ))`. – cdarke Mar 06 '15 at 17:50
  • @cdarke Yea sorry I just wrote that from semi memory, thanks for reminding me that the syntax is not completely correct. – Flo Mar 09 '15 at 11:45
  • @MichaelJaros I got a working solution now, seems you were wrong. – Flo Mar 09 '15 at 12:33
  • @Flo rici has explained in his solution that this is not the same message. I understand that his solution helps you more than telling you it is not possible, but technically, the latter is still correct. – Michael Jaros Mar 09 '15 at 12:58

3 Answers3

9

It's possible to capture the segfault error message, but you really need to work at it.

Here's one way:

outputA=$(bash -c '(./a)' 2>&1)

Here we create an child shell (with bash -c) whose stderr is redirected to stdout, and then get that child to execute the program in an explicit subshell. Errors inside the subshell will be captured by the child bash, which will then generate an error message (which is not quite the same as the message produced by an interactive bash):

$ echo $outputA
bash: line 1: 11636 Segmentation fault (core dumped) ( ./a )
rici
  • 234,347
  • 28
  • 237
  • 341
  • Wondering how this works, at least I can confirm that it works. I thought that the message will get issued by a program like `apport` (configured in `/proc/sys/kernel/core_pattern`). Seems that if bash runs in non-interactive mode it preserves the message – hek2mgl Mar 06 '15 at 16:42
  • 1
    @hek2mgl: any process can trap dead children (through SIGCHLD), and bash does so. That's somewhat independent of the creation of a coredump; as you say, `apport` is triggered by the kernel when a coredump is produced (if you've left the configuration that way), but the coredump does not suppress the parent notification. (The parent is informed about the coredump. See sigaction, and search for CLD_DUMPED) – rici Mar 06 '15 at 17:06
  • 1
    @hek2mgl: Also, apport doesn't produce the Segfault error message; bash does. The trick is getting a bash to redirect the error message, and making sure that the bash with the redirection is the one which produces the error message. Hence, two levels of indirection. – rici Mar 06 '15 at 17:10
  • thanks for your explanation, especially for the info about `CLD_DUMPED` - I wondered how bash could know about the `core dumped` otherwise since it is not safe that a coredump will get created. – hek2mgl Mar 07 '15 at 13:51
  • Thanks @rici another thing though, I said I also need the exit code, for comparisons between two program versions. Does that mean I would have to execute the program twice to get both the message and the exit code? Or can I somehow pass on the exit code of that subshell and save it in a variable? In my original post the first code block saves output & exit code (minus SEGFAULT MSG) in one execution. – Flo Mar 09 '15 at 11:38
  • Ok thanks @rici I figured it out: `outputA=$(bash -c '(./a.out); exit $?' 2>&1)` and `exitA=$?` – Flo Mar 09 '15 at 12:20
  • @flo: you don't need the `exit $?`, because the exit code of `bash -c` is the exit code of the subshell which is the exit code of the executable. (This doesn't work on all shells, apparently -- there are some workarounds in autoconf scripts -- but afaik it works without problems in bash, and it is required by Posix.) – rici Mar 09 '15 at 14:39
  • really useful for testing : `count=\`echo $(bash -c '(./script-to-test.sh cmd-arg)' 2>&1)| grep -ic "$expected_msg"\`; [[ $count -gt 0 ]] && "tst passed"` – Yordan Georgiev Sep 29 '19 at 11:17
5

Thanks to @rici this is the complete solution to my problem:

#!/bin/bash

resultA=$(bash -c '(./a.out); exit $?' 2>&1)
exitA=$?
resultB=$(bash -c '(./b.out); exit $?' 2>&1)
exitB=$?
Flo
  • 171
  • 1
  • 1
  • 8
  • why does running the programs have to be in parenthesis? Does this still work `'./a.out; exit $?'` work? – masonCherry Sep 04 '22 at 13:48
  • @masonCherry: That's actually explained in my answer (albeit briefly). If `a.out` produces a segfault, then you need `bash -c '(./a.out)'` in order to capture the error message. You don't need `exit $?` to pass through the exit code. However, if you use `./a.out; exit $?`, then you don't need the subshell (created with the parentheses) because `bash -c` needs to create a subshell in order to execute two commands. – rici Sep 16 '22 at 16:21
0

Check out hek2mgl's answer for the reason for the missing message in your output.

The Bash manpage provides a hint towards a solution:

When a command terminates on a fatal signal N, bash uses the value of 128+N as the exit status.

You could use this to handle the special case of a signal killing one of your child processes.

Michael Jaros
  • 4,586
  • 1
  • 22
  • 39