3

When I write the code the way below, I am able to run several commands after the else statement:

if [ "$?" -eq 0 ]
    then              
        echo "OK"
    else 
        echo "NOK"
        exit 1
fi

However, when I use another syntax I am unable to union 2 commands after the OR:

[ "$?" -eq 0 ] && echo "OK" || (echo "NOK" >&2 ; exit 1)

In my use-case I have a complex script based on "$?" == 0, so I'm looking for a way to abort (additionally to echoing the message) when it is not true.

  • 1
    Not that the question isn't a good one, but I'm curious why you are so insistent on avoiding the if/then/else syntax. If you're worried about having to use several lines, it is perfectly possible to make a one-liner of it: if a; then b; else c; fi – Dolda2000 Dec 24 '18 at 1:51
8

The pair of ( ) spawns a subshell, defeating the goal of exiting the whole script with the exit command inside.

Just replace the ( ) with { } (and adjusted syntax because { } are not automatical delimiters but more treated like commands: a space after { and last command inside must end with some terminator:; fits): this will run the chain of commands inside in the same shell, thus exit will affect this shell.

[ "$?" -eq 0 ] && echo "OK" || { echo "NOK" >&2; exit 1;}

UPDATE: @D.BenKnoble commented that should echo fail, the behaviour won't be like the former if ...; then ... else ... fi construct. So the first echo's exit code has to be "escaped" with a noop : command (which being built-in can't fail).

[ "$?" -eq 0 ] && { echo "OK"; :;} || { echo "NOK" >&2; exit 1;}

references:

POSIX:

Grouping Commands

The format for grouping commands is as follows:

(compound-list)
     Execute compound-list in a subshell environment; see Shell
     Execution Environment. Variable assignments and built-in commands 
     that affect the environment shall not remain in effect
     after the list finishes.

[...]

{ compound-list;}
     Execute compound-list in the current process environment. The
     semicolon shown here is an example of a control operator delimiting
     the } reserved word. Other delimiters are possible, as shown
     in Shell Grammar; a <newline> is frequently used.

dash manpage,bash manpage,...

  • This is NOT guaranteed to work the way you think it will; if echo fails for some reason the script will exit. – D. Ben Knoble Dec 23 '18 at 22:00
  • 1
    @D.BenKnoble you're right. I was just addressing the obvious, considering echo cannot fail. Addressed it by forcing the exit code of the (now first) compound command. – A.B Dec 24 '18 at 1:01
4

One way would be to use the following script line:

[ "$?" -eq 0 ] && echo "OK" || exit $(echo "NOK" >&2 ; echo 1)

That serves the purpose of reporting an error and returning an error code, whilst being silent if the "OK" path is chosen.

3

The problem is that you are executing the other branch in a subshell (that is what the parentheses mead - e.g. in bash(1) man page look for compound commands) - thus the exit quits the subshell and not the shell executing the script. To see it more graphically, put a long enough sleep in both versions of your code, and run ps faux (for coreutils version of ps) or something like pstree to see the process ancestry relationships.

Now, how to "fix" it:

  1. there's nothing wrong about if ... then / else / fi. Especially for longer blocks it makes the code more readable.

  2. if you want a one liner, then you can do

    [ "$?" -eq 0 ] && echo "OK" || echo "NOK" >&2 && exit 1
    

    which relies on echo exiting gracefully, or use a group command

    [ "$?" -eq 0 ] && echo "OK" || { echo "NOK" >&2 ; exit 1; }
    

    Check for general availability in your shell and for any peculiarities though - e.g. it should be terminated by a newline or a semicolon.

  3. most comprehensive - use a signal/event handler (bash example, syntax for other shells might differ):

    #!/bin/bash
    
    exit_handler () {
        echo "error happened, exiting"
        ... fix things, that may be half baked ...
    }
    
    trap exit_handler EXIT
    
    ... do stuff ...
    

    This gives you the opportunity to:

    • use set -e (set -o errexit) which is often considered dangerous - the EXIT handler can do things like removing any temporary files you one doesn't want to leave behind
    • have the cleanup code in one place only
    • handle other signals (those sent by e.g. kill) as well

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.