Shell-Scripting für Webentwickler – Bash & ZSH Automation auf macOS (2025)
Shell-Scripts automatisieren alles, was du mehr als dreimal machst. Von simplen Backup-Scripts bis zu komplexen Build-Pipelines – hier lernst du Shell-Scripting von Grund auf, mit praktischen Beispielen für Webentwickler.

~15 Min. Lesezeit · Veröffentlicht am
🎯 Was du hier lernst:
- Shell-Scripts schreiben und ausführen
- Build-Prozesse automatisieren
- Git-Workflows scripten
- Backup & Deploy automatisieren
- Fehler behandeln und debuggen
Erste Schritte
🟢 Einsteiger-Bereich: Dein erstes Shell-Script
Erstes Script erstellen
#!/bin/bash
# Mein erstes Script: hello.sh
echo "Hello World!"
echo "Aktuelles Datum: $(date)"
echo "Du bist: $USER"
echo "Arbeitsverzeichnis: $PWD" Script ausführbar machen und starten
# Ausführbar machen
chmod +x hello.sh
# Ausführen
./hello.sh
# Oder direkt mit bash/zsh
bash hello.sh
zsh hello.sh Bash vs. ZSH auf macOS
macOS nutzt seit Catalina standardmäßig ZSH. Die meisten Bash-Scripts laufen aber problemlos:
# Welche Shell ist aktiv?
echo $SHELL # /bin/zsh
# Verfügbare Shells
cat /etc/shells
# Bash-Version (macOS hat oft alte Version 3.2)
bash --version
# ZSH-Version
zsh --version Shebang & Permissions
Shebang-Varianten
#!/bin/bash # Bash explizit
#!/bin/zsh # ZSH explizit
#!/bin/sh # POSIX-kompatible Shell
#!/usr/bin/env bash # Bash über PATH (portabler)
#!/usr/bin/env zsh # ZSH über PATH
#!/usr/bin/env node # Für Node.js Scripts
#!/usr/bin/env python3 # Für Python Scripts Permissions verstehen
# Permissions anzeigen
ls -l script.sh
# -rw-r--r-- = 644 (nur Owner kann schreiben)
# -rwxr-xr-x = 755 (alle können ausführen)
# -rwx------ = 700 (nur Owner kann alles)
# Ausführbar für Owner
chmod u+x script.sh
# Ausführbar für alle
chmod +x script.sh
chmod 755 script.sh
# Nur Owner (sicherer)
chmod 700 script.sh Variablen & Parameter
Variablen definieren und nutzen
#!/bin/bash
# Variablen setzen (KEINE Leerzeichen um =)
NAME="Sven"
AGE=30
IS_DEV=true
# Variablen nutzen
echo "Hallo $NAME"
echo "Du bist $AGE Jahre alt"
echo "Entwickler: $IS_DEV"
# Geschützte Variablen (bei Sonderzeichen)
FILE="data.json"
echo "${FILE}_backup" # data.json_backup
echo "$FILE_backup" # Sucht nach Variable FILE_backup
# Command Substitution
CURRENT_DATE=$(date +%Y-%m-%d)
FILE_COUNT=$(ls -1 | wc -l)
GIT_BRANCH=$(git branch --show-current)
# Arithmetik
COUNT=5
COUNT=$((COUNT + 1)) # 6
DOUBLE=$((COUNT * 2)) # 12 Script-Parameter
#!/bin/bash
# script.sh param1 param2
echo "Script Name: $0"
echo "Erster Parameter: $1"
echo "Zweiter Parameter: $2"
echo "Alle Parameter: $@"
echo "Anzahl Parameter: $#"
echo "Exit Code letzter Befehl: $?"
echo "Prozess ID: $$"
# Mit Default-Werten
NAME=${1:-"Standardname"}
PORT=${2:-3000}
# Shift - Parameter verschieben
shift # $2 wird zu $1, $3 zu $2, etc.
echo "Nach shift: $1" Umgebungsvariablen
#!/bin/bash
# System-Variablen nutzen
echo "Home: $HOME"
echo "User: $USER"
echo "Path: $PATH"
echo "Shell: $SHELL"
echo "Editor: $EDITOR"
# Eigene exportieren
export API_KEY="secret123"
export NODE_ENV="production"
# Nur für einen Befehl
NODE_ENV=development npm start
# .env Datei laden
if [ -f .env ]; then
export $(cat .env | xargs)
fi Conditionals & Tests
If-Else Statements
#!/bin/bash
# Basis If-Else
if [ "$USER" = "sven" ]; then
echo "Hallo Sven!"
else
echo "Hallo Fremder!"
fi
# Elif
if [ "$1" = "start" ]; then
echo "Starte Server..."
elif [ "$1" = "stop" ]; then
echo "Stoppe Server..."
elif [ "$1" = "restart" ]; then
echo "Restart..."
else
echo "Usage: $0 {start|stop|restart}"
exit 1
fi
# Einzeiler
[ -f "config.json" ] && echo "Config exists" || echo "No config"
# Moderne [[ ]] Syntax (Bash/ZSH)
if [[ $NAME == "Sven" ]]; then
echo "Match!"
fi Test-Operatoren
# Datei-Tests
[ -f file.txt ] # Datei existiert
[ -d folder ] # Verzeichnis existiert
[ -e item ] # Existiert (Datei oder Verzeichnis)
[ -s file.txt ] # Datei existiert und nicht leer
[ -r file.txt ] # Lesbar
[ -w file.txt ] # Schreibbar
[ -x script.sh ] # Ausführbar
[ -L link ] # Symbolischer Link
# String-Vergleiche
[ "$A" = "$B" ] # Gleich
[ "$A" != "$B" ] # Ungleich
[ -z "$A" ] # Leer
[ -n "$A" ] # Nicht leer
# Zahlen-Vergleiche
[ $A -eq $B ] # Equal
[ $A -ne $B ] # Not equal
[ $A -gt $B ] # Greater than
[ $A -ge $B ] # Greater or equal
[ $A -lt $B ] # Less than
[ $A -le $B ] # Less or equal
# Logische Operatoren
[ $A -eq 1 ] && [ $B -eq 2 ] # AND
[ $A -eq 1 ] || [ $B -eq 2 ] # OR
[ ! -f file.txt ] # NOT Loops & Iteration
For Loops
#!/bin/bash
# Über Liste iterieren
for file in *.js; do
echo "Processing: $file"
done
# Über Array
FILES=(app.js index.js test.js)
for file in "${FILES[@]}"; do
echo "File: $file"
done
# Über Bereich (Bash/ZSH)
for i in {1..5}; do
echo "Nummer: $i"
done
# C-Style (Bash)
for ((i=0; i<10; i++)); do
echo "Index: $i"
done
# Über Kommando-Output
for branch in $(git branch -r); do
echo "Remote branch: $branch"
done While & Until
#!/bin/bash
# While Loop
COUNTER=0
while [ $COUNTER -lt 10 ]; do
echo "Count: $COUNTER"
COUNTER=$((COUNTER + 1))
done
# Datei zeilenweise lesen
while IFS= read -r line; do
echo "Zeile: $line"
done < input.txt
# Until Loop (läuft bis Bedingung true)
until [ -f "ready.txt" ]; do
echo "Warte auf ready.txt..."
sleep 1
done
# Endlos-Loop mit Break
while true; do
echo "Running... (Ctrl+C to stop)"
# Mache etwas
[ -f "stop.txt" ] && break
sleep 1
done Funktionen
Funktionen definieren und nutzen
#!/bin/bash
# Einfache Funktion
function greet() {
echo "Hallo $1!"
}
# Alternative Syntax
say_goodbye() {
echo "Tschüss $1!"
}
# Aufruf
greet "Sven"
say_goodbye "Welt"
# Mit Return Value
is_number() {
if [[ $1 =~ ^[0-9]+$ ]]; then
return 0 # true/success
else
return 1 # false/failure
fi
}
# Nutzen
if is_number "42"; then
echo "Ist eine Zahl!"
fi
# Mit Output
get_timestamp() {
echo $(date +%Y%m%d_%H%M%S)
}
# Output in Variable speichern
TIMESTAMP=$(get_timestamp)
echo "Backup_$TIMESTAMP.tar.gz"
# Lokale Variablen
calculate() {
local num1=$1
local num2=$2
local result=$((num1 + num2))
echo $result
}
RESULT=$(calculate 5 3) # 8 🔥 Fortgeschrittene Features: Hier wird's richtig interessant!
Input & Output
User Input
#!/bin/bash
# Einfacher Input
echo "Wie heißt du?"
read NAME
echo "Hallo $NAME!"
# Mit Prompt
read -p "Bitte Port eingeben: " PORT
echo "Nutze Port: $PORT"
# Silent (für Passwörter)
read -s -p "Passwort: " PASSWORD
echo # Neue Zeile nach silent input
# Mit Timeout
read -t 5 -p "Schnell! (5 Sekunden): " ANSWER
# Mit Default
read -p "Environment [development]: " ENV
ENV=${ENV:-development}
# Auswahl-Menü
echo "Wähle eine Option:"
select option in "Start" "Stop" "Restart" "Quit"; do
case $option in
Start) echo "Starting..."; break;;
Stop) echo "Stopping..."; break;;
Restart) echo "Restarting..."; break;;
Quit) exit;;
*) echo "Ungültige Option";;
esac
done Output Redirection
#!/bin/bash
# Stdout in Datei
echo "Log entry" > log.txt # Überschreiben
echo "Another entry" >> log.txt # Anhängen
# Stderr umleiten
command 2> errors.txt # Nur Fehler
command > output.txt 2>&1 # Alles in eine Datei
command &> all.txt # Kurzform für alles
# Stdout und Stderr trennen
command 1> stdout.txt 2> stderr.txt
# Tee - Output in Datei UND Terminal
echo "Important" | tee log.txt
echo "More" | tee -a log.txt # Append
# Here Document
cat << EOF > config.json
{
"name": "$APP_NAME",
"port": $PORT,
"env": "$NODE_ENV"
}
EOF
# Null Device (Output verwerfen)
command > /dev/null 2>&1
# Nur bei Erfolg speichern
command && echo "Success" > success.log Error Handling
Exit Codes & Error Checking
#!/bin/bash
# Exit Codes setzen
exit 0 # Success
exit 1 # General error
exit 2 # Misuse of shell command
exit 127 # Command not found
# Exit Code prüfen
npm build
if [ $? -eq 0 ]; then
echo "Build erfolgreich!"
else
echo "Build fehlgeschlagen!"
exit 1
fi
# Kurzform mit &&/||
npm build && echo "Success" || echo "Failed"
# Set -e: Bei Fehler sofort beenden
set -e
npm install # Script stoppt hier bei Fehler
npm build
npm test
# Set -u: Undefined variables als Fehler
set -u
echo $UNDEFINED_VAR # Fehler!
# Set -o pipefail: Pipe-Fehler beachten
set -eo pipefail
cat missing.txt | grep something # Stoppt bei Fehler
# Trap - Cleanup bei Exit
cleanup() {
echo "Cleaning up..."
rm -f /tmp/tempfile
}
trap cleanup EXIT
# Error Handler
error_handler() {
echo "Error on line $1"
exit 1
}
trap 'error_handler $LINENO' ERR Logging
#!/bin/bash
# Log-Funktion
log() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $@" | tee -a app.log
}
log "Script started"
log "Processing files..."
log "ERROR: File not found" >&2
# Mit Log-Levels
LOG_LEVEL=${LOG_LEVEL:-INFO}
log_debug() {
[ "$LOG_LEVEL" = "DEBUG" ] && echo "[DEBUG] $@" >&2
}
log_info() {
echo "[INFO] $@"
}
log_error() {
echo "[ERROR] $@" >&2
}
# Verwendung
log_debug "Variable X = $X"
log_info "Starting process..."
log_error "Failed to connect!" Praktische Beispiele
Backup Script
#!/bin/bash
# backup.sh - Erstellt timestamped Backups
set -e
# Config
SOURCE_DIR="${1:-$PWD}"
BACKUP_DIR="${2:-$HOME/Backups}"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_NAME="backup_${TIMESTAMP}.tar.gz"
# Backup-Verzeichnis erstellen
mkdir -p "$BACKUP_DIR"
# Backup erstellen
echo "Creating backup of $SOURCE_DIR..."
tar -czf "$BACKUP_DIR/$BACKUP_NAME" \
--exclude='node_modules' \
--exclude='.git' \
--exclude='dist' \
"$SOURCE_DIR"
# Alte Backups löschen (älter als 30 Tage)
echo "Cleaning old backups..."
find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +30 -delete
# Erfolg
echo "✅ Backup created: $BACKUP_NAME"
echo "📁 Location: $BACKUP_DIR"
echo "📊 Size: $(du -h "$BACKUP_DIR/$BACKUP_NAME" | cut -f1)"
# Liste der Backups
echo -e "\nCurrent backups:"
ls -lh "$BACKUP_DIR"/backup_*.tar.gz 2>/dev/null | tail -5 File Watcher
#!/bin/bash
# watch.sh - Überwacht Dateien und führt Befehle aus
# Nutzt fswatch (brew install fswatch)
if ! command -v fswatch &> /dev/null; then
echo "fswatch nicht installiert. Installiere mit: brew install fswatch"
exit 1
fi
# Config
WATCH_DIR="${1:-.}"
WATCH_PATTERN="${2:-*.js}"
COMMAND="${3:-npm run build}"
echo "👀 Watching $WATCH_DIR for changes in $WATCH_PATTERN"
echo "🚀 Will execute: $COMMAND"
echo "Press Ctrl+C to stop"
# Initial build
eval "$COMMAND"
# Watch for changes
fswatch -o "$WATCH_DIR" | while read f; do
clear
echo "📝 Change detected at $(date +%H:%M:%S)"
echo "⚡ Running: $COMMAND"
echo "---"
if eval "$COMMAND"; then
echo "✅ Success!"
else
echo "❌ Failed with exit code $?"
fi
done Project Setup Script
#!/bin/bash
# setup-project.sh - Neues Webprojekt initialisieren
set -e
# Projekt-Name abfragen
read -p "Projekt Name: " PROJECT_NAME
if [ -z "$PROJECT_NAME" ]; then
echo "❌ Projekt Name erforderlich!"
exit 1
fi
# Optionen
read -p "TypeScript verwenden? (y/n): " USE_TS
read -p "Tailwind CSS? (y/n): " USE_TAILWIND
read -p "Git initialisieren? (y/n): " USE_GIT
# Projekt-Verzeichnis erstellen
mkdir -p "$PROJECT_NAME"
cd "$PROJECT_NAME"
# package.json erstellen
cat > package.json << EOF
{
"name": "$PROJECT_NAME",
"version": "1.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
}
}
EOF
# Dependencies installieren
echo "📦 Installing dependencies..."
npm install -D vite
[ "$USE_TS" = "y" ] && npm install -D typescript @types/node
[ "$USE_TAILWIND" = "y" ] && npm install -D tailwindcss postcss autoprefixer
# Basis-Struktur
mkdir -p src public
cat > src/main.js << 'EOF'
console.log('Hello from Vite!');
EOF
cat > index.html << EOF
$PROJECT_NAME
EOF
# Git
if [ "$USE_GIT" = "y" ]; then
git init
echo "node_modules/" > .gitignore
echo "dist/" >> .gitignore
git add .
git commit -m "Initial commit"
fi
echo "✅ Projekt '$PROJECT_NAME' erstellt!"
echo "📁 cd $PROJECT_NAME"
echo "🚀 npm run dev" Build & Deploy Automation
Build Pipeline
#!/bin/bash
# build-pipeline.sh - Komplette Build-Pipeline
set -eo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Functions
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# Timer
START_TIME=$(date +%s)
# Clean
log_info "Cleaning dist folder..."
rm -rf dist
mkdir -p dist
# Install dependencies
if [ ! -d "node_modules" ] || [ package.json -nt node_modules ]; then
log_info "Installing dependencies..."
npm ci
fi
# Lint
log_info "Running linter..."
npm run lint || {
log_warn "Linting failed, continuing..."
}
# Tests
if [ "$SKIP_TESTS" != "true" ]; then
log_info "Running tests..."
npm test
else
log_warn "Skipping tests (SKIP_TESTS=true)"
fi
# Build
log_info "Building application..."
npm run build
# Post-processing
log_info "Optimizing assets..."
find dist -name "*.js" -exec gzip -k {} \;
find dist -name "*.css" -exec gzip -k {} \;
# Generate build info
cat > dist/build-info.json << EOF
{
"version": "$(git describe --tags --always)",
"branch": "$(git branch --show-current)",
"commit": "$(git rev-parse HEAD)",
"buildDate": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"builder": "$USER"
}
EOF
# Time calculation
END_TIME=$(date +%s)
DURATION=$((END_TIME - START_TIME))
log_info "✅ Build completed in ${DURATION}s"
log_info "📁 Output: ./dist"
log_info "📊 Size: $(du -sh dist | cut -f1)" Deploy Script
#!/bin/bash
# deploy.sh - Deploy to server via rsync/ssh
set -e
# Config from environment or defaults
SERVER=${DEPLOY_SERVER:-"user@server.com"}
REMOTE_PATH=${DEPLOY_PATH:-"/var/www/html"}
LOCAL_PATH=${LOCAL_PATH:-"./dist"}
SSH_KEY=${SSH_KEY:-"$HOME/.ssh/id_rsa"}
# Verify build exists
if [ ! -d "$LOCAL_PATH" ]; then
echo "❌ Build folder not found. Run build first!"
exit 1
fi
# Dry run option
if [ "$1" = "--dry-run" ]; then
echo "🔍 Dry run mode..."
DRY_RUN="--dry-run"
fi
# Pre-deploy checks
echo "🔍 Running pre-deploy checks..."
ssh -i "$SSH_KEY" "$SERVER" "df -h $REMOTE_PATH" || {
echo "❌ Cannot connect to server!"
exit 1
}
# Backup on server
echo "📦 Creating backup on server..."
ssh -i "$SSH_KEY" "$SERVER" \
"cd $REMOTE_PATH && tar -czf ../backup_$(date +%Y%m%d_%H%M%S).tar.gz ."
# Deploy
echo "🚀 Deploying to $SERVER..."
rsync -avz --delete \
$DRY_RUN \
--exclude '.DS_Store' \
--exclude '*.map' \
-e "ssh -i $SSH_KEY" \
"$LOCAL_PATH/" \
"$SERVER:$REMOTE_PATH/"
# Post-deploy
if [ -z "$DRY_RUN" ]; then
echo "🔄 Running post-deploy commands..."
ssh -i "$SSH_KEY" "$SERVER" << 'ENDSSH'
# Clear cache
redis-cli FLUSHDB
# Restart services
sudo systemctl reload nginx
# Verify
curl -s -o /dev/null -w "%{http_code}" http://localhost
ENDSSH
fi
echo "✅ Deploy complete!" Git Automation
Git Workflow Automation
#!/bin/bash
# git-flow.sh - Automatisierter Git Workflow
set -e
# Parse command
COMMAND=${1:-help}
BRANCH_NAME=${2:-}
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m'
# Current branch
CURRENT_BRANCH=$(git branch --show-current)
case "$COMMAND" in
feature)
if [ -z "$BRANCH_NAME" ]; then
echo "Usage: $0 feature "
exit 1
fi
echo -e "${BLUE}Creating feature branch...${NC}"
git checkout main
git pull origin main
git checkout -b "feature/$BRANCH_NAME"
echo -e "${GREEN}✅ Feature branch 'feature/$BRANCH_NAME' created${NC}"
;;
finish)
if [[ ! "$CURRENT_BRANCH" =~ ^feature/ ]]; then
echo -e "${RED}Not on a feature branch!${NC}"
exit 1
fi
echo -e "${BLUE}Finishing feature...${NC}"
# Stash uncommitted changes
git stash
# Update main
git checkout main
git pull origin main
# Merge feature
git merge --no-ff "$CURRENT_BRANCH" \
-m "Merge $CURRENT_BRANCH into main"
# Push
git push origin main
# Delete feature branch
git branch -d "$CURRENT_BRANCH"
git push origin --delete "$CURRENT_BRANCH" 2>/dev/null || true
echo -e "${GREEN}✅ Feature merged and cleaned up${NC}"
;;
sync)
echo -e "${BLUE}Syncing with remote...${NC}"
# Fetch all
git fetch --all --prune
# Update current branch
git pull --rebase
# Clean merged branches
git branch --merged main | grep -v main | xargs -n 1 git branch -d 2>/dev/null || true
echo -e "${GREEN}✅ Synced with remote${NC}"
;;
status)
echo -e "${BLUE}=== Git Status Overview ===${NC}"
echo "Branch: $CURRENT_BRANCH"
echo "Remote: $(git remote get-url origin)"
echo ""
# Status
git status -s
# Recent commits
echo -e "\n${BLUE}Recent commits:${NC}"
git log --oneline -5
# Branches
echo -e "\n${BLUE}Local branches:${NC}"
git branch -vv
;;
*)
echo "Git Workflow Helper"
echo "Usage: $0 {feature|finish|sync|status} [options]"
echo ""
echo "Commands:"
echo " feature - Create new feature branch"
echo " finish - Merge current feature to main"
echo " sync - Sync with remote and cleanup"
echo " status - Show comprehensive status"
;;
esac Commit Message Helper
#!/bin/bash
# commit-helper.sh - Strukturierte Commit Messages
set -e
# Commit types
echo "Select commit type:"
select TYPE in "feat" "fix" "docs" "style" "refactor" "test" "chore" "perf"; do
[ -n "$TYPE" ] && break
done
# Scope (optional)
read -p "Scope (optional, e.g., api, ui): " SCOPE
[ -n "$SCOPE" ] && SCOPE="($SCOPE)"
# Description
read -p "Short description: " DESCRIPTION
if [ -z "$DESCRIPTION" ]; then
echo "❌ Description required!"
exit 1
fi
# Body (optional)
echo "Long description (optional, Ctrl+D when done):"
BODY=$(cat)
# Breaking change?
read -p "Breaking change? (y/n): " BREAKING
if [ "$BREAKING" = "y" ]; then
read -p "Describe breaking change: " BREAKING_DESC
FOOTER="BREAKING CHANGE: $BREAKING_DESC"
fi
# Build commit message
COMMIT_MSG="$TYPE$SCOPE: $DESCRIPTION"
[ -n "$BODY" ] && COMMIT_MSG="$COMMIT_MSG\n\n$BODY"
[ -n "$FOOTER" ] && COMMIT_MSG="$COMMIT_MSG\n\n$FOOTER"
# Show preview
echo -e "\n📝 Commit message preview:"
echo -e "---"
echo -e "$COMMIT_MSG"
echo -e "---"
# Confirm
read -p "Commit with this message? (y/n): " CONFIRM
if [ "$CONFIRM" = "y" ]; then
git add -A
echo -e "$COMMIT_MSG" | git commit -F -
echo "✅ Committed!"
else
echo "❌ Aborted"
fi Debugging
Debug-Techniken
#!/bin/bash
# Debug-Modus aktivieren
set -x # Zeigt jeden Befehl vor Ausführung
set -v # Zeigt Script-Zeilen
# Oder beim Aufruf
bash -x script.sh
bash -v script.sh
# Selektives Debugging
DEBUG=${DEBUG:-false}
if [ "$DEBUG" = "true" ]; then
set -x
fi
# Debug-Funktion
debug() {
[ "$DEBUG" = "true" ] && echo "[DEBUG] $@" >&2
}
debug "Variable X = $X"
debug "Entering function foo()"
# Trace-Funktion
trace() {
echo "[TRACE:${BASH_SOURCE[1]}:${BASH_LINENO[0]}:${FUNCNAME[1]}] $@" >&2
}
# Assertions
assert() {
if [ "$1" != "$2" ]; then
echo "Assertion failed: '$1' != '$2'" >&2
echo "Location: ${BASH_SOURCE[1]}:${BASH_LINENO[0]}" >&2
exit 1
fi
}
# Verwendung
RESULT=$(calculate 2 3)
assert "$RESULT" "5"
# Variable Dump
dump_vars() {
echo "=== Variable Dump ==="
echo "PWD: $PWD"
echo "USER: $USER"
echo "PATH: $PATH"
echo "All vars:"
set | grep -E "^[A-Z_]+="
} ShellCheck - Linting für Shell Scripts
# Installation
brew install shellcheck
# Verwendung
shellcheck script.sh
# In VS Code
# Extension: ShellCheck
# Inline-Direktiven
# shellcheck disable=SC2086
echo $VARIABLE_WITHOUT_QUOTES # Absichtlich
# Global ignorieren
# shellcheck disable=SC2086,SC2046
# Strikt prüfen
shellcheck -S error script.sh # Nur Fehler
shellcheck -S warning script.sh # Mit Warnings
shellcheck -S info script.sh # Alles
# Format für CI/CD
shellcheck -f json script.sh Best Practices
Sicheres Scripting
#!/bin/bash
# Best Practice Template
# Strict Mode
set -euo pipefail
IFS=$'\n\t'
# Error handling
error_exit() {
echo "ERROR: $1" >&2
exit "${2:-1}"
}
# Cleanup on exit
cleanup() {
rm -f "$TEMP_FILE"
}
trap cleanup EXIT
# Check dependencies
check_deps() {
local deps=(git node npm)
for cmd in "${deps[@]}"; do
if ! command -v "$cmd" &> /dev/null; then
error_exit "$cmd is not installed"
fi
done
}
# Validate input
validate_input() {
if [ $# -eq 0 ]; then
error_exit "No arguments provided"
fi
if [ ! -f "$1" ]; then
error_exit "File not found: $1"
fi
}
# Main function
main() {
check_deps
validate_input "$@"
# Your code here
echo "Processing..."
}
# Only run main if script is executed (not sourced)
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
main "$@"
fi Performance Tips
#!/bin/bash
# SCHLECHT: Mehrere externe Befehle
cat file.txt | grep pattern | wc -l
# BESSER: Ein Befehl
grep -c pattern file.txt
# SCHLECHT: Schleife mit externem Befehl
for file in *.txt; do
cat "$file" | wc -l
done
# BESSER: Einen Befehl für alle
wc -l *.txt
# SCHLECHT: Backticks
files=`ls *.txt`
# BESSER: $() Syntax
files=$(ls *.txt)
# NOCH BESSER: Glob direkt
files=(*.txt)
# Parallel Processing
# SCHLECHT: Sequenziell
for file in *.jpg; do
convert "$file" "thumb_$file"
done
# BESSER: Parallel mit xargs
ls *.jpg | xargs -P 4 -I {} convert {} thumb_{}
# ODER: GNU Parallel
parallel convert {} thumb_{} ::: *.jpg
# Arrays statt String-Splitting
# SCHLECHT
FILES="file1.txt file2.txt file3.txt"
for f in $FILES; do
echo "$f"
done
# BESSER
FILES=("file1.txt" "file2.txt" "file3.txt")
for f in "${FILES[@]}"; do
echo "$f"
done Großes Cheatsheet
📋 Shell Scripting Cheatsheet
Variablen & Substitution
# Variable Assignment
VAR="value" # String
NUM=42 # Number
ARRAY=(one two three) # Array
declare -r CONST="fixed" # Readonly
declare -i INT=5 # Integer
declare -a ARR # Array
declare -A HASH # Associative array
# Variable Expansion
${VAR} # Value
${VAR:-default} # Default if unset
${VAR:=default} # Set default if unset
${VAR:?error} # Error if unset
${VAR:+alt} # Alt value if set
${#VAR} # Length
${VAR:2:5} # Substring (pos:len)
${VAR#pattern} # Remove from start
${VAR%pattern} # Remove from end
${VAR/old/new} # Replace first
${VAR//old/new} # Replace all
${VAR^} # Uppercase first
${VAR^^} # Uppercase all
${VAR,} # Lowercase first
${VAR,,} # Lowercase all Arrays
# Arrays (Bash 4+)
arr=(a b c) # Create
arr[0]="first" # Set element
${arr[0]} # Get element
${arr[@]} # All elements
${!arr[@]} # All indices
${#arr[@]} # Length
arr+=(d e) # Append
unset arr[1] # Remove element
# Associative Arrays
declare -A hash
hash[key]="value"
${hash[key]} # Get value
${!hash[@]} # All keys
${hash[@]} # All values Conditionals
# File Tests
-e file # Exists
-f file # Regular file
-d dir # Directory
-s file # Not empty
-r file # Readable
-w file # Writable
-x file # Executable
-L link # Symlink
-S file # Socket
-p file # Named pipe
-b file # Block device
-c file # Character device
-t fd # Terminal
# File Comparisons
file1 -nt file2 # Newer than
file1 -ot file2 # Older than
file1 -ef file2 # Same file
# String Tests
-z str # Empty
-n str # Not empty
s1 = s2 # Equal
s1 != s2 # Not equal
s1 < s2 # Less than
s1 > s2 # Greater than
# Numeric Tests
n1 -eq n2 # Equal
n1 -ne n2 # Not equal
n1 -lt n2 # Less than
n1 -le n2 # Less or equal
n1 -gt n2 # Greater than
n1 -ge n2 # Greater or equal Loop Patterns
# For Loops
for i in {1..10}; do ...; done
for i in {1..10..2}; do ...; done # Step 2
for ((i=0; i<10; i++)); do ...; done
for file in *.txt; do ...; done
for arg in "$@"; do ...; done
# While/Until
while read line; do ...; done < file
while true; do ...; break; done
until [ condition ]; do ...; done
# Process substitution
while read line; do
echo "$line"
done < <(command)
# Parallel execution
for i in {1..10}; do
command &
done
wait # Wait for all Functions
# Function definition
func() {
local var=$1
echo "Result"
return 0
}
# Call function
result=$(func arg1 arg2)
# Check return value
if func; then
echo "Success"
fi
# Pass arrays
func() {
local -n arr=$1 # Name reference
echo "${arr[@]}"
}
myarr=(1 2 3)
func myarr 🛠️ Useful One-Liners
File Operations
# Find and replace in files
find . -name "*.txt" -exec sed -i '' 's/old/new/g' {} \;
# Delete empty directories
find . -type d -empty -delete
# Find large files
find . -type f -size +100M
# Count lines of code
find . -name "*.js" -exec wc -l {} \; | awk '{sum+=$1} END {print sum}'
# Backup with timestamp
tar czf backup_$(date +%Y%m%d_%H%M%S).tar.gz folder/
# Batch rename
for f in *.jpeg; do mv "$f" "${f%.jpeg}.jpg"; done
# Create multiple directories
mkdir -p project/{src,dist,test}/{js,css,img}
# Watch file changes
while true; do clear; ls -la; sleep 1; done Text Processing
# Extract column
cut -d',' -f2 file.csv
# Sort unique
sort -u file.txt
# Count occurrences
sort file.txt | uniq -c | sort -rn
# JSON pretty print
cat file.json | python -m json.tool
# Extract between patterns
sed -n '/START/,/END/p' file.txt
# Remove empty lines
sed '/^$/d' file.txt
# Replace tabs with spaces
expand -t 4 file.txt
# Add line numbers
nl file.txt
cat -n file.txt Network & System
# Port in use?
lsof -i :3000
netstat -an | grep 3000
# Kill process on port
kill -9 $(lsof -t -i:3000)
# Monitor file
tail -f log.txt
# System info
uname -a # All info
sw_vers # macOS version
system_profiler # Detailed info
# Disk usage
du -sh * # Summary
df -h # Free space
ncdu # Interactive
# Process tree
pstree
ps aux | grep node
# Network test
ping -c 5 google.com
curl -I https://example.com
traceroute google.com 🎯 Git Aliases for Shell
# Add to ~/.gitconfig or ~/.zshrc
# Quick status
alias gs='git status -sb'
# Pretty log
alias gl='git log --graph --pretty=format:"%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset" --abbrev-commit'
# Quick commit
alias gc='git commit -m'
alias gca='git commit -am'
# Branch management
alias gb='git branch'
alias gco='git checkout'
alias gcb='git checkout -b'
# Stash helpers
alias gst='git stash'
alias gstp='git stash pop'
alias gstl='git stash list'
# Undo helpers
alias gundo='git reset HEAD~1 --soft'
alias greset='git reset --hard HEAD'
# Diff helpers
alias gd='git diff'
alias gds='git diff --staged'
# Remote
alias gp='git push'
alias gpu='git push -u origin $(git branch --show-current)'
alias gpl='git pull --rebase'
# Cleanup
alias gclean='git branch --merged | grep -v "\*\|main\|master" | xargs -n 1 git branch -d' ⚡ ZSH Specific Features
# ZSH Arrays (1-indexed!)
arr=(a b c)
echo $arr[1] # 'a' (nicht 0!)
# Globbing
**/*.js # Recursive
*.{js,ts} # Multiple extensions
*~*.bak # Exclude pattern
*(.) # Regular files only
*(/) # Directories only
*(*) # Executables only
*(@) # Symlinks only
# Modifiers
$file:t # Basename
$file:h # Directory
$file:e # Extension
$file:r # Remove extension
$file:u # Uppercase
$file:l # Lowercase
# History expansion
!! # Last command
!$ # Last argument
!^ # First argument
!* # All arguments
!-2 # Two commands ago
# Parameter expansion
${+VAR} # 1 if set, 0 if not
${VAR:#pattern} # Remove matching
${(j:,:)array} # Join with comma
${(s:,:)string} # Split by comma
${(U)VAR} # Uppercase
${(L)VAR} # Lowercase 📚 Script Template Collection
Minimal Script
#!/usr/bin/env bash
set -euo pipefail
# Your code here Full Featured Script
#!/usr/bin/env bash
#
# script-name.sh - Description
# Usage: script-name.sh [options] arguments
#
# Author: Your Name
# Version: 1.0.0
set -euo pipefail
IFS=$'\n\t'
# --- Constants ---
readonly SCRIPT_NAME=$(basename "$0")
readonly SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
readonly VERSION="1.0.0"
# --- Options ---
DEBUG=${DEBUG:-false}
VERBOSE=${VERBOSE:-false}
DRY_RUN=${DRY_RUN:-false}
# --- Colors ---
if [[ -t 1 ]]; then
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
else
RED=''
GREEN=''
YELLOW=''
BLUE=''
NC=''
fi
# --- Functions ---
usage() {
cat << EOF
Usage: $SCRIPT_NAME [OPTIONS] ARGUMENTS
Description of what this script does.
OPTIONS:
-h, --help Show this help message
-v, --version Show version
-d, --debug Enable debug mode
-n, --dry-run Dry run mode
-V, --verbose Verbose output
EXAMPLES:
$SCRIPT_NAME file.txt
$SCRIPT_NAME --debug --verbose file.txt
EOF
exit 0
}
log() {
echo -e "${GREEN}[INFO]${NC} $*"
}
warn() {
echo -e "${YELLOW}[WARN]${NC} $*" >&2
}
error() {
echo -e "${RED}[ERROR]${NC} $*" >&2
exit 1
}
debug() {
if [[ "$DEBUG" == "true" ]]; then
echo -e "${BLUE}[DEBUG]${NC} $*" >&2
fi
}
cleanup() {
debug "Cleaning up..."
# Cleanup code here
}
# --- Main ---
main() {
# Parse options
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
usage
;;
-v|--version)
echo "$SCRIPT_NAME version $VERSION"
exit 0
;;
-d|--debug)
DEBUG=true
set -x
shift
;;
-n|--dry-run)
DRY_RUN=true
shift
;;
-V|--verbose)
VERBOSE=true
shift
;;
-*)
error "Unknown option: $1"
;;
*)
break
;;
esac
done
# Validate arguments
if [[ $# -eq 0 ]]; then
error "No arguments provided. Use -h for help."
fi
# Setup trap
trap cleanup EXIT INT TERM
# Main logic
log "Starting $SCRIPT_NAME..."
# Your code here
log "Done!"
}
# Run main only if executed directly
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi