简体   繁体   中英

bash one-line conditional fails when using set -e

I started using set -e in my bash scripts, and discovered that short form of conditional expression breaks the script execution.

For example the following line should check that $var is not empty:

[ -z "$var" ] && die "result is empty"

But causes silent exit from script when $var has non-zero length.

I used this form of conditional expression in many places...

What should I do to make it run correctly? Rewrite everything with "if" construction (which would be ugly)? Or abandon "set -e"?

Edit: Everybody is asking for the code. Here is full [non]working example:

#!/bin/bash
set -e

function check_me()
{
    ws="smth"
    [ -z "$ws" ] && echo " fail" && exit 1
}

echo "checking wrong thing"
check_me
echo "check finished"

I'd expect it to print both echoes before and after function call. But it silently fails in the check_me function. Output is:

checking wrong thing

Use

[ -n "$var" ] || die "result is empty"

This way, the return value of the entire statement is true if $var is non-empty, so the ERR trap is not triggered.

You should write your script such that no command ever exits with non-zero status.

In your command [ -z "$var" ] can be true, in which case you call die , or false in which case -e does it's thing.

Either write it with if , as you say, or use something like this:

[ -z "$var" ] && die "result is empty" || true

I'd recommend if though.

I'm afraid you will have to rewrite everything so no false statements occur.

The definition of set -e is clear:

-e Exit immediately if a simple command (see SHELL GRAMMAR above) exits with a non-zero status. The shell does not exit if the command that fails is part of the command list immediately following a while or until keyword, part of the test in an if statement, part of a && or || list, or if the command's return value is being inverted via !. A trap on ERR, if set, is executed before the shell exits.

You are using the "optimization" system of Bash: because a false statement will cause an AND ( && ) statement never to be true, bash knows it doesn't have to execute the second part of the line. However, this is a clever "abuse" of the system, not intended behaviour and therefore incompatible with set -e . You will have to rewrite everything so it is using proper if s.

What the bash help isn't very clear on is that only the last statement in an && or || chain is subject to causing an exit under set -e . foo && bar will exit if bar returns false, but not if foo returns false.

So your script should work... but it doesn't. Why?

It's not because of the failed -z test. It's because that failure makes the function return a non-zero status :

#!/bin/bash
set -e

function check_me()
{
    ws="smth"
    [ -z "$ws" ] && echo " fail" && exit 1
    # The line above fails, setting $? to 1
    # The function now returns, returning 1!
}

echo "checking wrong thing"
check_me   # function returns 1, causing exit here
echo "check finished"

So there are multiple ways to fix this. You could add ||true to the conditional inside the function, or to the line that calls check_me . But as others have pointed out, using ||true has its own problems.

In this specific scenario, where the desired postcondition of check_me is "either this thing is valid or the script has exited", the straightforward thing to do is to write it like that, ie [[ -n "$ws" ]] || die "whatever" [[ -n "$ws" ]] || die "whatever" .

But using && conditions will actually work fine with set -e in general, as long as you don't use such a conditional as the last thing in a function . You need to add an explicit true or return 0 or even : as a statement following such a conditional, unless you intend the function to return false when the condition fails.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM