Expect test of exit code in exit trap to be non-zero if exit due to variable check - bash

I expect the following script to exit by writing a non-zero integer to the screen, but it actually writes 0. If I comment out trap exiting EXIT, run, and then execute echo $? from the console, I get what I expect, a non-zero exit code. I'd expect both flows to return the same non-zero exit code. For example, if I uncomment exit 10 then both flows, with and without the trap, print 10.
Is this a bug with variable testing?
#!/usr/bin/env bash
function exiting {
echo $?
}
trap exiting EXIT
# exit 10
: ${foo?'where is foo?'}

Related

Why can't I exit from an exit trap when I'm inside of a function in ZSH, unless I'm in a loop?

I'm really trying to understand the difference in how ZSH and Bash are handling signal traps, but I'm having a very hard time grasping why ZSH is doing what it's doing.
In short, I'm not able to exit a script with exit in ZSH from within a trap if the execution point is within a function, unless it's also within a loop.
Here is an example of how exit in a trap action behaves in the global / file level scope.
#!/bin/zsh
trap 'echo "Trap SIGINT" ; exit 130' SIGINT
sleep 1
echo "1"
sleep 1
echo "2"
sleep 1
echo "3"
If I call the script, I can send an INT signal by pressing Cntrl+C at any time to echo "Trap SIGINT" and exit the script immediately.
If I hit Cntrl+C after I see the first 1, the output looks like this:
$ ./foobar
1
^CTrap SIGINT
But if I wrap the code in a function, then the trap doesn't want to stop script execution until the function finishes. Using exit 130 from within the trap action just continues the code from the execution point within the function.
Here is an example of how using trap behaves in the function level scope.
#!/bin/zsh
trap 'echo "Trap SIGINT" ; exit 130' SIGINT
foobar() {
sleep 1
echo "1"
sleep 1
echo "2"
sleep 1
echo "3"
}
foobar
echo "Finished"
If I call the script, the only thing that an INT signal does is end the sleep command early. The script will just keep on going from the same execution point after that.
If I hit Cntrl+C repeatedly the output looks like this.
$ ./foobar
^CTrap SIGINT
1
^CTrap SIGINT
2
^CTrap SIGINT
3
It doesn't echo the "Finished" at the end, so it is exiting when the function is finished, but I can't seem to exit before it's finished.
It doesn't make a difference if I set the trap in the global / file scope or from within the function.
If I change exit 130 to return 130, then it will jump out of that function early but continue script execution. This is expected behavior from what I could read in the ZSH documentation.
If I wrap the code inside of a for or while loop as shown in the code below, the code then has no problem breaking out of the loop.
#!/bin/zsh
trap 'echo "Trap SIGINT" ; exit 130' SIGINT
foobar() {
for i in 1; do
sleep 1
echo "1"
sleep 1
echo "2"
sleep 1
echo "3"
done
sleep 1
echo "Outside of loop"
}
foobar
echo "Finished"
Even if I have the loop in the global / file scope and calling foobar from within the loop, it still has no problem exiting within the trap action. I assume it's because using
The one thing that does work correctly is defining a TRAPINT function instead of using the trap built-in, and returning a non-exit code from that function. However exiting from the TRAPINT function works the same way it does with the trap built-in.
I've tried to find anything on why it acts like this but I couldn't find anything.
So what's actually happening here? Why is ZSH not letting me exit from the trap action when the execution point is inside a function?
One way to make this work as expected is setting the ERR_EXIT option.
From the documentation:
If a command has a non-zero exit status, execute the ZERR trap, if set, and exit. This is disabled while running initialization scripts.
There's also ERR_RETURN:
If a command has a non-zero exit status, return immediately from the enclosing function. The logic is similar to that for ERR_EXIT, except that an implicit return statement is executed instead of an exit. This will trigger an exit at the outermost level of a non-interactive script.
Both options have some caveats and notes; refer to the documentation.
Adding a setopt localoptions err_exit as the first line of the foobar function (You probably don't want to do this globally) in your script causes:
$ ./foobar
1
^CTrap SIGINT
$
Now, the interesting bit. In your demonstration script, if you change your exit value from 130 to some other number, and the echo lines to echo "1 - $?" etc., you get:
$ ./foobar
1 - 0
2 - 0
^CTrap SIGINT
3 - 130
The sleep is still exiting with 130, the normal value for a process killed by a SIGINT. What happened to your exit in the trap and its value? Not a clue (I'll update the answer if I figure it out) .
I'd just stick with the TRAPnal functions when writing zsh scripts that care about signals.

bashs's command behavior about exit pipe exit. `exit 1 | exit 2`

I am curious about bash's behavior and the exit status of the situation when I enter the command
exit [exit status] | exit [exit status] | .. [repetition of exit and exit status]
it gives me output below. and, then doesn't exits.
Is this an undefined behavior?
bash-3.2$ exit 1 | exit 2
bash-3.2$ echo $?
2
From the bash man page:
Each command in a pipeline is executed as a separate process (i.e., in a subshell).
So, even the first exit will not exit your shell, as it only exits the subshell.
As for the exit codes:
The return status of a pipeline is the exit status of the last command, unless the pipefail option is enabled. If pipefail is enabled, the pipeline's return status is the value of the last (rightmost) command to exit with a non-zero status, or zero if all commands exit successfully.
You can activate pipefail like this:
$ set -o pipefail
$ exit 1 | exit 2 | exit 0
$ echo $?
2
exit 1 | exit 2 is not sequential but concurrent.
Even if the last command takes STDOUT output from the first command.
What is a simple explanation for how pipes work in Bash?
Moreover, each one of those commands is executed in a subshell.
So your main shell, where you type commands is not exited.
A piped command is like a whole composition of commands instead of one command after another.
If you want to exit, you can make it sequential exit 1 || exit 2.
Finally, by default, $? is the most recent foreground pipeline exit status.
What are the special dollar sign shell variables?

Using wait process-id on a bash if-condition returning error code 1 for successful process termination

I know a little of bash return codes on successful/failure conditions, but I was experimenting a little with wait on background processes on couple of scripts on if condition and I was surprised to see the behavior on the return error codes 0 for success and non-zero for failure cases.
My scripts:-
$cat foo.sh
#!/bin/bash
sleep 5
$cat bar.sh
#!/bin/bash
sleep 10
$cat experiment.sh
./foo.sh &
pid1=$!
./bar.sh &
pid2=$!
if wait $pid1 && wait $pid2
then
echo "Am getting screwed here!"
else
echo "Am supposed to be screwed here!"
fi
Run the script as it is and getting the output as Am getting screwed here! instead of Am supposed to be screwed here!
$./experiment.sh
Am getting screwed here!
Now modifying the scripts to forcefully return exit codes using exit in both foo.sh and bar.sh
$cat foo.sh
#!/bin/bash
sleep 5
exit 2
$cat bar.sh
#!/bin/bash
sleep 10
exit 17
And am surprised to see the output as
$./experiment.sh
Am supposed to be screwed here!
Apologize for the detailed post, but any help appreciated.
The man page for reference:- http://ss64.com/bash/wait.html
That's correct behavior. The exit status of wait (when called with a single process ID) is the exit status of the process being waited on. Since at least one of them has a non-zero exit status, the && list fails and the else branch is taken.
The rationale is that there is one way (0) for a command to succeed but many ways (any non-zero integer) for it to fail. Don't confuse bash's use of exit statuses with the standard Boolean interpretation of 0 as false and nonzero as true. The shell if statement checks if its command succeeds.

Non-bash equivalent to "trap ERR"

I'm trying to remove some bashisms from a shell script.
I can't figure out how I would replicate 'trap ERR': ERR is not a signal, and is not in the standard, but is a common bashism (see e.g. the LDP guide).
How do I replicate trap ERR in a standards-compliant /bin/sh script?
You can use a combination of set -e and trap ... EXIT.
#!/bin/sh
set -e
err_handler () {
[ $? -eq 0 ] && exit
# Code for non-zero exit status here
}
trap err_handler EXIT
set -e will cause the script to exit whenever an unguarded command has an non-zero exit status. The error handler will be called unconditionally when the script exits, but inside the handler you can simply exit if the current exit status is 0, i.e., we reached it without any errors occurring.
By "unguarded" command I mean a command that isn't run in a context where a non-zero exit status is reasonably expected to occur, such as in the condition for an if statement.
ERR is not really a signal. See: http://en.wikibooks.org/wiki/Bourne_Shell_Scripting/Debugging_and_signal_handling#Err..._ERR.3F at the bottom.

Can you access the code of an exit command in a trap?

I understand that I can use $? to see the exit code of the last executed command, but what if I want to identify whether I have thrown my own "exit 0" or "exit 1"?
For example:
#!/bin/bash -e
trap "{ echo Exit code $?; exit; }" EXIT
exit 1
If I run this script, it prints out "Exit code 0" and then exits with exit code 1. Can I access the code in the trap, or am I just going about this the wrong way? In other words, I would like this simple script to print out "Exit code 1".
It's 0 because $? at the beginning of a script, where it is substituted due to the double quotes, is 0.
Try this instead:
trap '{ echo Exit code $?; exit; }' EXIT
Any process that terminates sets the $?, it means that it constantly will get overwritten. Save $? to a separately named var that is unique and echo that upon exit.
Edit
See Exit Shell Script Based on Process Exit Code

Resources