The goal was to make a simple unintrusive wrapper that traces stdin and stdout to stderr:
#!/bin/bash
tee /dev/stderr | ./script.sh | tee /dev/stderr
exit ${PIPESTATUS[1]}
Test script script.sh
:
#!/bin/bash
echo asd
sleep 1
exit 4
But when the script exits, it doesn't terminate the wrapper. Possible solution is to end the first tee
from the second command of the pipe:
#!/bin/bash
# Second subshell will get the PID of the first one through the pipe.
# It will be able to kill the whole script by killing the first subshell.
# Create a temporary named pipe (it's safe, conflicts will throw an error).
pipe=$(mktemp -u)
if ! mkfifo $pipe; then
echo "ERROR: debug tracing pipe creation failed." >&2
exit 1
fi
# Attach it to file descriptor 3.
exec 3<>$pipe
# Unlink the named pipe.
rm $pipe
(echo $BASHPID >&3; tee /dev/stderr) | (./script.sh; r=$?; kill $(head -n1 <&3); exit $r) | tee /dev/stderr
exit ${PIPESTATUS[1]}
That's a lot of code. Is there another way?
How to escape quotes and pipe? Capturing the output of the command (using backquotes) and then echo ing that is an antipattern. Just run the command directly, and let its output go to the usual place.
For all options, the opposite of set -𝓧 is set +𝓧 (note the plus sign). So set +e will undo set -e , and set +o pipefail will undo set -o pipefail .
The PIPESTATUS environment variable in Bash comes to our rescue for getting the exit status of each command in a pipeline. $ PIPESTATUS is an array. It stores the exit status of each command in the pipeline: $ hello_world.sh 5 | grep "Hello World" | grep "Hello Universe" $ echo ${PIPESTATUS[@]} 5 0 1.
To set an exit code in a script use exit 0 where 0 is the number you want to return. In the following example a shell script exits with a 1 . This file is saved as exit.sh . Executing this script shows that the exit code is correctly set.
I think that you're looking for the pipefail option. From the bash man page:
pipefail
If set, the return value of a pipeline is the value of the last (rightmost) command to exit with a non-zero status, or zero if all commands in the pipeline exit successfully. This option is disabled by default.
So if you start your wrapper script with
#!/bin/bash
set -e
set -o pipefail
Then the wrapper will exit when any error occurs (set -e
) and will set the status of the pipeline in the way that you want.
The main issue at hand here is clearly the pipe. In bash, when executing a command of the form
command1 | command2
and command2
dies or terminates, the pipe which receives the output (/dev/stdout
) from command1
becomes broken. The broken pipe, however, does not terminate command1
. This will only happen when it tries to write to the broken pipe, upon which it will exit with sigpipe
. A simple demonstration of this can be seen in this question.
If you want to avoid this problem, you should make use of process substitution in combination with input redirection. This way, you avoid pipes. The above pipeline is then written as:
command2 < <(command1)
In the case of the OP, this would become:
./script.sh < <(tee /dev/stderr) | tee /dev/stderr
which can also be written as:
./script.sh < <(tee /dev/stderr) > >(tee /dev/stderr)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With