While both ;
and &&
allow executing multiple commands on a single line, they handle command execution flow fundamentally differently:
# Semicolon example (unconditional execution)
command1 ; command2
# Double ampersand example (conditional execution)
command1 && command2
The critical difference lies in how they handle exit statuses:
false ; echo "This will run" # Executes regardless of first command's success
false && echo "This won't run" # Only executes if first command succeeds
Semicolon use case: When you need sequential execution regardless of success
make clean ; make # Always try to build even if clean fails
Double ampersand use case: When execution should halt on failure
./configure && make && make install # Classic build chain
The &&
operator can save execution time by failing fast:
# Stops at first failing test
test_connection && download_file && process_data
Combine both operators for sophisticated control flows:
# Attempt primary method, fallback to secondary on failure
primary_command || { fallback_command ; cleanup }
Always prefer &&
for critical operations where failure should stop execution:
[[ -d "/valid/path" ]] && cd "/valid/path" || exit 1
Use ;
for unrelated operations or when you explicitly want to continue after failures:
rm old_file.log ; touch new_file.log
Here's a real-world deployment script example:
# Stop deployment if any step fails
npm ci && \
npm run build && \
rsync -avz dist/ deploy@server:/app && \
ssh deploy@server "sudo systemctl restart myapp"
While both the semicolon (;) and double ampersand (&&) can execute multiple commands in one line, their underlying logic differs significantly:
# Semicolon example (unconditional execution)
command1 ; command2
# Double ampersand example (conditional execution)
command1 && command2
The semicolon acts as a simple command separator, while && implements logical AND behavior:
# Will attempt ls even if cd fails
cd /nonexistent ; ls -al
# Will abort if cd fails
cd /nonexistent && ls -al
The critical difference lies in exit status handling:
false ; echo "This always runs" # Exit status 0
false && echo "Never reaches here" # Exit status 1
For &&:
make && sudo make install # Only install if build succeeds
For ;:
cleanup ; reboot # Force both operations regardless
Chaining with && can save execution time by early termination:
# Stops at first failing test
test1 && test2 && test3 && deploy
Advanced scripts often combine both for complex logic:
precheck && (setup ; configure) || error_handling