Bash vs Python for automation

The core philosophy

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.

Syntax side-by-side

The same task in each language reveals the different mental models.

Rename all .log files older than 7 days

Bash
find /var/logs -name "*.log" \
  -mtime +7 \
  -exec mv {} {}.bak \;
Python
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"))

Parse a JSON API response

Bash (with jq)
curl -s https://api.example.com/data \
  | jq '.users[] | select(.active) | .name'
Python
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))

Retry a command with backoff

Bash
for i in 1 2 3; do
  deploy.sh && break
  sleep $((i * 5))
done
Python
import time, subprocess

for attempt in range(1, 4):
    result = subprocess.run(["deploy.sh"])
    if result.returncode == 0:
        break
    time.sleep(attempt * 5)
Bash's brevity is a feature in scripts you write once and run rarely. Python's verbosity is a feature in scripts others need to read, maintain, or test.

Criteria comparison

Criterion Bash Python
Time to first working script
Faster
Moderate
Readability at scale
Degrades
Holds up
Error handling
Fragile
Robust
Process & pipeline composition
Native
subprocess
Cross-platform portability
Unix only
Excellent
Data structures & parsing
Painful
First-class
Startup overhead
Near-zero
~50–100ms
Testability
bats (niche)
pytest, unittest
Ecosystem / packages
System tools
PyPI (500k+)

Use case guide

Which tool wins in specific scenarios?

Bash wins
CI/CD pipeline steps
Python wins
API integration scripts
Bash wins
Dotfile & env setup
Python wins
Data transformation / ETL
Bash wins
Cron job wrappers
Python wins
Scheduled reports via email
Bash wins
Log tailing & grepping
Python wins
Multi-step deploy orchestration
Bash wins
Quick file renaming
Python wins
Web scraping
Either works
Backup scripts
Either works
Git hook automation

Common Bash pitfalls

Bash has a sharp edge hidden behind its simplicity. These are the most common sources of bugs:

Unquoted variables

file="my report.txt"
rm $file      # runs: rm my report.txt (two args — dangerous!)
rm "$file"    # correct

No error propagation by default

#!/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

Integer-only arithmetic

echo $((10 / 3))    # outputs 3, not 3.333
# Use Python or bc for float math:
python3 -c "print(10/3)"

When to mix both

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."