Bash – How to redirect error info of executable C program to stdout? (MAC OS X)

bashcio-redirectionosxpipe

I want to write a automatic C program checker.
For example, I have a toy "hello.c" program:

#include <stdio.h>

int main()
{
    int a, b;

    while (scanf("%d %d", (&a)-1000000000000000, &b) != EOF)
    {
        printf("%d\n", a+b);
    }

    return 0;
}

And here is my input file "1.in":

1 2
4 5
10 10
2 2
3 2
7 4

and output file "1.out":

3
9
20
4
5
11

I use "gcc hello.c -o hello.o" to compile and generate an executable program "hello.o". Obviously, the program will meet "segment fault": (Run in my MAC OS X)

$ ./hello.o <1.in
Segmentation fault: 11

But I want to make an auto checker using pipe and diff:

./hello.o <1.in | diff - 1.out

And the output is:

0a1,6
> 3
> 9
> 20
> 4
> 5
> 11

No error message display! But I want to display them in the terminal (MAC OS X).

I try to redirect stderr to stdout like:

./hello.o <1.in 2>&1 | diff - 1.out

But no effect!

I also try to redirect stderr to a file like:

./hello.o <1.in 2>log

and the info "Segmentation fault: 11" display in the terminal while nothing in the file.

The same situation happens when I use

./hello.o <1.in &>log

Maybe the error info isn't in stderr.

So, how can I solve this problem? Thank you!

Best Answer

NOTE: I've replaced hello.o with hello, since the .o file extension in this context would typically denote an object file and not the final executable program.

According to your post, you want to run the command:

./hello <1.in 2>&1 | diff - 1.out

And you want the error message from running ./hello <1.in to appear in the output of this command. However the error message isn't coming from the hello.o program itself, but from the shell. The closest thing I can think of for approximating the desired effect with a single line is to run the command in a subshell and then use this output with your diff command:

2>&1 bash -c './hello <1.in' | diff - 1.out

This gives us the following output:

1c1,6
< bash: line 1: 58469 Segmentation fault: 11  ./hello < 1.in
---
> 3
> 9
> 20
> 4
> 5
> 11

The only difference is that in this case you get some additional metadata output by the shell (i.e. the line number and the command string). If you want to exactly replicate the error message, then you can use trap to insert a hook which prints out exactly the right string.

I couldn't find a way to programmatically extract the error message, so I went to the Bash source code and searched for the "Segmentation fault" message. I found it in a file called siglist.c, along with a bunch of other signals and error descriptions. Using that information I wrote the following script:

#!/bin/bash 

# trapdesc.sh
#
#   Take an error code from the `trap` command and
#   print out the corresponding error message.
#
#   Example usage:
#
#       (trap 'bash trapdesc.sh $?' EXIT; <COMMAND>)
#

# List of signal codes and corresponding error messages
#
# Taken from bash source (siglist.c):
#
#   https://github.com/tpruzina/bash/blob/master/siglist.c
#
declare -a SIGNALS=(
"SIGHUP":"Hangup"
"SIGINT":"Interrupt"
"SIGQUIT":"Quit"
"SIGILL":"Illegal instruction"
"SIGTRAP":"BPT trace/trap"
"SIGABRT":"ABORT instruction"
"SIGEMT":"EMT instruction"
"SIGFPE":"Floating point exception"
"SIGKILL":"Killed"
"SIGBUS":"Bus error"
"SIGSEGV":"Segmentation fault"
"SIGSYS":"Bad system call"
"SIGPIPE":"Broken pipe"
"SIGALRM":"Alarm clock"
"SIGTERM":"Terminated"
"SIGURG":"Urgent IO condition"
"SIGSTOP":"Stopped (signal)"
"SIGTSTP":"Stopped"
"SIGCONT":"Continue"
"SIGCLD":"Child death or stop"
"SIGTTIN":"Stopped (tty input)"
"SIGIO":"I/O ready"
"SIGXCPU":"CPU limit"
"SIGXFSZ":"File limit"
"SIGVTALRM":"Alarm (virtual)"
"SIGPROF":"Alarm (profile)"
"SIGWINCH":"Window changed"
"SIGLOST":"Record lock"
"SIGUSR1":"User signal 1"
"SIGUSR2":"User signal 2"
"SIGMSG":"HFT input data pending"
"SIGPWR":"power failure imminent"
"SIGDANGER":"system crash imminent"
"SIGMIGRATE":"migrate process to another CPU"
"SIGPRE":"programming error"
"SIGGRANT":"HFT monitor mode granted"
"SIGRETRACT":"HFT monitor mode retracted"
"SIGSOUND":"HFT sound sequence has completed"
"SIGINFO":"Information request"
)

# Make sure we get an integer 
if ! [[ "$1" =~ ^[0-9]+$ ]]; then
    2>&1 echo "Not a signal identifier: $1"
    exit 1
fi

# Convert the signal from the `trap` function value to the signal ID
sid="$(($1 - 128))"

# Make sure the signal ID is in the valid range
if [[ "${sid}" -lt 0 || "${sid}" -gt 40 ]]; then
    2>&1 echo "Unrecognized signal: ${sid}"
    exit 1
fi

# Get the array-index for the signal
index="$((sid-1))"

# Get the signal description
description="$(echo ${SIGNALS[index]} | cut -d: -f2)"

# Print the error description
echo "${description}: ${sid}"

Now, using this script, we can run the following command:

(trap 'bash trapdesc.sh $?' EXIT; ./hello <1.in)

This produces the same string as running ./hello <1.in:

Segmentation fault: 11

But now you can capture that string from standard error (stderr) and pipe it to diff like you wanted:

(2>&1 trap 'bash trapdesc.sh $?' EXIT; ./hello <1.in) | diff - 1.out

This produces the exact output that you would have gotten if the error message had been written to standard output you had originally expected:

1c1,6
< Segmentation fault: 11
---
> 3
> 9
> 20
> 4
> 5
> 11