Bash is designed to compose programs. Every command in your path is a first-class citizen — piping grep into awk into sort is idiomatic and powerful. Python is designed to write programs. It gives you control flow, data structures, testing, and packaging — at the cost of a little more ceremony upfront.
The practical rule of thumb: if most of your script is calling external tools and moving data between them, Bash wins. If most of it is logic — conditionals, parsing, network calls, state — Python wins.
The same task in each language reveals the different mental models.
.log files older than 7 daysfind /var/logs -name "*.log" \
-mtime +7 \
-exec mv {} {}.bak \;
from pathlib import Path
from datetime import datetime, timedelta
cutoff = datetime.now() - timedelta(days=7)
for f in Path("/var/logs").glob("*.log"):
if datetime.fromtimestamp(f.stat().st_mtime) < cutoff:
f.rename(f.with_suffix(".log.bak"))
curl -s https://api.example.com/data \
| jq '.users[] | select(.active) | .name'
import requests
resp = requests.get("https://api.example.com/data")
users = resp.json()["users"]
names = [u["name"] for u in users if u["active"]]
print("\n".join(names))
for i in 1 2 3; do
deploy.sh && break
sleep $((i * 5))
done
import time, subprocess
for attempt in range(1, 4):
result = subprocess.run(["deploy.sh"])
if result.returncode == 0:
break
time.sleep(attempt * 5)
| Criterion | Bash | Python |
|---|---|---|
| Time to first working script | ||
| Readability at scale | ||
| Error handling | ||
| Process & pipeline composition | ||
| Cross-platform portability | ||
| Data structures & parsing | ||
| Startup overhead | ||
| Testability | ||
| Ecosystem / packages |
Which tool wins in specific scenarios?
Bash has a sharp edge hidden behind its simplicity. These are the most common sources of bugs:
file="my report.txt"
rm $file # runs: rm my report.txt (two args — dangerous!)
rm "$file" # correct
#!/usr/bin/env bash
set -euo pipefail # add this to every script header
# -e exits on error, -u catches unset vars
# -o pipefail catches errors in pipes
echo $((10 / 3)) # outputs 3, not 3.333
# Use Python or bc for float math:
python3 -c "print(10/3)"
The most pragmatic approach is often a Bash entrypoint that delegates heavy lifting to Python. The outer shell handles argument parsing, environment setup, and process glue — Python handles logic, data, and error handling.
#!/usr/bin/env bash
set -euo pipefail
export ENV="${1:-production}"
export LOG_DIR="/var/log/myapp"
# Bash handles the system setup
mkdir -p "$LOG_DIR"
echo "Starting deploy to $ENV..."
# Python handles the complex logic
python3 scripts/deploy.py \
--env "$ENV" \
--log-dir "$LOG_DIR"
echo "Done."