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 ps Command: Inspect Running Processes

Linux ps Command: Inspect Running Processes

Learn how to inspect Running Processes with the Linux ps command, including practical examples, key options, and important precautions.

Linux whatis Command: Show One-Line Command Descriptions

Linux whatis Command: Show One-Line Command Descriptions

Learn how to use the Linux whatis command to display concise descriptions of commands and manual pages, with essential options, practical examples, output interpretation, and common troubleshooting tips.

Linux fmt Command: Reformat Plain-Text Paragraphs

Linux fmt Command: Reformat Plain-Text Paragraphs

Learn how to reformat Plain-Text Paragraphs with the Linux fmt command, including practical examples, key options, and important precautions.

Best Linux Distributions for Web Servers: Ubuntu, Debian, or Rocky Linux?

Best Linux Distributions for Web Servers: Ubuntu, Debian, or Rocky Linux?

Compare Ubuntu Server, Debian, and Rocky Linux for web hosting based on support lifecycle, package freshness, documentation, operational stability, and application requirements.

Linux xz Command: Compress a File in xz Format

Linux xz Command: Compress a File in xz Format

Learn how to compress a File in xz Format with the Linux xz 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.

Linux id Command: Show User and Group IDs

Linux id Command: Show User and Group IDs

Learn how to show User and Group IDs with the Linux id command, including practical examples, key options, and important precautions.

Linux Tutorial / File Permissions and Safe sudo Use

Linux Tutorial / File Permissions and Safe sudo Use

Learn to read file and directory permissions, practice chmod safely, and understand when to use sudo and how to diagnose access errors.

Linux traceroute Command: Trace Routers on the Way to a Host

Linux traceroute Command: Trace Routers on the Way to a Host

Learn how to trace Routers on the Way to a Host with the Linux traceroute command, including practical examples, key options, and important precautions.

Linux history Command: Review and Reuse Shell Command History

Linux history Command: Review and Reuse Shell Command History

Learn how to use the Linux history command to review, search, reuse, and manage shell command history, with essential options, practical examples, output interpretation, and common troubleshooting tips.