Claude vs GPT-4 for Shell Scripting 2026
Shell scripts are unforgiving. A missing quote, an unset variable check, or a rm -rf without a guard can cause real damage. This comparison tests Claude and GPT-4o on five real-world scripting tasks.
Test 1: Safe File Processing Script
Prompt:
Write a Bash script that processes all .log files in a directory.
For each file: count lines, find unique IPs (format X.X.X.X), write results to a CSV.
The script should: use strict mode, handle missing directory argument,
not fail if no .log files exist, be POSIX-portable where possible.
Claude Output
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
usage() {
echo "Usage: $(basename "$0") <log-directory>" >&2
echo " Processes .log files and writes results to results.csv" >&2
exit 1
}
[[ $# -ne 1 ]] && usage
LOG_DIR="${1}"
OUTPUT_CSV="${LOG_DIR}/results.csv"
if [[ ! -d "${LOG_DIR}" ]]; then
echo "Error: '${LOG_DIR}' is not a directory" >&2
exit 1
fi
# Write CSV header
echo "filename,line_count,unique_ip_count,unique_ips" > "${OUTPUT_CSV}"
# Collect .log files; handle case where none exist
shopt -s nullglob
log_files=("${LOG_DIR}"/*.log)
shopt -u nullglob
if [[ ${#log_files[@]} -eq 0 ]]; then
echo "No .log files found in '${LOG_DIR}'" >&2
echo "Empty report written to ${OUTPUT_CSV}"
exit 0
fi
for log_file in "${log_files[@]}"; do
filename="$(basename "${log_file}")"
line_count=$(wc -l < "${log_file}")
# Extract and deduplicate IPs
unique_ips=$(grep -oE '\b([0-9]{1,3}\.){3}[0-9]{1,3}\b' "${log_file}" \
| sort -u \
| tr '\n' '|' \
| sed 's/|$//')
ip_count=$(grep -oE '\b([0-9]{1,3}\.){3}[0-9]{1,3}\b' "${log_file}" \
| sort -u \
| wc -l)
# Quote the IP list in case it contains special chars
printf '%s,%d,%d,"%s"\n' \
"${filename}" "${line_count}" "${ip_count}" "${unique_ips}" \
>> "${OUTPUT_CSV}"
done
echo "Results written to ${OUTPUT_CSV} (${#log_files[@]} files processed)"
Claude uses set -euo pipefail, IFS=$'\n\t', shopt -s nullglob for the empty directory case, properly quoted variables throughout, and printf for safe CSV output (not echo). The IP regex is standard and portable.
GPT-4o Output
GPT-4o produces a functional script but:
- Omits
IFSsetting (word splitting issues on filenames with spaces) - Uses
ls *.loginstead of glob expansion (fails on spaces in names) - Missing
shopt -s nullglob(script errors if no.logfiles exist) - Uses
echofor CSV output without quoting
Verdict: Claude — significantly safer for production use.
Test 2: Deployment Script with Rollback
Prompt:
Write a deployment script that:
1. Pulls a Docker image (IMAGE_TAG from env)
2. Runs smoke tests (curl check) against the new container
3. If smoke tests pass, stops the old container and starts the new one
4. If smoke tests fail, removes the new container and exits non-zero
5. Logs all actions with timestamps
Claude Output
#!/usr/bin/env bash
set -euo pipefail
: "${IMAGE_TAG:?IMAGE_TAG must be set}"
: "${SERVICE_NAME:?SERVICE_NAME must be set}"
: "${PORT:=8080}"
SMOKE_URL="http://localhost:${PORT}/health"
NEW_CONTAINER="${SERVICE_NAME}-new"
OLD_CONTAINER="${SERVICE_NAME}"
LOG_FILE="/var/log/deploy-${SERVICE_NAME}.log"
log() {
local level="${1}"; shift
printf '[%s] [%s] %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "${level}" "$*" \
| tee -a "${LOG_FILE}"
}
cleanup_new() {
log "WARN" "Removing failed new container ${NEW_CONTAINER}"
docker rm -f "${NEW_CONTAINER}" 2>/dev/null || true
}
trap cleanup_new ERR
log "INFO" "Pulling image ${IMAGE_TAG}"
docker pull "${IMAGE_TAG}"
log "INFO" "Starting new container ${NEW_CONTAINER}"
docker run -d \
--name "${NEW_CONTAINER}" \
--network bridge \
-p "127.0.0.1:$((PORT + 1)):${PORT}" \
"${IMAGE_TAG}"
log "INFO" "Waiting for container to start"
sleep 3
log "INFO" "Running smoke tests against ${SMOKE_URL}"
SMOKE_PORT=$((PORT + 1))
for attempt in 1 2 3; do
if curl -sf --max-time 5 "http://localhost:${SMOKE_PORT}/health" > /dev/null; then
log "INFO" "Smoke test passed (attempt ${attempt})"
break
fi
if [[ ${attempt} -eq 3 ]]; then
log "ERROR" "Smoke tests failed after 3 attempts"
exit 1
fi
log "WARN" "Smoke test attempt ${attempt} failed, retrying in 5s"
sleep 5
done
log "INFO" "Stopping old container ${OLD_CONTAINER}"
docker stop "${OLD_CONTAINER}" 2>/dev/null || true
docker rm "${OLD_CONTAINER}" 2>/dev/null || true
log "INFO" "Renaming ${NEW_CONTAINER} to ${OLD_CONTAINER}"
docker rename "${NEW_CONTAINER}" "${OLD_CONTAINER}"
trap - ERR
log "INFO" "Deployment complete"
Key details Claude gets right: trap cleanup_new ERR for automatic rollback on any failure, publishing the new container to a different port for smoke testing (doesn’t interfere with running service), ${VAR:?message} for required env vars, and || true on cleanup commands so they don’t abort.
GPT-4o wrote a similar script but forgot the trap and tested against the production port instead of an isolated one.
Test 3: Argument Parsing
Prompt:
Write a Bash script with long options: --input FILE (required), --output DIR (default: /tmp),
--verbose (flag), --format [json|csv|tsv] (default: json). Show usage on --help.
#!/usr/bin/env bash
set -euo pipefail
VERBOSE=false
FORMAT="json"
OUTPUT="/tmp"
INPUT=""
usage() {
cat <<EOF
Usage: $(basename "$0") --input FILE [OPTIONS]
Options:
--input FILE Input file (required)
--output DIR Output directory (default: /tmp)
--format FORMAT Output format: json, csv, tsv (default: json)
--verbose Enable verbose output
--help Show this help
EOF
exit "${1:-0}"
}
while [[ $# -gt 0 ]]; do
case "${1}" in
--input) INPUT="${2}"; shift 2 ;;
--output) OUTPUT="${2}"; shift 2 ;;
--format) FORMAT="${2}"; shift 2 ;;
--verbose) VERBOSE=true; shift ;;
--help) usage 0 ;;
*) echo "Unknown option: ${1}" >&2; usage 1 ;;
esac
done
[[ -z "${INPUT}" ]] && { echo "Error: --input is required" >&2; usage 1; }
[[ ! -f "${INPUT}" ]] && { echo "Error: '${INPUT}' is not a file" >&2; exit 1; }
[[ "${FORMAT}" =~ ^(json|csv|tsv)$ ]] || {
echo "Error: invalid format '${FORMAT}'" >&2; exit 1
}
${VERBOSE} && echo "Processing ${INPUT} → ${OUTPUT} (format: ${FORMAT})" >&2
Both Claude and GPT-4o handle argument parsing well. Claude’s version adds input validation (file existence check, format enum validation) without being asked — GPT-4o skips these unless specified.
Test 4: Process Monitor
Prompt:
Write a script that monitors a process by name, restarts it if it crashes,
with a maximum of 5 restarts in 60 seconds (circuit breaker pattern). Log everything.
Claude implements the circuit breaker correctly using an array of timestamps and checking how many falls within the window. GPT-4o uses a simple counter without the time window — it would restart 5 times quickly then refuse to restart after a single recovery.
Related Reading
- Best AI Tools for Writing Shell Scripts for Server Automation
- Best AI Tools for Writing Makefiles
-
Claude Sonnet vs GPT-4o for Code Review Accuracy Comparison
Related Articles
- AI-Powered Log Analysis Tools for Debugging
- How to Use AI for Log Anomaly Detection
- AI Powered Log Analysis Tools for Production Debugging
- How to Use Claude for Debugging Failed CI/CD Pipeline
- Claude vs Cursor: Refactoring Strategy Comparison
Built by theluckystrike — More at zovo.one