5

I am writing a shell installation script
After each command I need to check if the command is successful or not - And I must notify the user what failed.
If something fails - installation cannot continue, currently after each command I am adding

if [ $? -eq 0 ]; then  

But this adds like 6 lines to every command of the shell script
Is there a way to make this checks shorter?

sample:

do some command
if [ $? -eq 0 ]; then
    echo notify user OK
else
    echo notify user FAIL
    return -1
fi
do some command
if [ $? -eq 0 ]; then
    echo notify user OK
else
    echo notify user FAIL
    return -1
fi
JavaSheriff
  • 7,074
  • 20
  • 89
  • 159
  • 2
    BTW, `return -1` doesn't make sense -- only positive single-byte integers are guaranteed to be available as return values. – Charles Duffy Jan 04 '18 at 17:04
  • 2
    @cdarke, I'm not sure that "single-byte integer" implies "signed", but at the same point, I have no room to object when it comes to pedantry. :) – Charles Duffy Jan 04 '18 at 17:09
  • 1
    @CharlesDuffy: no, it was the word "positive", anyway I deleted the comment because I decided it wasn't helpful. – cdarke Jan 04 '18 at 17:12
  • 1
    BTW, `do` is a shell keyword, so `do some command` is a rather unfortunate choice of standins. – Charles Duffy Jan 04 '18 at 17:13
  • @CharlesDuffy: [Your answer on dup marked question is probably the best](https://stackoverflow.com/a/185900/548225) – anubhava Jan 04 '18 at 17:28

4 Answers4

5

First, the idiomatic way to check if a command worked is directly in the if statement.

if command; then
    echo notify user OK >&2
else
    echo notify user FAIL >&2
    return -1
fi

(Good practice: Use of >&2 to send the messages to stderr.)

There are several ways you could simplify this.

Write a function

Just like in other programming languages, common logic can be moved into shared functions.

check() {
    local command=("$@")

    if "${command[@]}"; then
        echo notify user OK >&2
    else
        echo notify user FAIL >&2
        exit 1
    fi
}

check command1
check command2
check command3

Don't print anything

In idiomatic shell scripts, successful commands don't print anything. Printing nothing means success in UNIX. Also, any well-behaved command that fails will already print an error message, so you don't need to add one.

Taking advantage of those two facts, you could use || exit to exit whenever a command fails. You can read || as "or else".

command1 || exit
command2 || exit
command3 || exit

Use -e

Alternatively, you could enable the -e shell flag to have the shell exit whenever a command fails. Then you don't need anything at all.

#!/bin/bash -e

command1
command2
command3

Don't print anything

If you do in fact want error messages, but are okay with no success messages, a die() function is popular.

die() {
    local message=$1

    echo "$message" >&2
    exit 1
}

command1 || die 'command1 failed'
command2 || die 'command2 failed'
command3 || die 'command3 failed'
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
  • this is great but how will the user know what failed? i can post the entire command in but some of them are really long – JavaSheriff Jan 04 '18 at 17:07
  • @user648026, `{ printf 'Notify user FAIL: '; printf '%q ' "$@"; echo; } >&2` will log the specific command that failed in the context of the `check` function given here. – Charles Duffy Jan 04 '18 at 17:08
  • The significantly simpler `printf 'Notify user FAIL: %s\n' "$*" >&2` is of course also less informative for debugging quoting problems, but nevertheless probably sufficient in many situations. – tripleee Jan 05 '18 at 02:44
  • It took me a whrle to figure out why Charles wrote it that way. The second `printf` basically loops over and prints each separate argument out of `"$@"`. Refactoring it to avoid printing a trailing space should be trivial (remove the last space in the previous `printf` and print a leading space here instead). – tripleee Jan 05 '18 at 02:47
3

This is a code antipattern. You want

if do some command; then
    echo notify user OK
else
    echo notify user fail
    exit 255  # exit code must be unsigned short
fi

The command set -e at the top of your script will force the shell to signal failure and exit the script if any command fails. This has some unobvious complications, but might actually be what you want here.

Returning to the earlier topic, a common style is

do some command && echo notify user OK || die 255 Notify user fail

where the definition of die is easily googlable and might also inspire you to explore ways to refactor the success scenario into a function so you can perhaps do something like

guard "enable the frobnitz" do some command

where perhaps guard is defined something like

guard () {
    local what=$1
    shift
    if "$@"; then
        echo "$what succeeded" >&2
    else
        rc=$?
        echo "$what failed" >&2
        exit $rc
    fi
}

Notice also how this has a couple of signficant improvements; we preserve the original failure exit code, and we print all the diagnostics to standard error. The previous examples should properly do at least the latter as well, actually.

tripleee
  • 175,061
  • 34
  • 275
  • 318
  • 1
    Scary how similar this is to John's answer. This has a couple of possibly useful variations so I won't delete it, but he is clearly the faster typist. – tripleee Jan 04 '18 at 17:14
  • 2
    (Not necessarily actually; I painstakingly composed this answer on my phone, as a matter of fact.) – tripleee Jan 04 '18 at 17:15
2

Add this to the beggining of your script. It will make the shell stop the script if any command returns a non-zero status.

set -e

Regards!

Matias Barrios
  • 4,674
  • 3
  • 22
  • 49
  • 4
    See [BashFAQ #105](http://mywiki.wooledge.org/BashFAQ/105) re: why `set -e` use is discouraged. (Skip the parable for the exercises below if in a hurry). – Charles Duffy Jan 04 '18 at 17:03
  • 1
    Also see https://www.in-ulm.de/~mascheck/various/set-e/ re: how `set -e` behavior varies (wildly and incompatibly!) between various shells. – Charles Duffy Jan 04 '18 at 17:04
  • 2
    @CharlesDuffy Nice reading. I did not know "set -e" was so flawed. – Matias Barrios Jan 04 '18 at 17:13
2

I guess you can shift the check logic inside a function like:

checkLastCommand() {
     if [ $? -eq 0 ]; then
          echo notify user OK
     else 
          echo notify user FAIL
          exit -1 
     fi
 }
 do some command
 checkLastCommand
 do some command
 checkLastCommand
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
snap
  • 1,598
  • 1
  • 14
  • 21