Bash Command Operators Explained: ;, &&, ||, &, and |

In Linux Bash, ; executes the next command regardless of whether the previous command succeeds, && executes the next command only if the previous command succeeds, and || executes the next command only if the previous command fails. Understanding the differences between these three symbols is simple if you think in terms of the exit status returned by a command.

Connecting short commands on one line is convenient, but if you misunderstand the execution conditions, unintended commands may run in operations such as deployment, deletion, or backup. This article explains not only the basic syntax and evaluation order of the three operators, but also background execution &, pipes | and |&, and exit status inversion !.

Overview of Bash command execution symbols

Operator Conditions for executing the next command Main use
; Execute regardless of the success or failure of the previous command Used to execute independent commands sequentially.
&& Execute only if the previous command succeeds Used when the previous step must succeed for the next step to be meaningful, such as installation, build, or deployment.
|| Execute only if the previous command fails Used for displaying error messages, executing alternative commands, and simple recovery.
& Run in the background without waiting for completion Used when running long tasks asynchronously while continuing to use the terminal.
| Connect the standard output of the previous command to the input of the following command Allows multiple commands to process data consecutively, such as output filtering, searching, and sorting.
|& Sending standard output and standard error together to a command Used when you want to record or filter normal output and errors at the same time.
! Invert the exit status of a command or pipeline Used when you need to treat a failure case as a success condition.

The GNU Bash Manual describes ;, &, &&, and || as operators for constructing a list of commands. | and |& are pipeline operators connecting command input and output, and ! is a reserved word that inverts the exit status of a pipeline.

What is an exit status that determines execution conditions?

In Bash, commands return an exit status between 0 and 255 when finished executing. Generally, 0 means success and any non-zero value means failure. The exit status of the last executed command can be checked with the special parameter $?.

true
echo $?

false
echo $?

true returns 0, which means success, and false returns a non-zero value, which means failure. && and || check the exit status directly, not the text displayed on the screen.

When Bash cannot find a command, it generally returns 127, and when it finds a command but cannot execute it, it returns 126. Different rules may apply for commands terminated by signals, so you should not assume that all failures return 1. The important criterion is whether it is 0 or not, rather than a specific number.

Usage of semicolon;: continue execution regardless of the result

A semicolon separates multiple commands in a line in order. Bash waits for the previous command to finish and then executes the following command regardless of its success. A newline also acts as a separator similar to a semicolon in most command lists.

command1 ; command2

In the following example, the last ls will execute even if the file copy fails.

mkdir -p backup ; cp report.txt backup/ ; ls -l backup/

If the three commands are independent of each other, this is a concise expression. However, if you need to check the list or perform subsequent actions only if the report.txt copy succeeds, && is more appropriate than a semicolon.

Usage of && and ||: execution depending on success or failure

&& executes the next command only if the previous command succeeds

Commands connected with && proceed to the next command only if the exit status of the previous command is 0. This is called short-circuit evaluation. If the previous step fails, there is no need to evaluate the following command, so it is not executed.

command1 && command2

You can write it as follows to execute an upgrade only if the package list has been successfully updated.

sudo apt update && sudo apt upgrade

It is also useful when building after moving to the source directory. You can prevent accidentally building in the current directory when cd fails.

cd /srv/myapp && make

|| executes the next command only if the previous command fails

A command connected with || will only execute if the exit status of the previous command is not 0. It is suitable for signaling failure or attempting an alternative action.

command1 || command2

Here is an example that checks if a configuration file exists and prints a message if it does not.

test -f /etc/myapp.conf || echo "The configuration file does not exist."

You can use it as follows to create a directory only if it does not exist. However, in practice, mkdir -p already allows directories that exist, so this can be a simpler method.

test -d backup || mkdir backup

Background execution, pipelines and state inversion

& executes the command in the background without waiting

If you add & at the end of a command, Bash executes that command asynchronously and can accept the next input without waiting for it to complete. This is useful when starting a long-running task and then running other commands in the same terminal.

./long-job.sh > job.log 2>&1 &

The final & means background execution, and the preceding > and 2>&1 are redirections that send output to a file. You can check background jobs with jobs and control them with fg or bg. If you need the process to continue after closing the terminal, it is safer to use a service manager or a separate session management tool.

