3

When writing a bash script, which is better to chain commands, a semicolon ; or double ampersand &&?

Eliah Kagan
  • 117,780
  • 7
    It's not really a matter of "better" - use && if you want a command to be executed conditionally based on the exit status of the previous one – steeldriver Apr 20 '18 at 19:13

1 Answers1

11

As steeldriver says, neither is better than the other. You use them for different purposes.

Use ; if you want to run one command, then another. The second command will run regardless of whether or not the first one succeeded. Separating commands with a ; achieves the same goal as running one command, and then running the other one. In a script, these achieve the same goal:

first-command
second-command
first-command; second-command

In contrast, && checks the exit status of the first command, and only runs the second command if the first command succeeded. In shell scripting (and when you are using a shell interactively, too), an an exit code of 0 is taken to mean "true" or "success," while any other exit code is taken to mean "false," or "failure." (That way, programs can return different error codes to signify different kinds of errors.)

So, here, as before, first-command runs first. But if it does't succeed, then second-command is not run at all:

first-command && second-command

This is similar to, but not exactly the same as, using if:

if first-command; then
    second-command
fi

The way they are different lies in what happens when first-command fails. With &&, the entire compound command's exit status is whatever failing (i.e., nonzero) exit code first-command returned. In contrast, with if, the entire compound command's exit status is zero (i.e., success) when first-command fails.


As an example of a situation where you might actually reasonably consider both ; and &&, suppose you wanted to run sudo apt update, which retrieves up-to-date information about what packages are available from each of your configured software sources and at what versions, followed by sudo apt upgrade, to upgrade your installed packages to their latest available versions. This is widely suggested, and is what I usually run:

sudo apt update && sudo apt upgrade

Because it uses &&, it runs sudo apt update, but only runs sudo apt upgrade if sudo apt update reported that it succeeded. The reason you would usually want this, in the case of those particular commands, is that you would usually want to inspect any errors that the first command produced in order to decide how to proceed.

But you might expect a problem but still want to upgrade what packages you could. Or you might not expect a problem but just prefer to proceed ahead anyway. In that situation, you could separate them with ; (or just put them on separate lines, in a script):

sudo apt update; sudo apt upgrade

That runs sudo apt update, then runs sudo apt upgrade regardless of whether or not it reported succeeding.

This works as an example of a situation where you might actually be choosing between the two, because it is a case where the first command is important for the second one, but where the second one might still be useful even if the first one failed. However, in many situations where you want to run one command and then another, you either have:

  • No dependence of the second command on the first, where if the first fails, you still want to run the second one and its ability to succeed is expected not to be affected. Then you will usually use ; or place them on separate lines of a script.
  • Complete dependence of the second command on the first, such that it would make no sense--or be actively harmful--to attempt the second command if the first had failed. Then you will want to use && to separate them.

By the way, there is also a || operator. This runs the second command only if the first command failed rather than only if it succeeded (as with &&):

first-command || second-command

|| should not be confused with |, which sets up a pipe between commands.

Eliah Kagan
  • 117,780
  • 1
    A useful example is the following 'one-liner' to test if the computer is booted in UEFI or BIOS (alias legacy) mode: test -d /sys/firmware/efi && echo efi || echo bios – sudodus Apr 20 '18 at 20:15
  • 1
    @sudodus You can use that but I don't consider it good technique. I recommend against trying to use && and || as a ternary conditional. Often the second command can fail; then the third runs. Even echo can fail, like if stdout is not writable (you can redirect to /dev/full to simulate this). In that command, if echo efi fails then echo bios should also fail. But imagine if you replaced echo bios with something that had other effects! Even when there's no risk, it's poorly self documenting, as a && b || c doesn't achieve ternary conditional semantics outside such corner cases. – Eliah Kagan Apr 20 '18 at 20:39
  • Please suggest a better syntax to determine if the computer was booted in UEFI or BIOS mode. (I think a one-liner is best, it can be more complicated, since it can be copied and pasted.) – sudodus Apr 21 '18 at 05:35
  • 1
    @sudodus Just based on the command you showed, ls -ld /sys/firmware/efi is more compact and clear about what it's doing; to say how to interpret its output is to explain how the test works. However, if your specific goal is to print efi when the system is booted in UEFI mode and bios when it's booted in BIOS mode, and you need to do it in a one-liner, then I would still prefer if [ -d /sys/firmware/efi ]; then echo efi; else echo bios; fi. It's longer, but it still fits comfortably on one line, and its meaning is extremely clear. Readers needn't consider the exit status of echo efi. – Eliah Kagan Apr 21 '18 at 11:06
  • I will follow your advice, but I think test something is easier to understand than [ something ] so I would prefer if test -d /sys/firmware/efi; then echo efi; else echo bios; fi – sudodus Apr 21 '18 at 15:42
  • 1
    @sudodus and EliahKagan a && { b ; : ;} || c provides a possible workaround for the problem: the group always returns 0 no matter whether b failed. How about test -d /sys/firmware/efi && { echo efi; : ;} || echo bios? – dessert Apr 23 '18 at 07:02
  • 1
    @dessert, Yes it works too :-) But we must agree with EliahKagan that an 'if then else fi' statement is easier to understand than the logic behind '&& and ||'. – sudodus Apr 23 '18 at 07:07