Your last example is the most fail safe.
trap 'rm -rf "$dir"' EXIT
This will execute as long as the shell itself is still functional. Basically SIGKILL is the only thing that it won't handle since the shell is forcibly terminated.
(perhaps SIGSEGV too, didn't try, but it can be caught)
If you don't leave it up to the shell to clean up after itself, the only other possible alternative is to have the kernel do it. This is not normally a kernel feature, however there is one trick you can do, but it has it's own issues:
#!/bin/bash
mkdir /tmp/$$
mount -t tmpfs none /tmp/$$
cd /tmp/$$
umount -l /tmp/$$
rmdir /tmp/$$
do_stuff
Basically you create a tmpfs mount, and then lazy unmount it. Once the script is done it'll be removed.
The downside other than being overly complex, is that if the script dies for any reason before the unmount, you've not got a mount laying around.
This also uses tmpfs, which will consume memory. But you could make the process more complex and use a loop filesystem, and remove the file backing it after it's mounted.
Ultimately the trap
is best as far as simplicity and safety, and unless you're script is regularly getting SIGKILLed, I'd stick with it.
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
Best Answer
This would try to execute
./a.out
and then add a line with a single0
to its output if that program exited with a non-zero exit status or failed to execute at all. The0
would be caught bytail -n 1
and placed in$grade
.If
./a.out
executed correctly and terminated with a zero exit status, theecho
would not be triggered.Remove the redirection of standard error to
/dev/null
if you are interested in seeing diagnostic messages related to running./a.out
.Change the
0
to"$?"
to get the exit code instead. To be able to differentiate a number from an error, you may want to useNaN
instead, or some error string.