| passes the output of the preceding command to the input of the following command

A pipe | connects the standard output (stdout) of the preceding command to the standard input (stdin) of the following command. Unlike ||, which chooses execution depending on the success of the previous command, a pipe passes data between commands.

ps aux | grep nginx | sort

In this example, grep receives the process list and finds lines containing nginx, and the result is sorted by sort. The output of each command can be processed sequentially without saving all of it in a temporary file.

|& passes even the standard error along with it

`|&` sends both the standard output and standard error (stderr) of the preceding command to the input of the following command. In Bash, it is a simplified form of `2>&1 |`.

./build.sh |& tee build.log

You can display the normal and error messages of a build on the screen while saving the same contents to a log file. If you need to handle standard error separately, it is better to use explicit redirection instead of `|&`.

`!` inverts the success and failure of a command or pipeline.

Placing `!` before a command or pipeline logically inverts the exit status. If the original exit status is 0, it is evaluated as failure, and if it is non-zero, it is evaluated as success.

if! grep -q "enabled" app.conf; then
 echo "The setting is not enabled."
fi

This pattern is useful for clearly handling failure conditions, such as when a specific string is not found. Note that it inverts only the exit status, not the output of the command itself.

Pipeline errors depend on the `pipefail` setting.

By default, the exit status of a pipeline is the exit status of the last command. Even if an earlier command fails, the pipeline may appear to succeed if the last command succeeds. Scripts that need to detect failures inside a pipeline should consider the `pipefail` option.

set -o pipefail
command1 | command2 | command3

When pipefail is enabled, the pipeline returns 0 only if all commands succeed. If one or more fail, it returns the non-zero exit status of the rightmost failed command.

Evaluation order when mixing multiple operators

&& and || have the same precedence and are evaluated from left to right

In a Bash command list, && and || have the same precedence and are evaluated left-associatively. Therefore, the following command should not be simply interpreted as "if the first command succeeds, run the second, otherwise run the third."

command1 && command2 || command3

If command1 succeeds, command2 is executed. However, if command2 fails, the result of the entire left expression becomes a failure, so command3 is also executed. In other words, the failure of the second command affects the execution condition of the third command.

true && false || echo "This statement will be executed."

If you need an if-else, write the condition explicitly

If you need to branch based only on the success of the first command, regardless of the second command’s success, it is safer not to use &&... ||... as a shorthand for if-else. Use an if statement to clearly express your intention.

if command1; then
  command2
else
  command3
fi

When you need to group multiple commands into a single unit of success or failure, you can use curly braces or parentheses. A curly brace group runs in the current shell and requires a semicolon or newline before the closing brace.

command1 && {
  command2
  command3
}

A parenthesis group runs in a subshell, so any changes to the current directory or regular shell variables made inside the group do not remain outside the group.

( cd /srv/myapp && make ) || echo "Build failed."

Command chaining patterns frequently used in practice

Deploy only if all required steps succeed

You can chain commands so that the deployment command runs only if both the build and test succeed. If any intermediate step fails, the subsequent steps are not executed.

make && make test && ./deploy.sh

Leave a failure message and exit

This is a pattern that sends a message to standard error and exits with an explicit failure status when a required file is missing.

test -f app.conf || {
 echo "The app.conf file is required." >&2
 exit 1
}

Always run cleanup commands after the main task

You can use a semicolon to delete temporary files regardless of whether the task succeeds or fails.

./process-data.sh ; rm -f /tmp/myapp.lock

However, if you need to make sure cleanup occurs even if the script is terminated midway or receives a signal, a semicolon alone is not enough. In such cases, it is safer to use Bash's trap to register cleanup tasks upon termination.

Criteria for choosing in interactive commands and shell scripts

When running briefly in the terminal, && and || are fast and easy to read. For example, checking a file after changing directory or printing a message if a command fails is sufficient in a single line.

On the other hand, in operational scripts where you run several commands at each branch or need logging, retries, and recovery, it is better to use if, functions, and explicit exit. Expressing the failure points and subsequent actions clearly rather than writing concisely is advantageous for maintenance.

Turning on set -e may give the impression that error handling is automatically resolved, but you need to be careful when it comes to conditions and lists inside &&/||. For important scripts, it is safer to explicitly check the failure of each command and set the necessary messages and exit statuses yourself.

