31

I apologize in advance if this is a duplicate question. I did make an effort to search/check before asking here.

I'm comfortable with writing one-liners like this:

foocommand && foocommand2 && foocommand3

The idea being that I only want subsequent commands to run if the previous one was "successful".

I'm writing a somewhat lengthy script and this one-liner isn't feasible because it looks like a huge block of confusing code to everyone else.

I want to space out the commands and write comments inbetween them in the script. How can I do this and still have the equivalent of && in there?

6
  • 4
    This bears repeating: && doesn't mean the subsequent command will run if the previous one was successful. It means the command will run if the collective result of all the previous commands in the command list is success. You may know this, but future readers may misunderstand.
    – kojiro
    Feb 3, 2013 at 0:41
  • 1
    Also just as a note, this behavior you're describing that comes from ||and && (as opposed to | or &) is called short circuiting. The behavior used with the latter operators is called eager evaluation. Feb 3, 2013 at 2:03
  • 7
    @kojiro: I don't see the distinction. a && b && c will only run b if a succeeds, so "it only runs c if b succeeds" and "it only runs c if a and b both succeed" are equivalent statements: b can't succeed unless a succeeded.
    – ruakh
    Feb 3, 2013 at 6:13
  • 1
    @ruakh a || b && c is more illustrative.
    – kojiro
    Feb 3, 2013 at 11:49
  • 8
    @ruakh no, true || false && echo hi will output hi. The specification reads, The operators "&&" and "||" shall have equal precedence and shall be evaluated with left associativity.
    – kojiro
    Feb 3, 2013 at 22:31

7 Answers 7

40

You can change the shebang line to

#!/bin/bash -e

After any error, the script will stop.

6
  • 1
    Interesting. I'll keep that in mind. Thank you.
    – Mike B
    Feb 2, 2013 at 21:19
  • saves me from typing set -e when I need it Feb 2, 2013 at 21:38
  • @Silverrocker It is POSIX too (except for the bash part of course). The POSIX shell takes a bunch of options that are applicable to set. Or vice versa? set is a way of setting shell command line options within the script.
    – Kaz
    Feb 3, 2013 at 3:38
  • 7
    Unfortunately, there are a number of standard utilities that don't follow the usual exit-status conventions, so -e can misbehave. For example, you probably don't want your script to terminate every time you diff two non-identical files. You can, of course, work around this by appending || true (or perhaps || [ $? = 1 ]) to such commands, but the OP mentions an "everyone else" who will have to read this script, and said "everyone else" will probably not be used to having to cope with -e.
    – ruakh
    Feb 3, 2013 at 6:23
  • 4
    I used to put the -e on the she-bang, until one admin of my company kept launching my scripts with bash myscript.sh, so it ignored the -e. Now all my scripts have a set -e in the second line. Feb 4, 2013 at 8:00
24

You can do it like this:

#!/bin/sh
ls -lh &&
    # This is a comment
    echo 'Wicked, it works!'

I hope I understood what you asked correctly.

5
  • Thanks. This is the closest to what I was hoping to do. And it makes it very easy to convert back into a one liner if needed.
    – Mike B
    Feb 2, 2013 at 21:18
  • Mike, since you're concerned about code cleanliness, a common convention is to indent the 2-N sections of the chain underneath the first.
    – jblaine
    Feb 13, 2013 at 19:42
  • @jblaine I'm not sure I understand what you mean. Can you please elaborate?
    – Mike B
    Feb 16, 2013 at 19:41
  • Mike, Stackexchange won't let me format my answer properly, so here is all I meant: indent
    – jblaine
    Apr 3, 2013 at 19:03
  • @jblaine Good point, edited to indent the lines. Aug 21, 2014 at 4:18
19

If you don't like the set -e idea, maybe you can invert the logic.

foocommand || exit 1
foocommand2 || exit 2
foocommand3 || exit 3

More usefully, replace exit with something to print a useful error message, then exit. Inside a function, of course, you want return instead of exit.

3
  • 11
    Or foocommand || exit to exit with foocommand's exit status if non-zero. Feb 2, 2013 at 21:10
  • How does this work? Is it like in C - if the left argument evaluates to TRUE, the no more arguments are checked?
    – Vorac
    Mar 29, 2013 at 15:39
  • Yes, that's right. It's a common idiom in Lisp (and functional languages in general, I suppose) and in Perl.
    – tripleee
    Mar 30, 2013 at 8:14
9

You can use if else fi blocks instead.

if foocommand; then

  # some comments

  if foocommand2; then

    # more comments

    foocommand3
  fi
fi

It's a little more readable.

Alternatively you can just use \ to break your big 1 liner into several lines

foocommand && \
  # some comment
  foocommand2 && \
    # more comment
    foocommand3

But of course this can be confusing to the untrained eye.

2
  • 4
    I don't think the \ is necessary there. Feb 3, 2013 at 1:49
  • You are right. Force of habit.
    – lmcanavals
    Feb 3, 2013 at 2:15
5

You could consider using nested if statements. Other option is to use curly's to group like { true && echo hi; } || echo huh

Update 1:

Here's an example without newlines/comments:

{ { false && echo "inner true"; } && { echo "inner true" && true; } || { echo "inner false" && false; } || echo "outter false"; }
2
  • @Stephane Thanks for adding semicolon. I've been using my phone to reply so I haven't been double checking my answers. :) Feb 2, 2013 at 21:52
  • Interesting idea. Yeah, I had thought of nested if statements but if I'm chaining a lot of stuff together it might get messy.
    – Mike B
    Feb 2, 2013 at 22:38
4

Split each sequence of commands into functions:

big_block_1() {
  # ...
}
big_block_2() {
  # ...
}
big_block_3() {
  # ...
}

big_block_1 && big_block_2 && big_block_3
0

I always like to write a "fail" function which lets me specify the thing I was going to do when the failure occurred and then use the || pattern. Like the following:

function fail() {
  echo "Failure: ${@}"
  exit 1
}

foocommand || fail "couldn't foo"

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .