Command Chaining & Pipes in the Shell
Command chaining and pipes are foundational techniques for working effectively in Unix-like shells such as Bash, or Zsh. They allow you to combine simple commands into useful workflows—something especially useful in DevOps, SRE, and automation contexts. This guide walks through how these operators work, when to use them, and how to apply them in real-world scenarios.
Running Commands Sequentially (;)
The semicolon (;) lets you run multiple commands one after another, regardless of whether the previous command succeeds or fails.
Example
mkdir project ; cd project ; echo "initialized"
Behavior
Use Case
Useful when you want a fixed sequence of steps and do not care about intermediate failures.
DevOps Example
docker stop myapp ; docker rm myapp ; docker run -d myapp:latest
Even if docker stop fails (container not running), the rest still executes.
Conditional Execution on Success (&&)
The && operator runs the next command only if the previous command succeeds (exit code 0).
Example
mkdir project && cd project
Behavior
Use Case
Ideal for setup scripts, deployment pipelines, and any operation that depends on success.
DevOps Example
git pull && docker build -t myapp . && docker run -d myapp
If git pull fails, nothing else runs—preventing bad builds.
Conditional Execution on Failure (||)
The || operator runs the next command only if the previous command fails (non-zero exit code).
Example
mkdir project || echo "Directory already exists"
Behavior
Acts as a fallback or error handler
Use Case
Great for scenarios that require graceful error handling, as well as implementing logging or fallback logic when operations do not proceed as expected.
DevOps Example
curl -f http://service/health || echo "Service is down"
Combining && and ||
You can combine both operators to create compact control flow.
Example
mkdir project && echo "Created" || echo "Failed"
Important Note
This behaves like:
if mkdir project; then
echo "Created"
else
echo "Failed"
fi
Pipes (|) – Passing Output Between Commands
The pipe (|) sends the output of one command as input to another.
Basic Syntax
command1 | command2
Example: Paging Output
ls /bin | less
ls lists files, and less lets you scroll through them.
Example: Limiting Output
ls /bin | head -n 5
Shows only the first 5 results
Example: Filtering
ps aux | grep nginx
Lists processes and filters for nginx
Example: Counting Results
ls /var/log | wc -l
Counts number of files
Advanced Pipe Workflows
Pipes become more useful when chaining multiple commands.
Example: Log Analysis
cat access.log | grep 500 | awk '{print $1}' | sort | uniq -c | sort -nr
Breakdown
Example: Kubernetes Debugging
kubectl get pods -A | grep CrashLoopBackOff | awk '{print $2}'
Line Continuation (\)
The backslash (\) allows you to split long commands across multiple lines.
Example
mkdir project || \
echo "Directory exists"
Multi-Line Pipeline Example
cat access.log | \
grep "ERROR" | \
awk '{print $2, $5}' | \
sort | uniq -c
Real-World DevOps Patterns
Pattern 1: Safe Directory Setup
mkdir -p /app/data && cd /app/data
Pattern 2: Build + Deploy Pipeline
npm install && npm run build && docker build -t app . && docker run -d app
Pattern 3: Health Check with Fallback
curl -f http://localhost:8080/health || systemctl restart app
Pattern 4: Log Monitoring Pipeline
tail -f /var/log/syslog | grep --line-buffered "ERROR"
Pattern 5: Disk Usage Analysis
du -sh * | sort -hr | head -n 10
Exit Codes and Why They Matter
All chaining relies on exit codes:
You can inspect it:
echo $?
Example:
false && echo "won't run"
false || echo "runs because previous failed"
Best Practices
Prefer && for critical workflows: Prevents cascading failures
Use || for fallback logic: Handle errors cleanly
Combine pipes with filters: Use grep, awk, sed, sort, uniq
Break long commands with \: Improves readability and maintainability
Avoid overly complex one-liners
Sometimes scripts are clearer:
#!/bin/bash
if mkdir project; then
cd project
else
echo "Failed"
fi
Putting It All Together
Example: End-to-End Workflow
mkdir app && cd app && \
git clone https://github.com/example/repo . && \
docker build -t app . && \
docker run -d -p 8080:80 app || \
echo "Deployment failed"
Final Thoughts
Command chaining and pipes are fundamentally about composition—taking small, focused commands and connecting them into reliable, efficient workflows that amplify their individual capabilities. By mastering these patterns, you enable faster troubleshooting through quick command combinations, cleaner automation by reducing unnecessary complexity, more powerful command-line usage that leverages existing tools effectively, and a reduced need for large, hard-to-maintain scripts. This approach is especially valuable in SRE and DevOps environments, where repeatability, reliability, and speed are critical, allowing engineers to build robust operational workflows using simple, composable building blocks.