Common mistakes

  • If you think the command after ; guarantees the success of the previous command: The semicolon does not check for success.
  • If you judge success by screen output: && and || use exit status, not output content.
  • If you use A && B || C as a complete if-else: C will execute even if B fails.
  • If you chain a long deployment procedure in a single line: It is difficult to figure out which step failed, so scripts with explicit error handling are better.
  • If you do not check the exit status of the command itself: Some commands return 0 even if they print warnings, or return a failure status under unexpected conditions.

Frequently Asked Questions

Are semicolons and line breaks the same?

In general command lists, one or more line breaks act as command separators, just like a semicolon. However, in contexts like quotes, backslashes, pipes, and compound commands, where the shell interprets that the input is not finished, line break handling can be different.

What should be checked if the command after && does not execute?

After executing the previous command alone, check the exit status with echo $?. If the value is non-zero, it is normal that the command after && does not run. You should also check permissions, file paths, options, and input values.

Is || an operator that ignores errors?

No. || only runs the following command if the previous command fails. If the final command succeeds, the exit status of the entire list can become 0, so you need to deliberately decide whether to log the original error or exit with a failure status again.

How are & and && different?

& runs the command asynchronously in the background, so the shell does not wait for it to complete. && is a conditional chaining operator that only executes the next command if the previous command finishes and succeeds.

How are the pipe | and || different?

| connects the standard output of the previous command to the standard input of the following command. || does not pass data and executes the subsequent command based on whether the previous command failed.

Summary

To execute independent commands sequentially, use ;. To continue only if the previous step succeeds, use &&. To execute an alternative action if a command fails, use ||. To avoid waiting for completion, use &. To pass data between commands, use | or |&. To invert success and failure evaluation, use !.

For short terminal tasks, command chaining operators are efficient, but for tasks involving multiple steps and recovery logic, it is safer to write them out using conditionals and functions. In particular, remember that A && B || C will execute C even if B fails.

References

More in This Category
Linux free Command: Check Memory and Swap Usage

Linux free Command: Check Memory and Swap Usage

Learn how to use the Linux free command to inspect available memory, used memory, cache, and swap usage, with essential options, practical examples, output interpretation, and common troubleshooting tips.

Linux renice Command: Change the Priority of a Running Process

Linux renice Command: Change the Priority of a Running Process

Learn how to change the Priority of a Running Process with the Linux renice command, including practical examples, key options, and important precautions.

Linux tmux Command: Detach and Reattach Terminal Sessions

Linux tmux Command: Detach and Reattach Terminal Sessions

Learn how to detach and Reattach Terminal Sessions with the Linux tmux command, including practical examples, key options, and important precautions.

Linux apt Command: Manage Packages on Debian and Ubuntu

Linux apt Command: Manage Packages on Debian and Ubuntu

Learn how to manage Packages on Debian and Ubuntu with the Linux apt command, including practical examples, key options, and important precautions.

Linux df Command: Check Filesystem Free Space

Linux df Command: Check Filesystem Free Space

Learn how to check Filesystem Free Space with the Linux df command, including practical examples, key options, and important precautions.

Linux more Command: View Text One Screen at a Time

Linux more Command: View Text One Screen at a Time

Learn how to use Linux more to read text one screen at a time, move and search interactively, start at a line or pattern, and choose between more and less.

Linux mv Command: Move and Rename Files and Directories

Linux mv Command: Move and Rename Files and Directories

Learn how to move and rename files or directories with Linux mv, avoid unwanted overwrites, clarify destination paths, and handle cross-filesystem moves.

Linux reboot Command: Restart the System Safely

Linux reboot Command: Restart the System Safely

Learn how to restart the System Safely with the Linux reboot command, including practical examples, key options, and important precautions.

Linux uname Command: Check Kernel and System Architecture

Linux uname Command: Check Kernel and System Architecture

Learn how to check Kernel and System Architecture with the Linux uname command, including practical examples, key options, and important precautions.

Linux diff Command: Compare Text Files and Directories

Linux diff Command: Compare Text Files and Directories

Learn how Linux diff compares text files and directories, produces unified patches, ignores selected whitespace changes, and reports differences through exit status.