10 Arbeit mit Dateien und Verzeichnissen

10.1 Datei- und Verzeichnisoperationen in Skripten

Effiziente Skripte für die Systemadministration und Automatisierung erfordern häufig komplexe Operationen mit Dateien und Verzeichnissen. In diesem Abschnitt behandeln wir fortgeschrittene Techniken für den Umgang mit Dateisystemobjekten in Shell-Skripten.

10.1.1 Pfade und Pfadvariablen

In Bash-Skripten ist die präzise Arbeit mit Pfaden essenziell. Eine gängige Praxis ist die Verwendung von Variablen zur Speicherung von Pfaden:

# Pfade als Variablen definieren
BASE_DIR="/var/log"
BACKUP_DIR="/mnt/backup/logs"
LOG_FILE="$BASE_DIR/system.log"

10.1.1.1 Absolute vs. relative Pfade

Bei der Arbeit mit Pfaden müssen Sie zwischen absoluten und relativen Pfaden unterscheiden:

# Absoluter Pfad - beginnt mit /
cd /var/log

# Relativer Pfad - relativ zum aktuellen Verzeichnis
cd ../config

10.1.1.2 Arbeiten mit dem aktuellen Verzeichnis

Folgende Möglichkeiten bestehen, um das aktuelle Verzeichnis in Skripten zu referenzieren:

# Aktuelles Verzeichnis ermitteln
CURRENT_DIR=$(pwd)
# Alternative Methode
CURRENT_DIR="$PWD"

# Das Verzeichnis, in dem das Skript liegt (nicht das Arbeitsverzeichnis)
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

Der Unterschied zwischen PWD und dem Verzeichnis des Skripts ist wichtig, besonders wenn Skripte von anderen Verzeichnissen aus aufgerufen werden.

10.1.2 Prüfen von Datei- und Verzeichniseigenschaften

Bash bietet spezielle Testoperatoren zur Überprüfung von Dateisystemobjekten:

# Prüfen, ob eine Datei existiert
if [ -f "$FILENAME" ]; then
    echo "Die Datei $FILENAME existiert."
fi

# Prüfen, ob ein Verzeichnis existiert
if [ -d "$DIRNAME" ]; then
    echo "Das Verzeichnis $DIRNAME existiert."
fi

# Prüfen, ob eine Datei lesbar ist
if [ -r "$FILENAME" ]; then
    echo "Die Datei $FILENAME ist lesbar."
fi

# Prüfen, ob eine Datei schreibbar ist
if [ -w "$FILENAME" ]; then
    echo "Die Datei $FILENAME ist schreibbar."
fi

# Prüfen, ob eine Datei ausführbar ist
if [ -x "$FILENAME" ]; then
    echo "Die Datei $FILENAME ist ausführbar."
fi

# Prüfen, ob eine Datei nicht leer ist
if [ -s "$FILENAME" ]; then
    echo "Die Datei $FILENAME ist nicht leer."
fi

Eine vollständige Übersicht der Testoperatoren:

Operator Beschreibung
-e DATEI Wahr, wenn die Datei existiert
-f DATEI Wahr, wenn die Datei existiert und eine reguläre Datei ist
-d VERZEICHNIS Wahr, wenn das Verzeichnis existiert
-s DATEI Wahr, wenn die Datei existiert und größer als 0 Byte ist
-r DATEI Wahr, wenn die Datei lesbar ist
-w DATEI Wahr, wenn die Datei schreibbar ist
-x DATEI Wahr, wenn die Datei ausführbar ist
-L DATEI Wahr, wenn die Datei ein symbolischer Link ist

10.1.3 Datei- und Verzeichniserstellung

Skripte müssen oft Verzeichnisstrukturen erstellen oder sicherstellen, dass bestimmte Dateien existieren:

# Ein Verzeichnis erstellen, falls es nicht existiert
if [ ! -d "$LOG_DIR" ]; then
    mkdir -p "$LOG_DIR"
    echo "Verzeichnis $LOG_DIR wurde erstellt."
fi

# Mehrere Verzeichnisebenen auf einmal erstellen
mkdir -p /pfad/zu/verschachteltem/verzeichnis

# Eine leere Datei erstellen (oder Zeitstempel aktualisieren, wenn sie existiert)
touch "$LOG_FILE"

# Eine Datei mit Inhalt erstellen
cat > "$CONFIG_FILE" << EOF
# Konfigurationseinstellungen
DEBUG=true
LOG_LEVEL=info
MAX_CONNECTIONS=100
EOF

10.1.4 Dateien und Verzeichnisse kopieren, verschieben und löschen

# Dateien kopieren mit Erhaltung der Attribute
cp -p "$SOURCE_FILE" "$DEST_FILE"

# Verzeichnisse rekursiv kopieren
cp -r "$SOURCE_DIR" "$DEST_DIR"

# Datei verschieben/umbenennen
mv "$OLD_NAME" "$NEW_NAME"

# Datei sicher löschen (mit Nachfrage)
rm -i "$FILENAME"

# Verzeichnis rekursiv löschen (Vorsicht!)
rm -rf "$DIRNAME"

10.1.4.1 Sicherheitshinweise

Beim Löschen von Dateien und Verzeichnissen ist besondere Vorsicht geboten:

# GEFÄHRLICH - Variablen könnten leer sein!
rm -rf $DIR/  # Wenn $DIR leer ist, wird / gelöscht!

# SICHER - Prüfung, ob die Variable nicht leer ist
if [ -n "$DIR" ] && [ -d "$DIR" ]; then
    rm -rf "$DIR"
fi

# SICHER - Durch Anführungszeichen und Vorprüfung
[ -d "$DIR" ] && rm -rf "$DIR"

10.1.5 Umgang mit Dateipfaden und Dateinamen

Bash bietet verschiedene Methoden zur Manipulation von Dateipfaden:

FULLPATH="/var/log/syslog.1.gz"

# Dateiname ohne Pfad extrahieren
FILENAME=$(basename "$FULLPATH")  # Ergebnis: syslog.1.gz

# Verzeichnispfad extrahieren
DIRECTORY=$(dirname "$FULLPATH")  # Ergebnis: /var/log

# Dateiendung entfernen
BASENAME="${FILENAME%.*}"         # Ergebnis: syslog.1

# Alles nach dem ersten Punkt entfernen
NAME="${FILENAME%%.*}"            # Ergebnis: syslog

# Endung extrahieren
EXTENSION="${FILENAME##*.}"       # Ergebnis: gz

10.1.6 Arbeiten mit temporären Dateien und Verzeichnissen

Für vorübergehende Daten sollten temporäre Dateien verwendet werden:

# Temporäre Datei erstellen
TEMP_FILE=$(mktemp)
echo "Temporäre Daten" > "$TEMP_FILE"
# Mit der Datei arbeiten...
rm "$TEMP_FILE"  # Aufräumen nicht vergessen

# Temporäres Verzeichnis erstellen
TEMP_DIR=$(mktemp -d)
# Mit dem Verzeichnis arbeiten...
rm -rf "$TEMP_DIR"  # Aufräumen

Der Befehl mktemp erstellt standardmäßig Dateien im System-Temp-Verzeichnis (üblicherweise /tmp) mit einzigartigen Namen.

10.1.7 Dateien suchen und filtern

Der find-Befehl ist ein leistungsstarkes Werkzeug zur Suche und Verarbeitung von Dateien:

# Alle Logdateien in /var/log finden, die älter als 7 Tage sind
find /var/log -name "*.log" -type f -mtime +7

# Alle leeren Dateien im Home-Verzeichnis finden und löschen
find ~ -type f -empty -delete

# Alle ausführbaren Dateien in /usr/bin finden
find /usr/bin -type f -executable

# Nach Dateigröße suchen (größer als 100MB)
find /home -type f -size +100M

# Dateien nach Inhaltstyp suchen
find /var/www -type f -name "*.php" -exec grep -l "mysql_connect" {} \;

10.1.8 Ausführen von Befehlen für gefundene Dateien

Mit find und der -exec-Option können Befehle für jede gefundene Datei ausgeführt werden:

# Alle Textdateien in UTF-8 konvertieren
find . -name "*.txt" -exec iconv -f ISO-8859-1 -t UTF-8 {} -o {}.utf8 \;

# Berechtigungen für alle Skripte in einem Verzeichnis ändern
find ./scripts -name "*.sh" -exec chmod 755 {} \;

# Alle gefundenen Dateien komprimieren
find ./logs -name "*.log" -mtime +30 -exec gzip {} \;

Eine effizientere Alternative zu -exec ... \; ist -exec ... \+, die mehrere Dateinamen an einen einzigen Befehlsaufruf übergibt:

# Viele Dateien auf einmal löschen (effizienter)
find /tmp -name "temp*" -mtime +7 -exec rm {} \+

10.1.9 Dateiinhalte verarbeiten

# Zeilen aus einer Datei lesen und verarbeiten
while IFS= read -r line; do
    echo "Verarbeite: $line"
    # Weitere Verarbeitung...
done < "$INPUT_FILE"

# Bestimmte Zeilen auswählen
sed -n '10,20p' "$FILE"  # Zeilen 10-20 ausgeben

# Text in einer Datei ersetzen
sed -i 's/alter_text/neuer_text/g' "$FILE"

# Zeilen hinzufügen
echo "Neue Zeile" >> "$FILE"

10.1.10 Dateisysteminformationen abrufen

# Freien Speicherplatz prüfen
SPACE_AVAILABLE=$(df -k /var/log | awk 'NR==2 {print $4}')
if [ "$SPACE_AVAILABLE" -lt 1048576 ]; then  # Weniger als 1GB
    echo "Warnung: Wenig Speicherplatz verfügbar!"
fi

# Dateigröße ermitteln
FILE_SIZE=$(stat -c %s "$FILENAME")
echo "Die Datei $FILENAME ist $FILE_SIZE Bytes groß."

# Zeitstempel der letzten Änderung abrufen
LAST_MODIFIED=$(stat -c %Y "$FILENAME")
echo "Die Datei wurde zuletzt am $(date -d @$LAST_MODIFIED) geändert."

10.1.11 Beispiel: Backup-Skript

Hier ist ein praktisches Beispiel, das viele der vorgestellten Konzepte kombiniert:

#!/bin/bash
# backup.sh - Ein einfaches Backup-Skript

# Konfiguration
SOURCE_DIR="/home/user/documents"
BACKUP_DIR="/mnt/backup"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/backup_$TIMESTAMP.tar.gz"

# Prüfen, ob Quellverzeichnis existiert
if [ ! -d "$SOURCE_DIR" ]; then
    echo "Fehler: Quellverzeichnis $SOURCE_DIR existiert nicht!"
    exit 1
fi

# Prüfen, ob Backup-Verzeichnis existiert, sonst erstellen
if [ ! -d "$BACKUP_DIR" ]; then
    mkdir -p "$BACKUP_DIR"
    if [ $? -ne 0 ]; then
        echo "Fehler: Konnte Backup-Verzeichnis nicht erstellen!"
        exit 2
    fi
fi

# Freien Speicherplatz prüfen
SPACE_NEEDED=$(du -s "$SOURCE_DIR" | awk '{print $1}')
SPACE_AVAILABLE=$(df -k "$BACKUP_DIR" | awk 'NR==2 {print $4}')

if [ "$SPACE_AVAILABLE" -lt "$SPACE_NEEDED" ]; then
    echo "Fehler: Nicht genug Speicherplatz für Backup!"
    exit 3
fi

# Backup erstellen
echo "Erstelle Backup von $SOURCE_DIR nach $BACKUP_FILE..."
tar -czf "$BACKUP_FILE" -C "$(dirname "$SOURCE_DIR")" "$(basename "$SOURCE_DIR")"

if [ $? -eq 0 ]; then
    echo "Backup erfolgreich erstellt: $BACKUP_FILE"
    # Alte Backups aufräumen (älter als 30 Tage)
    find "$BACKUP_DIR" -name "backup_*.tar.gz" -type f -mtime +30 -delete
    echo "Alte Backups wurden aufgeräumt."
else
    echo "Fehler beim Erstellen des Backups!"
    exit 4
fi

exit 0

10.2 Wildcards und Globbing-Muster

Beim Arbeiten mit Dateien und Verzeichnissen sind Wildcards und Globbing-Muster unverzichtbare Werkzeuge für Shell-Skripte. Sie ermöglichen es, mehrere Dateien mit einem einzigen Befehl zu finden und zu manipulieren, indem Muster anstelle von exakten Dateinamen verwendet werden. Dieser Abschnitt behandelt die verschiedenen Arten von Globbing-Mustern und deren effektive Anwendung in Shell-Skripten.

10.2.1 Grundlegende Wildcards

Die Bash-Shell bietet drei grundlegende Wildcards:

Wildcard Beschreibung Beispiel
* Passt auf null oder mehr beliebige Zeichen *.txt passt auf alle Dateien mit der Endung .txt
? Passt auf genau ein beliebiges Zeichen file?.txt passt auf file1.txt, fileA.txt, aber nicht file10.txt
[...] Passt auf ein einzelnes Zeichen aus einer Menge file[123].txt passt nur auf file1.txt, file2.txt und file3.txt

10.2.1.1 Der Stern-Operator (*)

Der Stern ist die am häufigsten verwendete Wildcard. Er passt auf eine beliebige Anzahl von Zeichen (auch null):

# Alle Dateien im aktuellen Verzeichnis auflisten
ls *

# Alle Textdateien auflisten
ls *.txt

# Alle Dateien, die mit 'log' beginnen
ls log*

# Alle Dateien mit 'config' irgendwo im Namen
ls *config*

# Alle Dateien, die mit 'data' beginnen und mit '.csv' enden
ls data*.csv

10.2.1.2 Der Fragezeichen-Operator (?)

Das Fragezeichen steht für genau ein beliebiges Zeichen:

# Dateien mit einem Zeichen nach 'file' auflisten
ls file?.txt      # Passt auf file1.txt, fileA.txt, etc.

# Dateien mit drei Zeichen nach 'log' auflisten
ls log???.txt     # Passt auf log001.txt, logABC.txt, etc.

# Dateien mit genau einem Zeichen vor der Erweiterung
ls file.?         # Passt auf file.a, file.1, etc.

10.2.1.3 Zeichenklassen ([...])

Eckige Klammern definieren eine Menge von Zeichen, wobei ein einzelnes Zeichen aus dieser Menge übereinstimmen muss:

# Dateien, deren Namen mit einer Ziffer von 1 bis 5 enden
ls file[12345]

# Dateien, deren Namen mit einem Buchstaben von a bis e enden
ls file[a-e]

# Dateien, deren Namen mit einem Kleinbuchstaben oder einer Ziffer enden
ls file[a-z0-9]

# Dateien, deren Namen mit allem außer x, y oder z enden
ls file[^xyz]
ls file[!xyz]     # Alternative Syntax

10.2.2 Erweiterte Globbing-Muster

Die Bash bietet auch erweiterte Globbing-Muster, die jedoch standardmäßig deaktiviert sind. Sie müssen aktiviert werden mit:

# Erweiterte Globbing-Muster aktivieren
shopt -s extglob

Mit aktiviertem erweiterten Globbing stehen folgende Muster zur Verfügung:

Muster Beschreibung
?(muster) Null oder eine Übereinstimmung
*(muster) Null oder beliebig viele Übereinstimmungen
+(muster) Eine oder beliebig viele Übereinstimmungen
@(muster) Genau eine Übereinstimmung
!(muster) Alles außer dem angegebenen Muster

Beispiele für erweiterte Globbing-Muster:

# Aktivieren des erweiterten Globbings
shopt -s extglob

# Alle Dateien außer .txt-Dateien
ls !(*.txt)

# Dateien, die entweder .jpg oder .png sind
ls *@(.jpg|.png)

# Dateien, die mit log beginnen und optional mit .txt oder .log enden
ls log*?(.txt|.log)

# Dateien, die mit einer oder mehr Ziffern beginnen
ls +([0-9])*

10.2.3 Globbing in Shell-Skripten

Globbing wird häufig in Shell-Skripten verwendet, um Gruppen von Dateien zu verarbeiten:

#!/bin/bash
# Beispiel: Alle .txt-Dateien in .md-Dateien konvertieren

# Prüfen, ob .txt-Dateien existieren
txt_files=(*.txt)
if [ ${#txt_files[@]} -eq 0 ] || [ ! -e "${txt_files[0]}" ]; then
    echo "Keine .txt-Dateien gefunden!"
    exit 1
fi

# Alle .txt-Dateien durchlaufen und in .md umwandeln
for file in *.txt; do
    # Neuen Dateinamen erstellen (Endung ändern)
    new_file="${file%.txt}.md"
    
    # Datei konvertieren (hier nur einfaches Kopieren)
    cp "$file" "$new_file"
    echo "Konvertiert: $file -> $new_file"
done

echo "Konvertierung abgeschlossen."

10.2.3.1 Vorsichtsmaßnahmen bei Globbing in Skripten

Beim Umgang mit Wildcards in Skripten gibt es wichtige Feinheiten zu beachten:

  1. Umgang mit leeren Ergebnissen: Wenn ein Globbing-Muster keine Dateien findet, wird das Muster selbst als Argument verwendet, was zu unerwarteten Fehlern führen kann.

    # Problematisch, wenn keine .csv-Dateien existieren
    for file in *.csv; do
        # Bei keiner Übereinstimmung wird die Schleife mit file="*.csv" durchlaufen
        echo "Verarbeite $file"
    done
    
    # Besserer Ansatz
    shopt -s nullglob  # Leere Ausgabe bei keiner Übereinstimmung
    for file in *.csv; do
        echo "Verarbeite $file"
    done
    shopt -u nullglob  # Zurücksetzen der Option
    
    # Alternative Prüfung
    if compgen -G "*.csv" > /dev/null; then
        for file in *.csv; do
            echo "Verarbeite $file"
        done
    else
        echo "Keine CSV-Dateien gefunden."
    fi
  2. Umgang mit Dateinamen, die Leerzeichen enthalten: Globbing und Leerzeichen können tückisch sein:

    # Problematisch bei Dateinamen mit Leerzeichen
    for file in *.txt; do
        mv $file ${file}.bak  # FEHLER bei Dateinamen mit Leerzeichen!
    done
    
    # Korrekt mit Anführungszeichen
    for file in *.txt; do
        mv "$file" "${file}.bak"
    done
  3. Rekursives Globbing: Die Bash bietet mit der globstar-Option auch rekursives Globbing:

    # Rekursives Globbing aktivieren
    shopt -s globstar
    
    # Alle .txt-Dateien in allen Unterverzeichnissen finden
    for file in **/*.txt; do
        echo "Gefunden: $file"
    done
    
    # Globstar wieder deaktivieren
    shopt -u globstar

10.2.4 Anwenden von Globbing mit anderen Befehlen

Globbing-Muster lassen sich mit vielen Befehlen kombinieren:

# Alle .log-Dateien in allen Unterverzeichnissen nach einem Fehler durchsuchen
shopt -s globstar
grep -l "ERROR" **/*.log

# Alle .txt-Dateien außer README.txt löschen
rm -f !(README).txt

# Alle Dateien außer .git-Verzeichnis in ein Archiv packen
tar -czf backup.tar.gz !(*.git|*.git/*|backup.tar.gz)

10.2.5 Brace Expansion

Die Bash unterstützt auch die sogenannte “Brace Expansion” (Klammerexpansion), die zwar kein Globbing im eigentlichen Sinne ist, aber ähnlich verwendet werden kann:

# Mehrere Dateien auf einmal erstellen
touch file{1..5}.txt  # Erstellt file1.txt bis file5.txt

# Mit führenden Nullen
touch image{01..10}.jpg  # Erstellt image01.jpg bis image10.jpg

# Mit Schrittweite
echo {10..20..2}  # Ausgabe: 10 12 14 16 18 20

# Kombinationen von Texten
echo file{A,B,C}.txt  # Ausgabe: fileA.txt fileB.txt fileC.txt

# Verschachtelte Braces
echo {a,b{1,2},c}.txt  # Ausgabe: a.txt b1.txt b2.txt c.txt

Brace Expansion kann mit Globbing kombiniert werden:

# Alle .txt und .md Dateien kopieren
cp ./*.{txt,md} /backup/

# Mehrere Verzeichnisse auf einmal erstellen
mkdir -p ./logs/{app,system,error}/{2023,2024}/{01..12}

10.2.6 Unterschied zwischen Globbing und regulären Ausdrücken

Obwohl Globbing-Muster ähnlich wie reguläre Ausdrücke aussehen, gibt es wichtige Unterschiede:

  1. Syntax: Globbing verwendet *, ? und [...] mit einer anderen Semantik als RegEx.
  2. Zweck: Globbing ist für Dateinamen gedacht, RegEx für Textsuche.
  3. Komplexität: RegEx sind mächtiger und komplexer als Globbing-Muster.

Hier ein Vergleich:

Operation Globbing Regulärer Ausdruck
Beliebige Zeichen * .*
Ein Zeichen ? .
Zeichenklasse [abc] [abc]
Zeichenbereich [a-z] [a-z]
Negation [!abc] [^abc]
Gruppierung N/A (...)
Alternative N/A (ohne extglob) a\|b

10.2.7 Beispiel: Einpacken von Projektdateien mit Ausschlüssen

Hier ist ein praktisches Beispiel für ein Skript, das Projektdateien archiviert, aber bestimmte Dateitypen und Verzeichnisse ausschließt:

#!/bin/bash
# pack_project.sh - Projektdateien archivieren mit Ausschlüssen

PROJECT_DIR="$1"
OUTPUT_FILE="${2:-project_backup.tar.gz}"

if [ -z "$PROJECT_DIR" ] || [ ! -d "$PROJECT_DIR" ]; then
    echo "Fehler: Bitte gültiges Projektverzeichnis angeben."
    echo "Verwendung: $0 <projektverzeichnis> [ausgabedatei]"
    exit 1
fi

# In das Projektverzeichnis wechseln
cd "$PROJECT_DIR" || exit 2

# Erweiterte Globbing-Muster aktivieren
shopt -s extglob

# Dateien und Verzeichnisse, die ausgeschlossen werden sollen
EXCLUDE_PATTERN="@(*/.git|*/node_modules|*/build|*/dist|*/.DS_Store|*/*.log|*/*.tmp)"

# Temporäre Datei für die Liste der einzuschließenden Dateien
INCLUDE_LIST=$(mktemp)

# Alle Dateien außer den ausgeschlossenen finden
find . -type f -not -path "$EXCLUDE_PATTERN" > "$INCLUDE_LIST"

# Archiv erstellen
echo "Erstelle Archiv $OUTPUT_FILE..."
tar -czf "../$OUTPUT_FILE" -T "$INCLUDE_LIST"

# Aufräumen
rm "$INCLUDE_LIST"

echo "Fertig! Archiv wurde erstellt: $(realpath "../$OUTPUT_FILE")"
echo "Anzahl der archivierten Dateien: $(tar -tf "../$OUTPUT_FILE" | wc -l)"

exit 0

10.3 Rekursive Operationen im Dateisystem

Bei der Arbeit mit komplexen Verzeichnisstrukturen ist die Fähigkeit, rekursive Operationen durchzuführen, essenziell für effizientes Shell-Scripting. Rekursive Operationen ermöglichen es, Aktionen auf Dateien und Verzeichnisse in allen Unterverzeichnissen anzuwenden, ohne dass jedes Verzeichnis manuell angegeben werden muss. Dieser Abschnitt behandelt verschiedene Methoden und Werkzeuge für rekursive Operationen im Linux-Dateisystem.

10.3.1 Der find-Befehl für rekursive Operationen

Der find-Befehl ist das mächtigste Werkzeug für rekursive Operationen im Dateisystem:

# Grundlegende Syntax
find [Startverzeichnis] [Ausdrücke]

Der find-Befehl durchsucht standardmäßig rekursiv alle Unterverzeichnisse und ist daher ideal für Operationen, die die gesamte Verzeichnisstruktur betreffen.

10.3.1.1 Beispiele für rekursive Suchen

# Alle .log-Dateien im gesamten /var/log-Verzeichnis finden
find /var/log -type f -name "*.log"

# Alle leeren Verzeichnisse finden
find /home/user -type d -empty

# Alle Dateien über 100MB finden
find /var -type f -size +100M

# Alle Dateien, die in den letzten 7 Tagen geändert wurden
find /home/user/documents -type f -mtime -7

10.3.1.2 Rekursives Ausführen von Befehlen mit find

Der find-Befehl kann Aktionen auf gefundene Dateien ausführen, was ihn besonders nützlich für rekursive Operationen macht:

# Alle .php-Dateien rekursiv nach einem Muster durchsuchen
find /var/www -name "*.php" -exec grep -l "mysql_connect" {} \;

# Alle .tmp-Dateien rekursiv löschen
find /tmp -name "*.tmp" -exec rm -f {} \;

# Alle Verzeichnisse mit bestimmten Berechtigungen finden und ändern
find /opt -type d -perm 777 -exec chmod 755 {} \;

# Komprimieren aller Textdateien, die älter als 30 Tage sind
find /var/log -name "*.txt" -type f -mtime +30 -exec gzip {} \;
10.3.1.2.1 Effizienz bei rekursiven Operationen mit -exec ... \+

Bei mehreren Dateien ist die Verwendung von -exec ... \+ anstelle von -exec ... \; effizienter, da mehrere Dateien in einem einzigen Befehlsaufruf verarbeitet werden:

# Ineffizient: Ein Befehlsaufruf pro Datei
find /var/log -name "*.old" -exec rm {} \;

# Effizienter: Mehrere Dateien pro Befehlsaufruf
find /var/log -name "*.old" -exec rm {} \+
10.3.1.2.2 Parallele Ausführung mit xargs

Für noch mehr Effizienz, besonders bei CPU-intensiven Operationen, kann xargs mit der Option -P verwendet werden, um Befehle parallel auszuführen:

# Parallelisierte Komprimierung von Logdateien (4 Prozesse gleichzeitig)
find /var/log -name "*.log" -type f -print0 | xargs -0 -P 4 -I {} gzip {}

10.3.1.3 Tiefenbegrenzung bei rekursiven Operationen

Bei sehr tiefen Verzeichnisstrukturen kann es sinnvoll sein, die Rekursionstiefe zu begrenzen:

# Nur bis zur Tiefe 2 suchen (aktuelles Verzeichnis = Tiefe 0)
find /home/user -maxdepth 2 -name "*.conf"

# Erst ab Tiefe 2 beginnen
find /var -mindepth 2 -name "*.pid"

10.3.2 Rekursive Dateioperationen mit nativen Befehlen

Einige Standardbefehle unterstützen rekursive Operationen direkt durch spezielle Optionen:

10.3.2.1 Rekursives Kopieren mit cp

# Rekursives Kopieren eines Verzeichnisses mit allen Unterverzeichnissen
cp -r /quellverzeichnis /zielverzeichnis

# Rekursives Kopieren mit Erhaltung von Attributen
cp -a /quellverzeichnis /zielverzeichnis

Die Option -a (archivieren) ist eine Kombination aus -r (rekursiv), -p (Erhaltung von Dateieigenschaften) und weiteren Optionen und wird oft für Backups verwendet.

10.3.2.2 Rekursives Löschen mit rm

# Rekursives Löschen eines Verzeichnisses und aller Inhalte
rm -r /zu/löschendes/verzeichnis

# Rekursives Löschen ohne Nachfrage (Vorsicht!)
rm -rf /zu/löschendes/verzeichnis

Beim rekursiven Löschen ist äußerste Vorsicht geboten. Es empfiehlt sich, zusätzliche Sicherheitsmaßnahmen zu implementieren:

#!/bin/bash
# Sicheres rekursives Löschen

TARGET_DIR="$1"

# Sicherheitsprüfungen
if [ -z "$TARGET_DIR" ]; then
    echo "Fehler: Kein Zielverzeichnis angegeben." >&2
    exit 1
fi

if [ "$TARGET_DIR" = "/" ] || [ "$TARGET_DIR" = "/home" ] || [ "$TARGET_DIR" = "/etc" ]; then
    echo "Fehler: Löschen kritischer Systemverzeichnisse nicht erlaubt." >&2
    exit 2
fi

# Bestätigung vom Benutzer einholen
echo "Soll das Verzeichnis '$TARGET_DIR' mit ALLEN Inhalten gelöscht werden? (j/N)"
read -r response
if [[ ! "$response" =~ ^[jJ] ]]; then
    echo "Abgebrochen."
    exit 0
fi

# Rekursives Löschen mit Ausgabe der gelöschten Dateien
find "$TARGET_DIR" -type f -print -delete
find "$TARGET_DIR" -type d -empty -print -delete

echo "Löschvorgang abgeschlossen."

10.3.2.3 Rekursive Berechtigungsänderungen mit chmod

# Rekursives Ändern von Berechtigungen
chmod -R 755 /verzeichnis

# Berechtigungen nur für Verzeichnisse rekursiv ändern (mit find)
find /verzeichnis -type d -exec chmod 755 {} \;

# Berechtigungen nur für Dateien rekursiv ändern (mit find)
find /verzeichnis -type f -exec chmod 644 {} \;

10.3.2.4 Rekursives Ändern von Besitzer und Gruppe mit chown

# Rekursives Ändern von Besitzer und Gruppe
chown -R benutzer:gruppe /verzeichnis

# Nur den Besitzer rekursiv ändern
chown -R benutzer /verzeichnis

# Nur die Gruppe rekursiv ändern
chgrp -R gruppe /verzeichnis

10.3.3 Die Option --recursive bei GNU-Befehlen

Viele GNU-Befehle unterstützen eine --recursive-Option (oder -r):

# Rekursive Suche mit grep
grep -r "suchtext" /verzeichnis

# Rekursives Auflisten von Dateien mit ls
ls -R /verzeichnis

# Rekursives Finden und Ersetzen mit sed (vorsichtig!)
find /verzeichnis -type f -name "*.txt" -exec sed -i 's/alt/neu/g' {} \;

10.3.4 Rekursive Operationen mit der globstar-Option in Bash

Die Bash bietet mit der globstar-Option eine einfache Möglichkeit für rekursives Globbing:

# globstar aktivieren
shopt -s globstar

# Alle .sh-Dateien in allen Unterverzeichnissen auflisten
ls -la **/*.sh

# Alle .txt-Dateien rekursiv nach einem Muster durchsuchen
grep "muster" **/*.txt

# Alle .bak-Dateien rekursiv löschen
rm **/*.bak

# globstar deaktivieren
shopt -u globstar

Die globstar-Option ist besonders nützlich für schnelle, ad-hoc rekursive Operationen, ist jedoch nicht so mächtig wie der find-Befehl, wenn es um komplexe Suchkriterien geht.

10.3.5 Fortgeschrittene rekursive Techniken

10.3.5.1 Rekursive Verzeichnisgrößenermittlung

Der du-Befehl (disk usage) ist spezialisiert auf rekursive Größenermittlung:

# Rekursive Verzeichnisgrößen anzeigen (human-readable)
du -h /verzeichnis

# Nur Gesamtgröße anzeigen
du -sh /verzeichnis

# Die 5 größten Unterverzeichnisse finden
du -h /verzeichnis | sort -rh | head -5

10.3.5.2 Echtzeit-Überwachung rekursiver Änderungen mit inotifywait

Das Werkzeug inotifywait (aus dem Paket inotify-tools) ermöglicht die Überwachung von Dateisystemereignissen:

# Rekursiv alle Ereignisse im Verzeichnis überwachen
inotifywait -m -r /verzeichnis

# Nur bestimmte Ereignisse überwachen (z.B. Änderungen)
inotifywait -m -r -e modify,create,delete /verzeichnis

# Skript zur Protokollierung von Dateisystemänderungen
#!/bin/bash
LOG_FILE="/var/log/fs_changes.log"

inotifywait -m -r -e modify,create,delete,move --format '%:e %w%f' /zu/überwachendes/verzeichnis |
while read -r line; do
    echo "$(date '+%Y-%m-%d %H:%M:%S') $line" >> "$LOG_FILE"
done

10.3.5.3 Rekursive Dateisynchronisation

Für die Synchronisation ganzer Verzeichnisstrukturen ist rsync das Werkzeug der Wahl:

# Grundlegende rekursive Synchronisation
rsync -av /quellverzeichnis/ /zielverzeichnis/

# Synchronisation mit Löschung nicht mehr vorhandener Dateien
rsync -av --delete /quellverzeichnis/ /zielverzeichnis/

# Trockenlauf (zeigt, was synchronisiert würde, ohne Änderungen vorzunehmen)
rsync -av --dry-run /quellverzeichnis/ /zielverzeichnis/

# Inkrementelles Backup mit Hardlinks für unverändertes
rsync -av --link-dest=/pfad/zum/letzten/backup /quellverzeichnis/ /backup/$(date +%Y%m%d)/

10.3.6 Leistungsoptimierung bei rekursiven Operationen

Rekursive Operationen können bei großen Verzeichnisstrukturen ressourcenintensiv sein. Hier einige Strategien zur Optimierung:

10.3.6.1 Beschränkung der Suche durch geeignete Filterung

# Nur bestimmte Dateitypen einbeziehen
find /verzeichnis -type f -name "*.log" -o -name "*.txt"

# Bestimmte Verzeichnisse ausschließen
find /verzeichnis -type f -not -path "*/node_modules/*" -not -path "*/.*/*"

10.3.6.2 Verwenden von -prune für effizientes Ausschließen von Unterverzeichnissen

# Alle Verzeichnisse außer './temp' und './cache' durchsuchen
find . \( -name temp -o -name cache \) -prune -o -type f -name "*.py" -print

10.3.6.3 Optimale Ausführung von Aktionen

# Die schnelleren Tests zuerst platzieren
find /verzeichnis -type f -size -1M -mtime +30 -name "*.log"

# Bei großen Dateimengen: parallel verarbeiten
find /verzeichnis -type f -name "*.jpg" -print0 | xargs -0 -P 8 -n 10 jpegoptim

10.3.7 Praktische Beispiele für rekursive Skripte

10.3.7.1 Rekursive Suche und Ersetzung in mehreren Dateien

#!/bin/bash
# search_replace.sh - Rekursive Suche und Ersetzung in Dateien
# Verwendung: search_replace.sh Verzeichnis Suchmuster Ersetzung Dateimuster

if [ $# -ne 4 ]; then
    echo "Verwendung: $0 <verzeichnis> <suchmuster> <ersetzung> <dateimuster>"
    echo "Beispiel: $0 /var/www 'alte_domain.com' 'neue_domain.com' '*.php'"
    exit 1
fi

DIR="$1"
SEARCH="$2"
REPLACE="$3"
PATTERN="$4"

if [ ! -d "$DIR" ]; then
    echo "Fehler: Verzeichnis '$DIR' existiert nicht."
    exit 2
fi

# Zuerst Dateien finden, die das Suchmuster enthalten
echo "Suche nach Dateien, die '$SEARCH' enthalten..."
MATCHING_FILES=$(grep -l "$SEARCH" $(find "$DIR" -type f -name "$PATTERN"))

if [ -z "$MATCHING_FILES" ]; then
    echo "Keine übereinstimmenden Dateien gefunden."
    exit 0
fi

# Anzahl der gefundenen Dateien ermitteln
COUNT=$(echo "$MATCHING_FILES" | wc -l)
echo "Gefunden: $COUNT Dateien mit dem Muster '$SEARCH'"

# Bestätigung einholen
echo "Möchten Sie '$SEARCH' durch '$REPLACE' in diesen Dateien ersetzen? (j/N)"
read -r CONFIRM
if [[ ! "$CONFIRM" =~ ^[jJ] ]]; then
    echo "Abgebrochen."
    exit 0
fi

# Ersetzung durchführen
echo "$MATCHING_FILES" | while read -r FILE; do
    echo "Bearbeite: $FILE"
    sed -i "s|$SEARCH|$REPLACE|g" "$FILE"
done

echo "Ersetzung abgeschlossen."
exit 0

10.3.7.2 Rekursive Bereinigung alter Logdateien

#!/bin/bash
# cleanup_logs.sh - Alte Logdateien komprimieren oder löschen

# Konfiguration
LOG_DIR="/var/log"
DAYS_TO_COMPRESS=7
DAYS_TO_DELETE=30
DRY_RUN=false

# Optionen verarbeiten
while getopts "d:c:r:n" opt; do
  case $opt in
    d) LOG_DIR="$OPTARG" ;;
    c) DAYS_TO_COMPRESS="$OPTARG" ;;
    r) DAYS_TO_DELETE="$OPTARG" ;;
    n) DRY_RUN=true ;;
    *) echo "Ungültige Option"; exit 1 ;;
  esac
done

# Funktion: Logdateien komprimieren, die älter als X Tage sind
compress_old_logs() {
    local days="$1"
    echo "Suche nach Logdateien älter als $days Tage zur Komprimierung..."
    
    if $DRY_RUN; then
        find "$LOG_DIR" -name "*.log" -type f -mtime +$days -not -name "*.gz" -print
    else
        find "$LOG_DIR" -name "*.log" -type f -mtime +$days -not -name "*.gz" -exec gzip {} \;
    fi
}

# Funktion: Alte komprimierte Logs löschen
delete_old_logs() {
    local days="$1"
    echo "Suche nach komprimierten Logdateien älter als $days Tage zum Löschen..."
    
    if $DRY_RUN; then
        find "$LOG_DIR" -name "*.log.gz" -type f -mtime +$days -print
    else
        find "$LOG_DIR" -name "*.log.gz" -type f -mtime +$days -delete
    fi
}

# Hauptprogramm
if [ ! -d "$LOG_DIR" ]; then
    echo "Fehler: Verzeichnis '$LOG_DIR' existiert nicht."
    exit 1
fi

if $DRY_RUN; then
    echo "DRY RUN: Es werden keine Änderungen vorgenommen."
fi

compress_old_logs "$DAYS_TO_COMPRESS"
delete_old_logs "$DAYS_TO_DELETE"

echo "Bereinigung abgeschlossen."
exit 0

10.3.7.3 Rekursive Duplikaterkennung

#!/bin/bash
# find_duplicates.sh - Sucht Dateiduplikate in einem Verzeichnis basierend auf MD5-Hashes

if [ $# -lt 1 ]; then
    echo "Verwendung: $0 <verzeichnis> [--delete]"
    exit 1
fi

DIR="$1"
DELETE=false

if [ "$2" = "--delete" ]; then
    DELETE=true
    echo "WARNUNG: Automatisches Löschen von Duplikaten aktiviert!"
fi

if [ ! -d "$DIR" ]; then
    echo "Fehler: Verzeichnis '$DIR' existiert nicht."
    exit 2
fi

TEMP_FILE=$(mktemp)
DUPE_REPORT="duplicate_files_$(date +%Y%m%d_%H%M%S).txt"

echo "Berechne Hashes für alle Dateien (dies kann einige Zeit dauern)..."
find "$DIR" -type f -exec md5sum {} \; | sort > "$TEMP_FILE"

echo "Suche nach Duplikaten..."
cat "$TEMP_FILE" | uniq -w 32 -d --all-repeated=separate > "$DUPE_REPORT"

# Anzahl der Duplikatsgruppen ermitteln
DUPE_GROUPS=$(grep -c "^[0-9a-f]\{32\}" "$DUPE_REPORT")

if [ "$DUPE_GROUPS" -eq 0 ]; then
    echo "Keine Duplikate gefunden."
    rm "$TEMP_FILE" "$DUPE_REPORT"
    exit 0
fi

echo "Gefunden: $DUPE_GROUPS Duplikatsgruppen."
echo "Vollständiger Bericht gespeichert in: $DUPE_REPORT"

if $DELETE; then
    echo "Lösche alle Duplikate außer der ersten Instanz jeder Datei..."
    
    # Durchlaufe jede Gruppe von Duplikaten
    cat "$TEMP_FILE" | cut -c 35- | uniq -d | while read -r original; do
        # Finde alle Dateien mit dem gleichen Hash
        md5=$(md5sum "$original" | cut -d' ' -f1)
        duplicates=$(grep "^$md5" "$TEMP_FILE" | cut -c 35- | grep -v "^$original$")
        
        # Lösche die Duplikate
        echo "$duplicates" | while read -r duplicate; do
            echo "Lösche Duplikat: $duplicate"
            rm "$duplicate"
        done
    done
    
    echo "Bereinigung abgeschlossen."
fi

rm "$TEMP_FILE"
exit 0

10.4 Suchen und Filtern von Dateien basierend auf Eigenschaften

Die präzise Suche und Filterung von Dateien anhand ihrer Eigenschaften ist eine grundlegende Fähigkeit für effektives Shell-Scripting. In diesem Abschnitt werden wir verschiedene Methoden und Werkzeuge behandeln, mit denen Dateien basierend auf Attributen wie Größe, Alter, Berechtigungen, Eigentümer und weiteren Eigenschaften gesucht und gefiltert werden können.

10.4.1 Die find-Befehlsstruktur verstehen

Der find-Befehl bietet die umfassendsten Möglichkeiten, um Dateien basierend auf verschiedenen Kriterien zu suchen. Ein typischer find-Befehl hat folgende Struktur:

find [Startverzeichnis] [Optionen] [Tests] [Aktionen]

Die grundlegenden Komponenten sind:

10.4.2 Suche basierend auf Dateinamen

Die einfachste und häufigste Suche basiert auf dem Dateinamen:

# Nach Dateien mit exaktem Namen suchen
find /pfad/zum/verzeichnis -name "dateiname.txt"

# Nach Dateien mit Muster suchen (Globbing)
find /pfad/zum/verzeichnis -name "*.log"

# Groß-/Kleinschreibung ignorieren
find /pfad/zum/verzeichnis -iname "*.Jpg"

# Negation: Alles außer bestimmten Dateien
find /pfad/zum/verzeichnis -not -name "*.tmp"

Diese Suchkriterien können mit logischen Operatoren kombiniert werden:

# Logisches ODER: Dateien mit .jpg ODER .png Endung
find /pfad/zum/verzeichnis -name "*.jpg" -o -name "*.png"

# Logisches UND: Dateien mit .log Endung, die NICHT cache im Namen haben
find /pfad/zum/verzeichnis -name "*.log" -not -name "*cache*"

# Gruppierung mit Klammern (Escape-Zeichen beachten)
find /pfad/zum/verzeichnis \( -name "*.jpg" -o -name "*.png" \) -not -name "*thumb*"

10.4.3 Suche basierend auf Dateityp

# Reguläre Dateien
find /pfad/zum/verzeichnis -type f

# Verzeichnisse
find /pfad/zum/verzeichnis -type d

# Symbolische Links
find /pfad/zum/verzeichnis -type l

# Pipes (FIFOs)
find /pfad/zum/verzeichnis -type p

# Socket-Dateien
find /pfad/zum/verzeichnis -type s

# Block-Gerätedateien
find /pfad/zum/verzeichnis -type b

# Zeichen-Gerätedateien
find /pfad/zum/verzeichnis -type c

Diese können kombiniert werden, um z.B. alle nicht-regulären Dateien zu finden:

find /pfad/zum/verzeichnis -not -type f

10.4.4 Suche basierend auf Dateigröße

Die Größeneinheiten bei find sind:

Die Präfixe + und - bedeuten “größer als” bzw. “kleiner als”:

# Dateien größer als 10MB
find /pfad/zum/verzeichnis -type f -size +10M

# Dateien kleiner als 1KB
find /pfad/zum/verzeichnis -type f -size -1k

# Dateien genau 0 Bytes (leere Dateien)
find /pfad/zum/verzeichnis -type f -size 0

# Dateien zwischen 5MB und 10MB
find /pfad/zum/verzeichnis -type f -size +5M -size -10M

Ein häufiges Anwendungsbeispiel ist das Finden großer Dateien, die potenziell Speicherplatz verschwenden:

#!/bin/bash
# large_files.sh - Finde die 10 größten Dateien in einem Verzeichnis

TARGET_DIR="${1:-/home/user}"
NUM_FILES="${2:-10}"

echo "Die $NUM_FILES größten Dateien in $TARGET_DIR:"
find "$TARGET_DIR" -type f -printf "%s %p\n" | sort -rn | head -n "$NUM_FILES" | 
awk '{printf "%.2f MB\t%s\n", $1/(1024*1024), substr($0, length($1)+2)}'

10.4.5 Suche basierend auf Zeitstempeln

Dateien in Unix/Linux haben drei Zeitstempel:

Bei der Suche mit find werden diese Zeiten in Tagen angegeben:

# Dateien, die in den letzten 7 Tagen geändert wurden
find /pfad/zum/verzeichnis -type f -mtime -7

# Dateien, die vor mehr als 30 Tagen geändert wurden
find /pfad/zum/verzeichnis -type f -mtime +30

# Dateien, die genau vor 1 Tag geändert wurden
find /pfad/zum/verzeichnis -type f -mtime 1

# Dateien, auf die in den letzten 14 Tagen nicht zugegriffen wurde
find /pfad/zum/verzeichnis -type f -atime +14

Für präzisere Zeitangaben bietet find auch min-Versionen dieser Optionen, die in Minuten statt Tagen zählen:

# Dateien, die in den letzten 60 Minuten geändert wurden
find /pfad/zum/verzeichnis -type f -mmin -60

# Dateien, auf die in den letzten 30 Minuten zugegriffen wurde
find /pfad/zum/verzeichnis -type f -amin -30

Noch genauere Zeitstempelsuchen sind mit -newerXY möglich:

# Dateien, die neuer als eine Referenzdatei sind
find /pfad/zum/verzeichnis -type f -newer /pfad/zur/referenzdatei

# Dateien, die nach einem bestimmten Zeitpunkt geändert wurden
touch -t 202401010000 /tmp/zeitstempel
find /pfad/zum/verzeichnis -type f -newer /tmp/zeitstempel

Ein Beispielskript für die automatische Archivierung alter Dateien:

#!/bin/bash
# archive_old_files.sh - Archiviert Dateien, die älter als X Tage sind

SOURCE_DIR="${1:-/var/data}"
DAYS_OLD="${2:-90}"
ARCHIVE_DIR="/var/archives/$(date +%Y%m)"

# Archivverzeichnis erstellen, falls es nicht existiert
mkdir -p "$ARCHIVE_DIR"

# Dateien finden, die älter als DAYS_OLD Tage sind
echo "Suche Dateien in $SOURCE_DIR, die älter als $DAYS_OLD Tage sind..."
OLD_FILES=$(find "$SOURCE_DIR" -type f -mtime +$DAYS_OLD)

if [ -z "$OLD_FILES" ]; then
    echo "Keine alten Dateien gefunden."
    exit 0
fi

# Erstelle temporäre Dateiliste
TEMP_LIST=$(mktemp)
echo "$OLD_FILES" > "$TEMP_LIST"

# Archiviere die alten Dateien
echo "Archiviere alte Dateien nach $ARCHIVE_DIR..."
tar -czf "$ARCHIVE_DIR/old_files_$(date +%Y%m%d).tar.gz" -T "$TEMP_LIST"

# Lösche die Originaldateien nach erfolgreicher Archivierung
if [ $? -eq 0 ]; then
    echo "Entferne alte Dateien..."
    xargs rm -f < "$TEMP_LIST"
else
    echo "Fehler beim Archivieren, Originaldateien bleiben erhalten."
fi

# Aufräumen
rm "$TEMP_LIST"

10.4.6 Suche basierend auf Berechtigungen

Die Suche nach Dateien mit bestimmten Berechtigungen ist besonders für Sicherheitsaudits wichtig:

# Dateien mit exakten Berechtigungen (777)
find /pfad/zum/verzeichnis -type f -perm 777

# Dateien mit mindestens den angegebenen Berechtigungen (alle haben Schreibzugriff)
find /pfad/zum/verzeichnis -type f -perm -002

# Dateien mit irgendeiner der angegebenen Berechtigungen (irgendwer hat Schreibzugriff)
find /pfad/zum/verzeichnis -type f -perm /002

# Symbolische Darstellung für weltlesbare Dateien
find /pfad/zum/verzeichnis -type f -perm -o=r

# SUID-Binärdateien finden (potenzielle Sicherheitsrisiken)
find /usr -type f -perm -4000

Ein Beispielskript zur Überprüfung unsicherer Berechtigungen:

#!/bin/bash
# security_audit.sh - Findet Dateien mit potenziell unsicheren Berechtigungen

TARGET_DIR="${1:-/home}"
REPORT_FILE="security_audit_$(date +%Y%m%d).txt"

echo "Sicherheitsaudit für $TARGET_DIR am $(date)" > "$REPORT_FILE"
echo "=================================================" >> "$REPORT_FILE"

# Weltschreibbare Verzeichnisse finden
echo -e "\n### Weltschreibbare Verzeichnisse ###" >> "$REPORT_FILE"
find "$TARGET_DIR" -type d -perm -o=w | grep -v '/tmp$\|/temp$' >> "$REPORT_FILE"

# Weltschreibbare Dateien finden
echo -e "\n### Weltschreibbare Dateien ###" >> "$REPORT_FILE"
find "$TARGET_DIR" -type f -perm -o=w >> "$REPORT_FILE"

# SUID/SGID Programme finden
echo -e "\n### SUID/SGID Programme ###" >> "$REPORT_FILE"
find "$TARGET_DIR" -type f \( -perm -4000 -o -perm -2000 \) >> "$REPORT_FILE"

# Weltausführbare Skripte finden
echo -e "\n### Weltausführbare Skriptdateien ###" >> "$REPORT_FILE"
find "$TARGET_DIR" -type f -perm -o=x -name "*.sh" -o -name "*.pl" -o -name "*.py" >> "$REPORT_FILE"

echo "Audit abgeschlossen. Ergebnisse in $REPORT_FILE"

10.4.7 Suche basierend auf Eigentümer und Gruppe

# Dateien, die einem bestimmten Benutzer gehören
find /pfad/zum/verzeichnis -type f -user benutzername

# Dateien, die einer bestimmten Gruppe gehören
find /pfad/zum/verzeichnis -type f -group gruppenname

# Dateien ohne Eigentümer (Besitzer gelöscht)
find /pfad/zum/verzeichnis -type f -nouser

# Dateien ohne gültige Gruppe
find /pfad/zum/verzeichnis -type f -nogroup

Ein Skript zur automatischen Bereinigung von Dateien ohne Eigentümer:

#!/bin/bash
# orphaned_files.sh - Behandelt Dateien ohne Eigentümer

TARGET_DIR="${1:-/}"
ACTION="${2:-report}"  # 'report' oder 'cleanup'

case "$ACTION" in
    report)
        echo "Bericht über verwaiste Dateien in $TARGET_DIR:"
        find "$TARGET_DIR" \( -nouser -o -nogroup \) -ls
        ;;
    cleanup)
        echo "Bereinigung verwaister Dateien in $TARGET_DIR:"
        # Verwaiste Dateien in /lost+found verschieben
        mkdir -p /lost+found
        find "$TARGET_DIR" \( -nouser -o -nogroup \) -type f \
            -exec mv {} /lost+found/ \; -printf "Verschoben: %p\n"
        find "$TARGET_DIR" \( -nouser -o -nogroup \) -type d \
            -exec chown root:root {} \; -printf "Eigentümer gesetzt: %p\n"
        ;;
    *)
        echo "Ungültige Aktion: $ACTION"
        echo "Verwendung: $0 [verzeichnis] [report|cleanup]"
        exit 1
        ;;
esac

10.4.8 Suche basierend auf Dateiinhalt

Die Kombination von find mit grep ermöglicht die Suche nach Dateien basierend auf ihrem Inhalt:

# Dateien finden, die ein bestimmtes Muster enthalten
find /pfad/zum/verzeichnis -type f -exec grep -l "suchmuster" {} \;

# Effizienter mit xargs
find /pfad/zum/verzeichnis -type f -name "*.conf" | xargs grep -l "parameter"

# Noch effizienter mit null-terminierter Ausgabe (für Dateien mit Leerzeichen)
find /pfad/zum/verzeichnis -type f -name "*.php" -print0 | xargs -0 grep -l "function"

Für komplexere Textsuchen kann ein Skript wie dieses verwendet werden:

#!/bin/bash
# content_search.sh - Suche in Dateien nach mehreren Mustern

TARGET_DIR="${1:-.}"
shift
PATTERNS=("$@")

if [ ${#PATTERNS[@]} -eq 0 ]; then
    echo "Verwendung: $0 [verzeichnis] <muster1> [muster2] ..."
    exit 1
fi

# Erstelle temporäre Datei für Ergebnisse
RESULTS=$(mktemp)

echo "Suche in $TARGET_DIR nach: ${PATTERNS[*]}"
echo "Ergebnisse:"

# Für jedes Muster suchen und Ergebnisse sammeln
for pattern in "${PATTERNS[@]}"; do
    echo -e "\nDateien mit '$pattern':" >> "$RESULTS"
    find "$TARGET_DIR" -type f -exec grep -l "$pattern" {} \; | sort >> "$RESULTS"
done

# Dateien finden, die ALLE angegebenen Muster enthalten
if [ ${#PATTERNS[@]} -gt 1 ]; then
    echo -e "\nDateien mit ALLEN angegebenen Mustern:" >> "$RESULTS"
    
    # Erstelle temporäre Dateien für die Schnittmenge
    TEMP1=$(mktemp)
    TEMP2=$(mktemp)
    
    # Erste Suchliste in TEMP1 speichern
    find "$TARGET_DIR" -type f -exec grep -l "${PATTERNS[0]}" {} \; > "$TEMP1"
    
    # Für jedes weitere Muster die Schnittmenge bilden
    for ((i=1; i<${#PATTERNS[@]}; i++)); do
        find "$TARGET_DIR" -type f -exec grep -l "${PATTERNS[$i]}" {} \; | \
        sort | comm -12 "$TEMP1" - > "$TEMP2"
        cat "$TEMP2" > "$TEMP1"
    done
    
    cat "$TEMP1" >> "$RESULTS"
    
    # Aufräumen
    rm "$TEMP1" "$TEMP2"
fi

# Ergebnisse anzeigen
cat "$RESULTS"
rm "$RESULTS"

10.4.9 Suche basierend auf Dateisystem-spezifischen Eigenschaften

# Dateien auf einem bestimmten Dateisystem finden
find /pfad/zum/verzeichnis -fstype ext4

# Dateien mit einer bestimmten Inode-Nummer finden
find /pfad/zum/verzeichnis -inum 12345

# Mehrere Links zu derselben Inode finden (Hardlinks)
find /pfad/zum/verzeichnis -type f -links +1

10.4.10 Suche nach leeren Dateien und Verzeichnissen

# Leere Dateien finden
find /pfad/zum/verzeichnis -type f -empty

# Leere Verzeichnisse finden
find /pfad/zum/verzeichnis -type d -empty

# Leere Dateien und Verzeichnisse finden und löschen
find /pfad/zum/verzeichnis -empty -delete

Ein nützliches Skript zum Aufräumen leerer Verzeichnisse:

#!/bin/bash
# cleanup_empty_dirs.sh - Räumt leere Verzeichnisse auf

TARGET_DIR="${1:-.}"
MIN_DEPTH="${2:-1}"  # Mindesttiefe, um Löschen des Wurzelverzeichnisses zu verhindern

if [ ! -d "$TARGET_DIR" ]; then
    echo "Fehler: $TARGET_DIR ist kein Verzeichnis."
    exit 1
fi

echo "Suche nach leeren Verzeichnissen in $TARGET_DIR..."

# Zuerst nur zählen und anzeigen
EMPTY_DIRS=$(find "$TARGET_DIR" -mindepth "$MIN_DEPTH" -type d -empty)
COUNT=$(echo "$EMPTY_DIRS" | grep -v "^$" | wc -l)

if [ "$COUNT" -eq 0 ]; then
    echo "Keine leeren Verzeichnisse gefunden."
    exit 0
fi

echo "Gefunden: $COUNT leere Verzeichnisse."
echo "$EMPTY_DIRS"

# Nachfragen, ob gelöscht werden soll
read -p "Möchten Sie diese Verzeichnisse löschen? (j/N) " -n 1 -r
echo

if [[ $REPLY =~ ^[jJ]$ ]]; then
    # Von der tiefsten Ebene beginnend löschen
    find "$TARGET_DIR" -mindepth "$MIN_DEPTH" -type d -empty -delete
    
    # Prüfen, ob alle leeren Verzeichnisse gelöscht wurden
    REMAINING=$(find "$TARGET_DIR" -mindepth "$MIN_DEPTH" -type d -empty | wc -l)
    
    if [ "$REMAINING" -eq 0 ]; then
        echo "Alle leeren Verzeichnisse wurden gelöscht."
    else
        echo "Es konnten nicht alle Verzeichnisse gelöscht werden. $REMAINING verbleibend."
    fi
else
    echo "Vorgang abgebrochen. Keine Verzeichnisse wurden gelöscht."
fi

10.4.11 Kombination mehrerer Suchkriterien

Die wahre Stärke von find liegt in der Kombination verschiedener Kriterien:

# Große, kürzlich geänderte Logdateien finden
find /var/log -type f -name "*.log" -size +10M -mtime -7

# Alte Konfigurationsdateien finden, die weltlesbar sind
find /etc -type f -perm -o=r -mtime +365

# Ausführbare Skriptdateien finden, die einem bestimmten Benutzer gehören
find /home -type f -user username -perm /u=x -name "*.sh"

10.4.12 Ausschließen von Verzeichnissen bei der Suche

Bei großen Verzeichnisstrukturen ist es oft effizienter, bestimmte Verzeichnisse auszuschließen:

# Bestimmte Verzeichnisse ausschließen
find /home -type f -not -path "*/node_modules/*" -not -path "*/.git/*"

# Effizientere Methode mit -prune
find /home -type d \( -name node_modules -o -name .git \) -prune -o -type f -print

Ein nützliches Skript für die Codesuche, das häufige Build- und Dependency-Verzeichnisse ausschließt:

#!/bin/bash
# code_search.sh - Suche in Codebasen unter Ausschluss von Build-Verzeichnissen

TARGET_DIR="${1:-.}"
PATTERN="$2"

if [ -z "$PATTERN" ]; then
    echo "Verwendung: $0 [verzeichnis] <suchmuster>"
    exit 1
fi

# Liste häufiger Verzeichnisse, die auszuschließen sind
EXCLUDE_DIRS=(
    node_modules
    .git
    .svn
    build
    dist
    target
    vendor
    __pycache__
    .idea
    .vscode
)

# Erstelle den Ausschlussfilter für find
EXCLUDE_EXPR=""
for dir in "${EXCLUDE_DIRS[@]}"; do
    EXCLUDE_EXPR="$EXCLUDE_EXPR -name $dir -o"
done
EXCLUDE_EXPR="( ${EXCLUDE_EXPR% -o} ) -prune -o"

# Dateiendungen für Quellcode
CODE_EXTENSIONS=(
    "*.c" "*.cpp" "*.h" "*.hpp"
    "*.java" "*.cs" "*.py" "*.rb"
    "*.js" "*.ts" "*.php" "*.go"
    "*.sh" "*.pl" "*.pm" "*.swift"
)

# Erstelle den Filter für Quellcodedateien
FILE_EXPR=""
for ext in "${CODE_EXTENSIONS[@]}"; do
    FILE_EXPR="$FILE_EXPR -name $ext -o"
done
FILE_EXPR="( ${FILE_EXPR% -o} )"

# Führe die Suche aus
echo "Suche nach '$PATTERN' in $TARGET_DIR (ohne Build-Verzeichnisse)..."

find "$TARGET_DIR" $EXCLUDE_EXPR -type f $FILE_EXPR -exec grep -l "$PATTERN" {} \;

10.4.13 Fortgeschrittene Ausgabeformatierung mit find

Der find-Befehl bietet verschiedene Ausgabeformatierungsoptionen, die besonders nützlich für Skripte sind:

# Benutzerdefinierte Ausgabeformatierung
find /pfad/zum/verzeichnis -type f -printf "%p %s %TY-%Tm-%Td %TH:%TM\n"

# Häufig verwendete Formatierungsoptionen:
# %p - Dateipfad
# %f - Dateiname
# %h - Verzeichnis
# %s - Größe in Bytes
# %TY, %Tm, %Td - Jahr, Monat, Tag der letzten Änderung
# %TH, %TM - Stunde, Minute der letzten Änderung
# %u - Eigentümer (Benutzername)
# %g - Gruppe
# %m - Berechtigungen (als Oktalzahl)

Ein Skript für detaillierte Dateilisten mit benutzerdefinierter Formatierung:

#!/bin/bash
# detailed_file_list.sh - Erstellt eine detaillierte Dateilistenanzeige

TARGET_DIR="${1:-.}"
OUTPUT_FORMAT="${2:-standard}"  # standard, csv, html

if [ ! -d "$TARGET_DIR" ]; then
    echo "Fehler: $TARGET_DIR ist kein Verzeichnis."
    exit 1
fi

case "$OUTPUT_FORMAT" in
    standard)
        echo "Detaillierte Dateiliste für $TARGET_DIR"
        echo "=========================================="
        find "$TARGET_DIR" -type f -printf "%-50p | %10s Bytes | %TY-%Tm-%Td %TH:%TM | %u:%g\n" | \
            sort
        ;;
    csv)
        OUTPUT_FILE="file_list_$(date +%Y%m%d).csv"
        echo "Pfad,Größe (Bytes),Datum,Zeit,Eigentümer,Gruppe" > "$OUTPUT_FILE"
        find "$TARGET_DIR" -type f -printf "\"%p\",%s,%TY-%Tm-%Td,%TH:%TM:%TS,%u,%g\n" >> "$OUTPUT_FILE"
        echo "CSV-Datei erstellt: $OUTPUT_FILE"
        ;;
    html)
        OUTPUT_FILE="file_list_$(date +%Y%m%d).html"
        cat > "$OUTPUT_FILE" << EOF
<!DOCTYPE html>
<html>
<head>
    <title>Dateiliste für $TARGET_DIR</title>
    <style>
        body { font-family: Arial, sans-serif; }
        table { border-collapse: collapse; width: 100%; }
        th, td { text-align: left; padding: 8px; border-bottom: 1px solid #ddd; }
        tr:nth-child(even) { background-color: #f2f2f2; }
        th { background-color: #4CAF50; color: white; }
    </style>
</head>
<body>
    <h1>Dateiliste für $TARGET_DIR</h1>
    <p>Erstellt am $(date)</p>
    <table>
        <tr>
            <th>Datei</th>
            <th>Größe</th>
            <th>Datum</th>
            <th>Eigentümer</th>
        </tr>
EOF

        find "$TARGET_DIR" -type f -printf "<tr><td>%p</td><td>%s Bytes</td><td>%TY-%Tm-%Td %TH:%TM</td><td>%u:%g</td></tr>\n" \
            >> "$OUTPUT_FILE"

        cat >> "$OUTPUT_FILE" << EOF
    </table>
</body>
</html>
EOF
        echo "HTML-Datei erstellt: $OUTPUT_FILE"
        ;;
    *)
        echo "Fehler: Ungültiges Ausgabeformat. Mögliche Werte: standard, csv, html"
        exit 1
        ;;
esac

10.4.14 Alternativen und Ergänzungen zu find

Neben find gibt es weitere nützliche Befehle für spezialisierte Suchen:

10.4.14.1 locate - Schnelle Dateisuche über eine Datenbank

# Schnelle Suche nach Dateinamen
locate filename.txt

# Aktualisieren der locate-Datenbank
sudo updatedb

10.4.14.2 fd - Eine moderne Alternative zu find

Der Befehl fd (auf den meisten Systemen installierbar) bietet eine benutzerfreundlichere Syntax:

# Installation (Ubuntu/Debian)
# sudo apt install fd-find

# Grundlegende Suche (implizit rekursiv)
fd pattern /pfad/zum/verzeichnis

# Nur bestimmte Dateitypen
fd --type f --extension txt

# Ignorieren von Verzeichnissen
fd pattern --exclude node_modules

10.4.14.3 fzf - Interaktive Fuzzy-Suche

# Installation (Ubuntu/Debian)
# sudo apt install fzf

# Interaktive Dateisuche
fzf

# Mit Vorschau
fzf --preview 'cat {}'

# Als Filter in Pipe
find . -type f | fzf

10.5 Temporäre Dateien und Verzeichnisse

Temporäre Dateien und Verzeichnisse sind ein wesentlicher Bestandteil vieler Shell-Skripte. Sie dienen als Zwischenspeicher für vorübergehende Daten, Berechnungsergebnisse oder als Arbeitsbereich für Dateimanipulationen. In diesem Abschnitt werden wir die verschiedenen Methoden zum Erstellen, Verwenden und sicheren Umgang mit temporären Dateien und Verzeichnissen in Shell-Skripten behandeln.

10.5.1 Grundlegendes zu temporären Dateien

Temporäre Dateien sollten folgende Eigenschaften haben:

  1. Eindeutigkeit: Der Dateiname sollte einzigartig sein, um Konflikte mit anderen Prozessen zu vermeiden.
  2. Sicherheit: Die Dateiberechtigungen sollten so eingestellt sein, dass nur der Ersteller darauf zugreifen kann.
  3. Kurzlebigkeit: Temporäre Dateien sollten automatisch gelöscht werden, wenn sie nicht mehr benötigt werden.
  4. Speicherort: Sie sollten in einem dafür vorgesehenen Verzeichnis abgelegt werden.

10.5.2 Das /tmp-Verzeichnis

Unter Linux und anderen Unix-ähnlichen Betriebssystemen ist /tmp das Standardverzeichnis für temporäre Dateien:

# Einfacher Zugriff auf /tmp
echo "Temporäre Daten" > /tmp/meine_temp_datei

# Prüfen des Dateisystem-Typs von /tmp
df -T /tmp

Neben /tmp gibt es auch /var/tmp, das ebenfalls für temporäre Dateien gedacht ist, aber in der Regel zwischen Systemneustarts erhalten bleibt.

10.5.3 Erstellen temporärer Dateien mit mktemp

Der Befehl mktemp ist die sicherste und empfohlene Methode zum Erstellen temporärer Dateien:

# Grundlegende Verwendung
TEMP_FILE=$(mktemp)
echo "Datei erstellt: $TEMP_FILE"  # Ausgabe z.B.: /tmp/tmp.A1B2C3D4E5

# Mit benutzerdefiniertem Präfix
TEMP_FILE=$(mktemp /tmp/myapp.XXXXXX)
echo "Datei erstellt: $TEMP_FILE"  # Ausgabe z.B.: /tmp/myapp.A1B2C3

# Temporäres Verzeichnis erstellen
TEMP_DIR=$(mktemp -d)
echo "Verzeichnis erstellt: $TEMP_DIR"

Die X-Zeichen im Template werden durch zufällige Zeichen ersetzt, um einen eindeutigen Namen zu erzeugen.

10.5.4 Beispiel: Verwendung temporärer Dateien in Skripten

#!/bin/bash
# temp_file_example.sh - Demonstration temporärer Dateien

# Temporäre Datei erstellen
TEMP_FILE=$(mktemp)

# Aufräumen bei Beendigung des Skripts
trap 'rm -f "$TEMP_FILE"' EXIT

echo "Temporäre Datei: $TEMP_FILE"

# Daten in die temporäre Datei schreiben
echo "Zeile 1: Beispieldaten" > "$TEMP_FILE"
echo "Zeile 2: Weitere Daten" >> "$TEMP_FILE"

# Mit den Daten arbeiten
echo "Inhalt der temporären Datei:"
cat "$TEMP_FILE"

# Datei verarbeiten (z.B. sortieren)
sort "$TEMP_FILE" -o "$TEMP_FILE.sorted"
echo "Sortierter Inhalt:"
cat "$TEMP_FILE.sorted"

# Aufräumen der zusätzlichen Datei
rm -f "$TEMP_FILE.sorted"

echo "Skript beendet. Die temporäre Datei wird automatisch gelöscht."

# Kein explizites Löschen nötig, da die trap-Anweisung dies übernimmt
exit 0

10.5.5 Die trap-Anweisung zum sicheren Aufräumen

Die trap-Anweisung ist entscheidend für die sichere Verwendung temporärer Dateien. Sie führt angegebene Befehle aus, wenn das Skript beendet wird:

# Grundlegende Syntax
trap 'Befehle' SIGNALE

# Beispiel: Aufräumen bei normalem Beenden und bei Abbruch
trap 'rm -f "$TEMP_FILE"; echo "Aufgeräumt!"' EXIT INT TERM

Häufig verwendete Signale für trap:

10.5.6 Mehrere temporäre Dateien verwalten

Für komplexere Skripte kann es notwendig sein, mehrere temporäre Dateien zu verwalten:

#!/bin/bash
# multi_temp_files.sh - Verwaltung mehrerer temporärer Dateien

# Temporäres Verzeichnis für alle temporären Dateien erstellen
TEMP_DIR=$(mktemp -d)
trap 'rm -rf "$TEMP_DIR"' EXIT

echo "Temporäres Arbeitsverzeichnis: $TEMP_DIR"

# Mehrere temporäre Dateien im temporären Verzeichnis erstellen
TEMP_FILE1="$TEMP_DIR/file1"
TEMP_FILE2="$TEMP_DIR/file2"
TEMP_FILE3="$TEMP_DIR/file3"

# Dateien mit Beispieldaten füllen
seq 1 5 > "$TEMP_FILE1"
seq 6 10 > "$TEMP_FILE2"

# Dateien verarbeiten
cat "$TEMP_FILE1" "$TEMP_FILE2" > "$TEMP_FILE3"

echo "Kombinierte Daten:"
cat "$TEMP_FILE3"

echo "Alle temporären Dateien werden beim Beenden gelöscht."
exit 0

10.5.7 Temporäre Dateien mit spezifischen Berechtigungen

Manchmal benötigen temporäre Dateien besondere Berechtigungen:

# Temporäre Datei mit restriktiven Berechtigungen erstellen
TEMP_FILE=$(mktemp)
chmod 600 "$TEMP_FILE"  # Nur Lese-/Schreibzugriff für den Eigentümer

# Für gemeinsam genutzte temporäre Dateien (Vorsicht!)
SHARED_TEMP=$(mktemp)
chmod 644 "$SHARED_TEMP"  # Lesbar für alle, aber nur vom Eigentümer schreibbar

10.5.8 Sicherheitsaspekte bei temporären Dateien

Temporäre Dateien können Sicherheitsrisiken darstellen, wenn sie nicht korrekt verwaltet werden:

  1. Race Conditions: Vermeiden Sie vorhersehbare Dateinamen, um “Time-of-check to time-of-use” (TOCTOU) Angriffe zu verhindern.
  2. Informationslecks: Stellen Sie sicher, dass vertrauliche Daten in temporären Dateien ordnungsgemäß gelöscht werden.
  3. Berechtigungen: Verwenden Sie restriktive Dateiberechtigungen (600 oder 700), besonders für vertrauliche Daten.

10.5.8.1 Beispiel: Unsichere vs. sichere Methode

Unsichere Methode:

# UNSICHER: Vorhersehbarer Dateiname
TEMP_FILE="/tmp/myapp_$$.tmp"  # $$ ist die Prozess-ID
echo "Geheime Daten" > "$TEMP_FILE"
# ... Verarbeitung ...
rm -f "$TEMP_FILE"

Sichere Methode:

# SICHER: Zufälliger Dateiname mit mktemp
TEMP_FILE=$(mktemp)
trap 'rm -f "$TEMP_FILE"' EXIT INT TERM
echo "Geheime Daten" > "$TEMP_FILE"
chmod 600 "$TEMP_FILE"  # Nur Eigentümer kann lesen/schreiben
# ... Verarbeitung ...
# Explizites Überschreiben vor dem Löschen bei sensiblen Daten
shred -u "$TEMP_FILE"  # Überschreibt die Datei mehrmals und löscht sie

10.5.9 Erstellen temporärer Dateien mit benutzerdefinierten Erweiterungen

In manchen Fällen ist es nützlich, temporäre Dateien mit bestimmten Erweiterungen zu erstellen:

# Temporäre .txt-Datei erstellen
TEMP_TXT=$(mktemp --suffix=.txt)

# Alternative Methode
TEMP_BASE=$(mktemp)
TEMP_TXT="${TEMP_BASE}.txt"
mv "$TEMP_BASE" "$TEMP_TXT"

10.5.10 Temporäre Verzeichnisstrukturen

Für komplexere Aufgaben kann es notwendig sein, temporäre Verzeichnisstrukturen zu erstellen:

#!/bin/bash
# temp_structure.sh - Erstellen und Verwenden einer temporären Verzeichnisstruktur

# Temporäres Hauptverzeichnis erstellen
TEMP_ROOT=$(mktemp -d)
trap 'rm -rf "$TEMP_ROOT"' EXIT

echo "Temporäres Hauptverzeichnis: $TEMP_ROOT"

# Unterverzeichnisse erstellen
mkdir -p "$TEMP_ROOT"/{input,output,logs}

# Dateien in den Unterverzeichnissen erstellen
echo "Eingabedaten" > "$TEMP_ROOT/input/data.txt"
echo "Protokoll gestartet" > "$TEMP_ROOT/logs/process.log"

# Verarbeitung simulieren
cat "$TEMP_ROOT/input/data.txt" | tr 'a-z' 'A-Z' > "$TEMP_ROOT/output/result.txt"
echo "Verarbeitung abgeschlossen" >> "$TEMP_ROOT/logs/process.log"

# Ergebnisse anzeigen
echo "Verarbeitete Daten:"
cat "$TEMP_ROOT/output/result.txt"

echo "Protokoll:"
cat "$TEMP_ROOT/logs/process.log"

echo "Temporäre Verzeichnisstruktur wird beim Beenden gelöscht."
exit 0

10.5.11 Verwenden des TMPDIR-Umgebungsvariable

Die TMPDIR-Umgebungsvariable gibt das Verzeichnis an, in dem temporäre Dateien erstellt werden sollen:

# Standard-Temporärverzeichnis verwenden
TEMP_FILE=$(mktemp)

# Benutzerdefiniertes Temporärverzeichnis setzen
export TMPDIR=/path/to/custom/temp
TEMP_FILE=$(mktemp)  # Wird nun in $TMPDIR erstellt

Ein robuster Ansatz in Skripten:

#!/bin/bash
# tmpdir_example.sh - Verwendet TMPDIR Umgebungsvariable

# Fallback auf /tmp, wenn TMPDIR nicht gesetzt ist
: "${TMPDIR:=/tmp}"

# Temporäre Datei im durch TMPDIR definierten Verzeichnis erstellen
TEMP_FILE=$(mktemp)
trap 'rm -f "$TEMP_FILE"' EXIT

echo "Temporäre Datei: $TEMP_FILE"
# ... Rest des Skripts ...

10.5.12 Persistente temporäre Dateien

In manchen Fällen möchten Sie temporäre Dateien über die Laufzeit des Skripts hinaus beibehalten:

#!/bin/bash
# persistent_temp.sh - Demonstration persistenter temporärer Dateien

# Temporäres Verzeichnis mit Zeitstempel
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
TEMP_DIR="/tmp/myapp_$TIMESTAMP"

mkdir -p "$TEMP_DIR"
echo "Temporäres Verzeichnis erstellt: $TEMP_DIR"

# Daten generieren
echo "Beispieldaten für spätere Analyse" > "$TEMP_DIR/data.txt"

echo "Das temporäre Verzeichnis wird NICHT automatisch gelöscht."
echo "Sie können es mit folgendem Befehl löschen:"
echo "  rm -rf $TEMP_DIR"

# Speicherort in einer Log-Datei festhalten
echo "$TEMP_DIR" >> ~/.myapp_temp_dirs.log

10.5.13 Automatische Bereinigung alter temporärer Dateien

Für persistente temporäre Dateien ist eine regelmäßige Bereinigung empfehlenswert:

#!/bin/bash
# cleanup_old_temp.sh - Bereinigt alte temporäre Dateien einer Anwendung

TEMP_PATTERN="/tmp/myapp_*"
MAX_AGE_DAYS=7

echo "Suche nach temporären Dateien älter als $MAX_AGE_DAYS Tage..."

# Alte temporäre Dateien finden und löschen
find ${TEMP_PATTERN} -type d -mtime +${MAX_AGE_DAYS} -print -exec rm -rf {} \;

echo "Bereinigung abgeschlossen."

Dieses Skript könnte als Cron-Job eingerichtet werden:

# Jeden Tag um 2:00 Uhr alte temporäre Dateien bereinigen
0 2 * * * /path/to/cleanup_old_temp.sh

10.5.14 Temporäre Dateien für Pipelines und Zwischenergebnisse

Temporäre Dateien sind besonders nützlich für komplexe Datenverarbeitungspipelines:

#!/bin/bash
# data_pipeline.sh - Verarbeitet Daten in mehreren Schritten mit Zwischenergebnissen

INPUT_FILE="$1"
if [ ! -f "$INPUT_FILE" ]; then
    echo "Fehler: Eingabedatei nicht gefunden."
    exit 1
fi

# Temporäres Verzeichnis für Zwischenergebnisse
TEMP_DIR=$(mktemp -d)
trap 'rm -rf "$TEMP_DIR"' EXIT

echo "Verarbeite $INPUT_FILE..."

# Schritt 1: Daten extrahieren
echo "Schritt 1: Daten extrahieren..."
grep -v "^#" "$INPUT_FILE" | cut -f2,3,5 > "$TEMP_DIR/step1.txt"

# Schritt 2: Daten transformieren
echo "Schritt 2: Daten transformieren..."
awk '{print $1 "," $2 "," $3}' "$TEMP_DIR/step1.txt" > "$TEMP_DIR/step2.csv"

# Schritt 3: Daten aggregieren
echo "Schritt 3: Daten aggregieren..."
sort -t, -k1,1 "$TEMP_DIR/step2.csv" | \
  awk -F, '{ sum[$1] += $3; count[$1]++ } 
  END { for (key in sum) print key "," sum[key] "," sum[key]/count[key] }' > \
  "$TEMP_DIR/step3.csv"

# Finale Ausgabe
echo "Ergebnisse:"
cat "$TEMP_DIR/step3.csv"

# Optional: Ergebnisse in eine permanente Datei speichern
OUTPUT_FILE="output_$(date +%Y%m%d).csv"
cp "$TEMP_DIR/step3.csv" "$OUTPUT_FILE"
echo "Ergebnisse gespeichert in $OUTPUT_FILE"

echo "Verarbeitung abgeschlossen. Temporäre Dateien werden gelöscht."
exit 0

10.5.15 Verwendung von /dev/shm für temporäre Dateien im Speicher

Für besonders schnellen Zugriff oder wenn Daten nicht auf die Festplatte geschrieben werden sollen:

# RAM-basiertes temporäres Verzeichnis verwenden
TEMP_FILE="/dev/shm/myapp_$RANDOM"
echo "Daten im RAM" > "$TEMP_FILE"
# ... Verarbeitung ...
rm -f "$TEMP_FILE"

Besser mit mktemp:

# Sicherere Variante mit mktemp
TEMP_FILE=$(mktemp -p /dev/shm)
trap 'rm -f "$TEMP_FILE"' EXIT

10.5.16 Beispiel: Temporäre Konfigurationsdatei

Häufig müssen temporäre Konfigurationsdateien für externe Programme erstellt werden:

#!/bin/bash
# temp_config.sh - Erstellt eine temporäre Konfigurationsdatei für ein Programm

# Temporäre Konfigurationsdatei erstellen
CONFIG_FILE=$(mktemp --suffix=.conf)
trap 'rm -f "$CONFIG_FILE"' EXIT

# Konfigurationsdatei füllen
cat > "$CONFIG_FILE" << EOF
# Automatisch generierte Konfiguration
debug = true
log_level = info
max_connections = 100
timeout = 30
input_file = "$1"
output_file = "$(realpath "$2")"
EOF

echo "Temporäre Konfigurationsdatei erstellt: $CONFIG_FILE"

# Externes Programm mit der Konfigurationsdatei aufrufen
echo "Führe Programm mit temporärer Konfiguration aus..."
./my_program --config "$CONFIG_FILE"

echo "Verarbeitung abgeschlossen. Temporäre Konfiguration wird gelöscht."
exit 0

10.5.17 Umgang mit großen temporären Dateien

Bei der Verarbeitung großer Datenmengen sollten zusätzliche Überlegungen angestellt werden:

#!/bin/bash
# large_temp_files.sh - Verwalten großer temporärer Dateien

# Verfügbaren Speicherplatz prüfen
TEMP_DIR="/tmp"
REQUIRED_SPACE=1048576  # 1 GB in KB

AVAILABLE_SPACE=$(df -k "$TEMP_DIR" | awk 'NR==2 {print $4}')

if [ "$AVAILABLE_SPACE" -lt "$REQUIRED_SPACE" ]; then
    echo "Fehler: Nicht genug Speicherplatz in $TEMP_DIR"
    echo "Verfügbar: $AVAILABLE_SPACE KB, Benötigt: $REQUIRED_SPACE KB"
    exit 1
fi

# Temporäre Datei erstellen
TEMP_FILE=$(mktemp)
trap 'rm -f "$TEMP_FILE"' EXIT

echo "Generiere große Datenmenge in $TEMP_FILE..."

# Große Datei generieren (z.B. 500 MB mit zufälligen Daten)
dd if=/dev/urandom of="$TEMP_FILE" bs=1M count=500 2>/dev/null

echo "Temporäre Datei erstellt: $(du -h "$TEMP_FILE" | cut -f1)"

# Verarbeitung simulieren
echo "Verarbeite Daten..."
sleep 2  # Simuliert Verarbeitung

echo "Verarbeitung abgeschlossen. Temporäre Datei wird gelöscht."
exit 0

10.5.18 Verhindern der automatischen Löschung temporärer Dateien

In Debugging-Szenarien kann es nützlich sein, temporäre Dateien zu erhalten:

#!/bin/bash
# debug_temp.sh - Behält temporäre Dateien für Debugging

# Kommandozeilenargument prüfen
KEEP_TEMP=0
if [ "$1" = "--keep-temp" ]; then
    KEEP_TEMP=1
    shift
fi

# Temporäre Datei erstellen
TEMP_FILE=$(mktemp)

# Nur aufräumen, wenn nicht im Debug-Modus
if [ "$KEEP_TEMP" -eq 0 ]; then
    trap 'rm -f "$TEMP_FILE"' EXIT
else
    echo "DEBUG-MODUS: Temporäre Datei wird beibehalten: $TEMP_FILE"
fi

# ... Rest des Skripts ...

if [ "$KEEP_TEMP" -eq 1 ]; then
    echo "Temporäre Datei für Inspektion: $TEMP_FILE"
fi

exit 0

10.5.19 Process Substitution als Alternative zu temporären Dateien

Bash bietet mit Process Substitution eine elegante Alternative zu temporären Dateien für viele Anwendungsfälle:

# Statt einer temporären Datei:
TEMP_FILE=$(mktemp)
command1 > "$TEMP_FILE"
command2 < "$TEMP_FILE"
rm "$TEMP_FILE"

# Mit Process Substitution:
command2 < <(command1)

Ein praktisches Beispiel:

# Ohne Process Substitution (mit temporärer Datei)
TEMP_FILE=$(mktemp)
grep "error" logfile.txt > "$TEMP_FILE"
sort -k2 "$TEMP_FILE"
rm "$TEMP_FILE"

# Mit Process Substitution (ohne temporäre Datei)
sort -k2 <(grep "error" logfile.txt)

Process Substitution kann besonders nützlich sein, um temporäre Dateien in einfachen Szenarien zu vermeiden.

10.6 Sicheres Löschen und Ändern von Dateien

Bei der Arbeit mit Shell-Skripten ist das sichere Löschen und Ändern von Dateien ein kritischer Aspekt, besonders in Produktivumgebungen. Fehler können zum unbeabsichtigten Datenverlust oder zur Beschädigung wichtiger Dateien führen. In diesem Abschnitt werden wir Techniken und Best Practices behandeln, die sicherstellen, dass Dateioperationen zuverlässig und sicher durchgeführt werden.

10.6.1 Risiken beim Löschen und Ändern von Dateien

Bevor wir uns mit den konkreten Methoden befassen, sollten wir uns der potenziellen Risiken bewusst sein:

  1. Unbeabsichtigtes Löschen: Durch falsche Globbing-Muster oder Variablen ohne Wert
  2. Rekursives Löschen: Versehentliches Löschen ganzer Verzeichnisstrukturen
  3. Race Conditions: Probleme bei gleichzeitigem Zugriff durch mehrere Prozesse
  4. Unvollständige Änderungen: Wenn ein Skript während einer Dateiänderung unterbrochen wird
  5. Berechtigungsprobleme: Fehlende Rechte zum Ändern oder Löschen bestimmter Dateien

10.6.2 Sicheres Löschen von Dateien

10.6.2.1 Überprüfung vor dem Löschen

Eine grundlegende Sicherheitsmaßnahme ist die Überprüfung der zu löschenden Dateien:

# Unsicher: Direkt löschen
rm /pfad/zur/datei

# Sicherer: Erst prüfen, dann löschen
if [ -f "/pfad/zur/datei" ]; then
    rm "/pfad/zur/datei"
fi

Bei Verwendung von Variablen ist es besonders wichtig, diese zu validieren:

# GEFÄHRLICH: Wenn $DATEI leer ist, wird alles gelöscht!
rm -rf $DATEI

# Sicherer Ansatz:
if [ -n "$DATEI" ] && [ -e "$DATEI" ]; then
    rm -rf "$DATEI"
else
    echo "Fehler: Ungültige Datei oder Verzeichnis: '$DATEI'" >&2
    exit 1
fi

10.6.2.2 Sicherung vor dem Löschen

Eine zusätzliche Sicherheitsebene ist das Erstellen einer Sicherung vor dem Löschen:

#!/bin/bash
# safe_delete.sh - Löscht Dateien mit vorheriger Sicherung

TARGET="$1"

if [ -z "$TARGET" ]; then
    echo "Verwendung: $0 <datei_oder_verzeichnis>"
    exit 1
fi

if [ ! -e "$TARGET" ]; then
    echo "Fehler: $TARGET existiert nicht."
    exit 2
fi

# Sicherungsverzeichnis erstellen, falls es nicht existiert
BACKUP_DIR="$HOME/.trash"
mkdir -p "$BACKUP_DIR"

# Zeitstempel für eindeutige Benennung
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_NAME=$(basename "$TARGET")
BACKUP_PATH="$BACKUP_DIR/${BACKUP_NAME}_${TIMESTAMP}"

# Sicherung erstellen
if [ -d "$TARGET" ]; then
    cp -r "$TARGET" "$BACKUP_PATH"
else
    cp "$TARGET" "$BACKUP_PATH"
fi

# Erfolgsprüfung
if [ $? -eq 0 ]; then
    echo "Sicherung erstellt: $BACKUP_PATH"
    rm -rf "$TARGET"
    echo "$TARGET wurde gelöscht."
else
    echo "Fehler beim Erstellen der Sicherung. Löschvorgang abgebrochen."
    exit 3
fi

10.6.2.3 Sichere Verwendung von Wildcards

Die Verwendung von Wildcards beim Löschen kann riskant sein:

# GEFÄHRLICH: Könnte unerwartete Dateien löschen
rm -f *.log

# Sicherer: Erst anzeigen, dann bestätigen
echo "Folgende Dateien werden gelöscht:"
ls -la *.log
read -p "Fortfahren? (j/N): " CONFIRM
if [[ "$CONFIRM" =~ ^[jJ] ]]; then
    rm -f *.log
    echo "Dateien wurden gelöscht."
else
    echo "Löschvorgang abgebrochen."
fi

Eine noch sicherere Methode ist die Verwendung von find mit Überprüfung:

#!/bin/bash
# safe_wildcard_delete.sh - Sicheres Löschen mit Wildcards

PATTERN="$1"
DIR="${2:-.}"  # Standardmäßig aktuelles Verzeichnis

if [ -z "$PATTERN" ]; then
    echo "Verwendung: $0 <muster> [verzeichnis]"
    echo "Beispiel: $0 '*.tmp' /tmp"
    exit 1
fi

# Dateien finden, die dem Muster entsprechen
FILES=$(find "$DIR" -type f -name "$PATTERN")

# Prüfen, ob Dateien gefunden wurden
if [ -z "$FILES" ]; then
    echo "Keine Dateien gefunden, die dem Muster '$PATTERN' in '$DIR' entsprechen."
    exit 0
fi

# Anzahl der gefundenen Dateien
COUNT=$(echo "$FILES" | wc -l)

# Gefundene Dateien anzeigen
echo "Gefunden: $COUNT Dateien, die dem Muster '$PATTERN' entsprechen:"
echo "$FILES" | awk '{print "  - " $0}'

# Bestätigung vom Benutzer einholen
read -p "Möchten Sie diese Dateien löschen? (j/N): " CONFIRM
if [[ "$CONFIRM" =~ ^[jJ] ]]; then
    # Dateien einzeln löschen mit Statusmeldung
    echo "$FILES" | while read FILE; do
        if rm "$FILE"; then
            echo "Gelöscht: $FILE"
        else
            echo "Fehler beim Löschen: $FILE" >&2
        fi
    done
    echo "Löschvorgang abgeschlossen."
else
    echo "Löschvorgang abgebrochen."
fi

10.6.2.4 Sicherheitsmechanismen gegen kritische Löschvorgänge

Für besonders kritische Systembereiche können zusätzliche Sicherheitsmaßnahmen implementiert werden:

#!/bin/bash
# protected_delete.sh - Verhindert das Löschen kritischer Verzeichnisse

function safe_rm() {
    local target="$1"
    local full_path=$(realpath "$target")
    
    # Liste geschützter Verzeichnisse
    local protected_dirs=(
        "/"
        "/bin"
        "/boot"
        "/etc"
        "/home"
        "/lib"
        "/lib64"
        "/usr"
        "/var"
    )
    
    # Prüfen, ob das Ziel ein geschütztes Verzeichnis ist
    for dir in "${protected_dirs[@]}"; do
        if [ "$full_path" = "$dir" ]; then
            echo "FEHLER: Das Löschen des geschützten Verzeichnisses '$full_path' ist nicht erlaubt." >&2
            return 1
        fi
    done
    
    # Prüfen, ob das Ziel ein Unterverzeichnis eines geschützten Verzeichnisses ist
    for dir in "${protected_dirs[@]}"; do
        if [[ "$full_path" == "$dir"/* ]] && [[ "$full_path" != "$dir/tmp"/* ]]; then
            echo "WARNUNG: Sie versuchen, ein Unterverzeichnis von '$dir' zu löschen."
            echo "Voller Pfad: $full_path"
            read -p "Sind Sie ABSOLUT sicher? (ja/NEIN): " CONFIRM
            if [ "$CONFIRM" != "ja" ]; then
                echo "Löschvorgang abgebrochen."
                return 1
            fi
        fi
    done
    
    # Durchführen des Löschvorgangs
    rm -rf "$target"
    return $?
}

# Verwendung der Funktion
safe_rm "$1"

10.6.3 Sicheres Ändern von Dateien

10.6.3.1 Atomare Dateiersetzung

Beim Ändern einer Datei ist es sicherer, eine temporäre Datei zu erstellen und diese dann atomar zu ersetzen:

#!/bin/bash
# safe_edit.sh - Sicheres Ändern einer Konfigurationsdatei

CONFIG_FILE="$1"
SEARCH="$2"
REPLACE="$3"

if [ ! -f "$CONFIG_FILE" ]; then
    echo "Fehler: Datei '$CONFIG_FILE' existiert nicht."
    exit 1
fi

# Temporäre Datei erstellen
TEMP_FILE=$(mktemp)
trap 'rm -f "$TEMP_FILE"' EXIT

# Kopie der Originaldatei erstellen
cp "$CONFIG_FILE" "$TEMP_FILE"

# Änderungen an der temporären Datei vornehmen
sed -i "s|$SEARCH|$REPLACE|g" "$TEMP_FILE"

# Prüfen, ob die Änderungen erfolgreich waren
if grep -q "$REPLACE" "$TEMP_FILE"; then
    # Atomares Ersetzen der Originaldatei
    mv "$TEMP_FILE" "$CONFIG_FILE"
    echo "Datei wurde erfolgreich aktualisiert."
else
    echo "Fehler: Die Änderungen konnten nicht durchgeführt werden."
    exit 2
fi

Die atomare Ersetzung mit mv ist wichtig, da sie sicherstellt, dass die Datei zu keinem Zeitpunkt in einem inkonsistenten Zustand ist.

10.6.3.2 Sicherungskopien bei Änderungen

Viele Bearbeitungswerkzeuge unterstützen das automatische Erstellen von Sicherungskopien:

# Sicherungskopie mit Erweiterung .bak erstellen
sed -i.bak "s/altes_muster/neues_muster/g" datei.txt

# Bei Erfolg verbleibt die Originaldatei als datei.txt.bak

Ein robustes Skript zur Dateiänderung mit Versionierung:

#!/bin/bash
# versioned_edit.sh - Ändert Dateien mit automatischer Versionierung

FILE="$1"
SEARCH="$2"
REPLACE="$3"

if [ ! -f "$FILE" ]; then
    echo "Fehler: Datei '$FILE' existiert nicht."
    exit 1
fi

# Backup-Verzeichnis erstellen
BACKUP_DIR="$(dirname "$FILE")/.backups"
mkdir -p "$BACKUP_DIR"

# Zeitstempel für die Versionierung
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BASENAME=$(basename "$FILE")
BACKUP_FILE="$BACKUP_DIR/${BASENAME}.${TIMESTAMP}"

# Sicherungskopie erstellen
cp "$FILE" "$BACKUP_FILE"
echo "Sicherungskopie erstellt: $BACKUP_FILE"

# Temporäre Datei für die Änderungen
TEMP_FILE=$(mktemp)
trap 'rm -f "$TEMP_FILE"' EXIT

# Änderungen durchführen
sed "s|$SEARCH|$REPLACE|g" "$FILE" > "$TEMP_FILE"

# Prüfen, ob Änderungen vorgenommen wurden
if cmp -s "$FILE" "$TEMP_FILE"; then
    echo "Keine Änderungen notwendig. Die Datei bleibt unverändert."
    exit 0
fi

# Atomare Ersetzung
mv "$TEMP_FILE" "$FILE"
echo "Datei wurde erfolgreich aktualisiert."

# Auflistung der vorhandenen Versionen
echo "Verfügbare Sicherungsversionen:"
ls -lt "$BACKUP_DIR" | grep "$BASENAME" | head -5

10.6.3.3 Verwendung von Sperren bei Dateiänderungen

Bei Systemen mit mehreren Benutzern oder Prozessen können Dateisperren notwendig sein:

#!/bin/bash
# locked_edit.sh - Dateiänderung mit exklusiver Sperre

FILE="$1"
SEARCH="$2"
REPLACE="$3"

if [ ! -f "$FILE" ]; then
    echo "Fehler: Datei '$FILE' existiert nicht."
    exit 1
fi

# Sperrdatei
LOCK_FILE="${FILE}.lock"

# Funktion zum Aufräumen
cleanup() {
    rm -f "$LOCK_FILE" "$TEMP_FILE"
}

# Temporäre Datei
TEMP_FILE=$(mktemp)
trap cleanup EXIT

# Versuche, eine Sperre zu erhalten
if [ -e "$LOCK_FILE" ]; then
    # Prüfen, ob der Prozess, der die Sperre hält, noch läuft
    LOCK_PID=$(cat "$LOCK_FILE" 2>/dev/null)
    if [ -n "$LOCK_PID" ] && kill -0 "$LOCK_PID" 2>/dev/null; then
        echo "Fehler: Datei wird bereits von Prozess $LOCK_PID bearbeitet."
        exit 2
    else
        echo "Warnung: Verwaiste Sperrdatei gefunden. Wird entfernt."
        rm -f "$LOCK_FILE"
    fi
fi

# Sperre setzen
echo $$ > "$LOCK_FILE"

# Datei bearbeiten
sed "s|$SEARCH|$REPLACE|g" "$FILE" > "$TEMP_FILE"

# Änderungen übernehmen
mv "$TEMP_FILE" "$FILE"
echo "Datei wurde erfolgreich aktualisiert."

# Sperre freigeben (wird auch durch trap erledigt)
rm -f "$LOCK_FILE"

Eine robustere Implementierung würde flock oder ähnliche Werkzeuge verwenden:

#!/bin/bash
# flock_edit.sh - Dateiänderung mit flock-Sperrung

FILE="$1"
SEARCH="$2"
REPLACE="$3"

if [ ! -f "$FILE" ]; then
    echo "Fehler: Datei '$FILE' existiert nicht."
    exit 1
fi

# Temporäre Datei
TEMP_FILE=$(mktemp)
trap 'rm -f "$TEMP_FILE"' EXIT

# Exklusive Sperre mit Timeout erhalten
(
    # Versuche für 10 Sekunden, eine Sperre zu erhalten
    if ! flock -x -w 10 200; then
        echo "Fehler: Konnte keine Sperre für $FILE erhalten." >&2
        exit 1
    fi
    
    # Änderungen durchführen
    sed "s|$SEARCH|$REPLACE|g" "$FILE" > "$TEMP_FILE"
    
    # Atomare Ersetzung
    mv "$TEMP_FILE" "$FILE"
    echo "Datei wurde erfolgreich aktualisiert."
    
) 200>"${FILE}.lock"

10.6.4 Sichere in-place-Änderung großer Dateien

Bei großen Dateien kann die direkte Ersetzung effizienter sein als das vollständige Kopieren:

#!/bin/bash
# safe_inplace_edit.sh - Sichere in-place-Änderung großer Dateien

FILE="$1"
SEARCH="$2"
REPLACE="$3"

if [ ! -f "$FILE" ]; then
    echo "Fehler: Datei '$FILE' existiert nicht."
    exit 1
fi

# Sicherungskopie erstellen
cp -p "$FILE" "${FILE}.bak"

# Änderungen direkt durchführen
if sed -i "s|$SEARCH|$REPLACE|g" "$FILE"; then
    echo "Datei wurde erfolgreich aktualisiert."
    echo "Sicherungskopie: ${FILE}.bak"
else
    echo "Fehler bei der Bearbeitung. Ursprungsdatei wird wiederhergestellt."
    mv "${FILE}.bak" "$FILE"
    exit 2
fi

10.6.5 Sichere Verarbeitung sensibler Daten

Bei vertraulichen Daten sind zusätzliche Vorkehrungen zu treffen:

#!/bin/bash
# secure_data_processing.sh - Sicherer Umgang mit vertraulichen Daten

INPUT_FILE="$1"
OUTPUT_FILE="$2"

if [ ! -f "$INPUT_FILE" ]; then
    echo "Fehler: Eingabedatei '$INPUT_FILE' existiert nicht."
    exit 1
fi

# Temporäre Dateien mit restriktiven Berechtigungen
TEMP_DIR=$(mktemp -d)
chmod 700 "$TEMP_DIR"
TEMP_FILE="$TEMP_DIR/temp_data"

# Sicherstellen, dass temporäre Dateien gelöscht werden
cleanup() {
    # Sichere Löschung mit Überschreiben
    if [ -f "$TEMP_FILE" ]; then
        shred -u "$TEMP_FILE"
    fi
    rmdir "$TEMP_DIR"
}
trap cleanup EXIT

# Vertrauliche Daten verarbeiten
cp "$INPUT_FILE" "$TEMP_FILE"
chmod 600 "$TEMP_FILE"

# Hier würde die eigentliche Datenverarbeitung stattfinden
# ...

# Ergebnisse sicher speichern
mv "$TEMP_FILE" "$OUTPUT_FILE"
chmod 600 "$OUTPUT_FILE"

echo "Verarbeitung abgeschlossen. Ergebnisse in '$OUTPUT_FILE' gespeichert."

10.6.6 Sicheres permanentes Löschen sensibler Daten

Bei vertraulichen Daten reicht ein einfaches Löschen nicht immer aus:

#!/bin/bash
# secure_delete.sh - Sicheres permanentes Löschen von Dateien

FILE="$1"
if [ ! -e "$FILE" ]; then
    echo "Fehler: Datei oder Verzeichnis '$FILE' existiert nicht."
    exit 1
fi

# Bei Verzeichnissen rekursiv vorgehen
if [ -d "$FILE" ]; then
    echo "Verzeichnis erkannt. Starte rekursives sicheres Löschen..."
    find "$FILE" -type f -exec shred -uzn 3 {} \;
    find "$FILE" -depth -type d -exec rmdir {} \;
else
    # Einzelne Datei sicher löschen
    echo "Lösche Datei '$FILE' sicher..."
    shred -uzn 3 "$FILE"
fi

echo "Sicheres Löschen abgeschlossen."

10.6.7 Nachverfolgung von Änderungen mit Protokollierung

Für kritische Systeme ist es wichtig, Änderungen zu protokollieren:

#!/bin/bash
# logged_file_operations.sh - Protokolliert alle Dateioperationen

LOG_FILE="/var/log/file_operations.log"
OP_TYPE="$1"  # 'delete', 'modify', 'create'
TARGET="$2"

# Funktion zur Protokollierung
log_operation() {
    local op_type="$1"
    local target="$2"
    local user="$USER"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    local hostname=$(hostname)
    
    # Protokolleintrag erstellen
    echo "$timestamp | $hostname | $user | $op_type | $target" | \
        sudo tee -a "$LOG_FILE" > /dev/null
}

# Je nach Operationstyp unterschiedlich vorgehen
case "$OP_TYPE" in
    delete)
        if [ -e "$TARGET" ]; then
            log_operation "DELETE" "$TARGET"
            rm -rf "$TARGET"
            echo "Gelöscht: $TARGET"
        else
            echo "Fehler: $TARGET existiert nicht."
            exit 1
        fi
        ;;
    modify)
        CONTENT="$3"
        if [ -f "$TARGET" ]; then
            # Sicherungskopie erstellen
            cp -p "$TARGET" "${TARGET}.bak"
            log_operation "MODIFY" "$TARGET"
            echo "$CONTENT" > "$TARGET"
            echo "Geändert: $TARGET"
        else
            echo "Fehler: $TARGET existiert nicht oder ist kein reguläre Datei."
            exit 1
        fi
        ;;
    create)
        CONTENT="$3"
        log_operation "CREATE" "$TARGET"
        echo "$CONTENT" > "$TARGET"
        echo "Erstellt: $TARGET"
        ;;
    *)
        echo "Verwendung: $0 {delete|modify|create} <ziel> [inhalt]"
        exit 1
        ;;
esac

10.6.8 Sicheres Umbenennen und Verschieben von Dateien

Das Umbenennen und Verschieben von Dateien birgt ähnliche Risiken wie das Löschen:

#!/bin/bash
# safe_rename.sh - Sicheres Umbenennen oder Verschieben von Dateien

SOURCE="$1"
DEST="$2"

if [ -z "$SOURCE" ] || [ -z "$DEST" ]; then
    echo "Verwendung: $0 <quelldatei> <zieldatei>"
    exit 1
fi

if [ ! -e "$SOURCE" ]; then
    echo "Fehler: Quelldatei '$SOURCE' existiert nicht."
    exit 2
fi

if [ -e "$DEST" ]; then
    echo "Warnung: Zieldatei '$DEST' existiert bereits."
    read -p "Überschreiben? (j/N): " CONFIRM
    if [[ ! "$CONFIRM" =~ ^[jJ] ]]; then
        echo "Vorgang abgebrochen."
        exit 0
    fi
    
    # Sicherungskopie der Zieldatei erstellen
    BACKUP="${DEST}.bak.$(date +%Y%m%d_%H%M%S)"
    cp -p "$DEST" "$BACKUP"
    echo "Sicherungskopie erstellt: $BACKUP"
fi

# Prüfen, ob der Zielordner existiert
DEST_DIR=$(dirname "$DEST")
if [ ! -d "$DEST_DIR" ]; then
    echo "Zielverzeichnis existiert nicht. Erstelle $DEST_DIR..."
    mkdir -p "$DEST_DIR"
fi

# Umbenennen/Verschieben durchführen
if mv "$SOURCE" "$DEST"; then
    echo "Erfolgreich verschoben: $SOURCE -> $DEST"
else
    echo "Fehler beim Verschieben."
    # Wenn nötig, Zieldatei aus Backup wiederherstellen
    if [ -e "$BACKUP" ]; then
        mv "$BACKUP" "$DEST"
        echo "Zieldatei aus Sicherung wiederhergestellt."
    fi
    exit 3
fi

10.6.9 Best Practices für sicheres Löschen und Ändern von Dateien

Zusammenfassend lassen sich folgende Best Practices für den sicheren Umgang mit Dateien in Shell-Skripten festhalten:

  1. Überprüfen Sie immer die Existenz von Dateien und Verzeichnissen, bevor Sie sie manipulieren.

  2. Validieren Sie Variablen, besonders wenn sie für Löschvorgänge verwendet werden.

  3. Erstellen Sie Sicherungskopien vor kritischen Änderungen oder Löschvorgängen.

  4. Verwenden Sie atomare Operationen wie mv für die Ersetzung von Dateien.

  5. Implementieren Sie Transaktionsmechanismen für komplexe Änderungen.

  6. Verwenden Sie Sperren bei gleichzeitigem Zugriff durch mehrere Prozesse.

  7. Protokollieren Sie wichtige Änderungen für die spätere Nachverfolgung.

  8. Behandeln Sie vertrauliche Daten besonders sorgfältig und verwenden Sie sichere Löschmethoden.

  9. Implementieren Sie Rückfalllösungen für den Fall, dass etwas schiefgeht.

  10. Testen Sie kritische Skripte in einer sicheren Umgebung, bevor Sie sie in Produktion einsetzen.

10.7 Berechtigungsmanagement und Besitzrechte

Das Verständnis und die effektive Verwaltung von Dateiberechtigungen und Besitzrechten sind grundlegende Fähigkeiten für die Shell-Programmierung unter Linux und Unix-ähnlichen Betriebssystemen. In diesem Abschnitt werden wir uns mit Konzepten, Werkzeugen und Techniken befassen, die für das Berechtigungsmanagement in Shell-Skripten relevant sind.

10.7.1 Grundlegende Berechtigungskonzepte

Das Unix-Berechtigungsmodell basiert auf drei Berechtigungstypen und drei Benutzerklassen:

10.7.1.1 Berechtigungstypen:

10.7.1.2 Benutzerklassen:

10.7.2 Anzeigen von Berechtigungen

Die Berechtigungen können mit dem ls Befehl angezeigt werden:

$ ls -l datei.txt
-rw-r--r-- 1 benutzer gruppe 1024 Apr 10 14:30 datei.txt

Das erste Zeichen (-) gibt den Dateityp an: - -: Reguläre Datei - d: Verzeichnis - l: Symbolischer Link - c: Zeichenorientiertes Gerät - b: Blockorientiertes Gerät - p: Named Pipe (FIFO) - s: Socket

Die nächsten neun Zeichen stellen die Berechtigungen dar, in Dreiergruppen: - Erste Gruppe (rw-): Eigentümerberechtigungen (Lesen, Schreiben, nicht Ausführen) - Zweite Gruppe (r--): Gruppenberechtigungen (nur Lesen) - Dritte Gruppe (r--): Berechtigungen für andere (nur Lesen)

10.7.3 Ändern von Berechtigungen mit chmod

Der chmod-Befehl (change mode) wird verwendet, um Dateiberechtigungen zu ändern:

10.7.3.1 Symbolische Notation:

# Eigentümer erhält Leserecht
chmod u+r datei.txt

# Gruppe erhält Schreibrecht
chmod g+w datei.txt

# Andere verlieren Ausführungsrecht
chmod o-x datei.txt

# Allen Berechtigungsklassen Leserecht geben
chmod a+r datei.txt

# Mehrere Änderungen auf einmal
chmod u+rwx,g+rx,o+r datei.txt

10.7.3.2 Numerische (oktale) Notation:

Jeder Berechtigungstyp hat einen numerischen Wert: - Lesen (r) = 4 - Schreiben (w) = 2 - Ausführen (x) = 1

Die Summe dieser Werte für jede Benutzerklasse ergibt die oktale Darstellung:

# rwxr-xr-- (Eigentümer: rwx=7, Gruppe: r-x=5, Andere: r--=4)
chmod 754 datei.txt

# Häufig verwendete Oktalmodi:
chmod 755 datei.txt  # rwxr-xr-x (typisch für ausführbare Dateien/Skripte)
chmod 644 datei.txt  # rw-r--r-- (typisch für reguläre Dateien)
chmod 600 datei.txt  # rw------- (private Dateien)
chmod 777 datei.txt  # rwxrwxrwx (volle Berechtigungen für alle - vermeiden!)

10.7.4 Ändern von Eigentümer und Gruppe mit chown und chgrp

Der Eigentümer und die Gruppe einer Datei können mit chown und chgrp geändert werden:

# Eigentümer ändern
chown neuer_benutzer datei.txt

# Gruppe ändern
chgrp neue_gruppe datei.txt

# Eigentümer und Gruppe gleichzeitig ändern
chown neuer_benutzer:neue_gruppe datei.txt

10.7.5 Rekursive Änderungen von Berechtigungen und Eigentümern

Für Verzeichnisse und deren Inhalt können Änderungen rekursiv angewendet werden:

# Rekursives Ändern von Berechtigungen
chmod -R 755 verzeichnis/

# Rekursives Ändern von Eigentümer und Gruppe
chown -R benutzer:gruppe verzeichnis/

10.7.6 Spezielle Berechtigungen: SUID, SGID und Sticky Bit

Neben den grundlegenden Berechtigungen gibt es drei spezielle Berechtigungen:

  1. SUID (Set User ID): Wenn auf eine ausführbare Datei gesetzt, wird sie mit den Rechten des Eigentümers ausgeführt, nicht mit den Rechten des ausführenden Benutzers.
  2. SGID (Set Group ID): Ähnlich wie SUID, aber für Gruppen. Bei Verzeichnissen bewirkt es, dass neue Dateien die Gruppe des Verzeichnisses erben.
  3. Sticky Bit: Wenn auf ein Verzeichnis gesetzt, können Dateien nur vom Eigentümer, vom Verzeichniseigentümer oder vom Root-Benutzer gelöscht werden.
# SUID setzen (4 voranstellen)
chmod 4755 datei.txt  # Dargestellt als -rwsr-xr-x

# SGID setzen (2 voranstellen)
chmod 2755 datei.txt  # Dargestellt als -rwxr-sr-x

# Sticky Bit setzen (1 voranstellen)
chmod 1777 verzeichnis/  # Dargestellt als drwxrwxrwt

# Symbolische Notation für spezielle Berechtigungen
chmod u+s datei.txt      # SUID setzen
chmod g+s datei.txt      # SGID setzen
chmod +t verzeichnis/    # Sticky Bit setzen

10.7.7 Spezielle Berechtigungen verstehen und sicher verwenden

Die speziellen Berechtigungen sind mächtig, aber potenziell gefährlich, wenn sie falsch verwendet werden:

10.7.7.1 SUID-Beispiel:

# Das passwd-Programm verwendet SUID, um die /etc/shadow-Datei zu ändern
$ ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root 59640 Mar 22 14:23 /usr/bin/passwd

10.7.7.2 SGID-Beispiel für ein Verzeichnis:

#!/bin/bash
# create_collaboration_dir.sh - Erstellt ein Verzeichnis für Gruppenarbeit

GROUP="developers"
DIR="/shared/projects"

# Verzeichnis erstellen und Gruppe setzen
mkdir -p "$DIR"
chgrp "$GROUP" "$DIR"

# SGID setzen, damit neue Dateien zur developers-Gruppe gehören
chmod g+s "$DIR"

# Volle Berechtigungen für Mitglieder der Gruppe
chmod 770 "$DIR"

echo "Kollaborationsverzeichnis erstellt: $DIR"
echo "Alle in der Gruppe $GROUP können dort zusammenarbeiten."

10.7.7.3 Sticky Bit-Beispiel:

# /tmp verwendet das Sticky Bit, damit Benutzer nicht die Dateien anderer löschen können
$ ls -ld /tmp
drwxrwxrwt 20 root root 4096 Apr 10 15:45 /tmp

10.7.8 Standardberechtigungen mit umask

Die umask legt die Standardberechtigungen für neu erstellte Dateien und Verzeichnisse fest:

# Aktuelle umask anzeigen
umask  # Typisches Ergebnis: 0022

# umask setzen (z.B. auf 027 für mehr Privatsphäre)
umask 027

Die umask ist eine Bitmaske, die von den maximalen Standardberechtigungen (666 für Dateien, 777 für Verzeichnisse) subtrahiert wird:

10.7.9 Umask in Skripten verwenden

In Skripten kann die umask temporär geändert werden:

#!/bin/bash
# secure_file_creation.sh - Erstellt Dateien mit restriktiven Berechtigungen

# Ursprüngliche umask speichern
OLD_UMASK=$(umask)

# Temporär restriktive umask setzen (nur Eigentümer hat Zugriff)
umask 077

echo "Erstelle private Konfigurationsdatei..."
echo "secret_key=12345" > config.txt

# umask wiederherstellen
umask $OLD_UMASK

echo "Private Datei erstellt. Berechtigungen:"
ls -l config.txt

10.7.10 Berechtigungsprüfungen in Skripten

In Skripten ist es wichtig, Berechtigungen zu prüfen, bevor Operationen durchgeführt werden:

#!/bin/bash
# permission_check.sh - Prüft und handhabt Berechtigungen

FILE="$1"
if [ -z "$FILE" ]; then
    echo "Verwendung: $0 <datei>"
    exit 1
fi

# Existenz prüfen
if [ ! -e "$FILE" ]; then
    echo "Fehler: Datei existiert nicht: $FILE"
    exit 2
fi

# Berechtigungen prüfen
if [ ! -r "$FILE" ]; then
    echo "Fehler: Keine Leseberechtigung für $FILE"
    exit 3
fi

if [ ! -w "$FILE" ]; then
    echo "Warnung: Keine Schreibberechtigung für $FILE"
    read -p "Möchten Sie die Schreibberechtigung hinzufügen? (j/N): " ANSWER
    if [[ "$ANSWER" =~ ^[jJ] ]]; then
        chmod u+w "$FILE"
        echo "Schreibberechtigung hinzugefügt."
    else
        echo "Fortfahren im schreibgeschützten Modus."
    fi
fi

# Datei verarbeiten
echo "Verarbeite $FILE..."
# ... weitere Aktionen ...

10.7.11 Erweiterte Zugriffsberechtigungen (ACLs)

Access Control Lists (ACLs) bieten feinere Kontrolle über Dateiberechtigungen als das traditionelle Unix-Berechtigungsmodell:

# ACLs für eine Datei anzeigen
getfacl datei.txt

# Benutzer hinzufügen mit spezifischen Rechten
setfacl -m u:benutzer:rwx datei.txt

# Gruppe hinzufügen mit spezifischen Rechten
setfacl -m g:gruppe:r-x datei.txt

# Standardberechtigungen für ein Verzeichnis festlegen
setfacl -d -m u:benutzer:rwx verzeichnis/

Ein Skript zur ACL-Verwaltung:

#!/bin/bash
# manage_acls.sh - Verwaltet ACLs für ein Projekt

PROJECT_DIR="$1"
if [ ! -d "$PROJECT_DIR" ]; then
    echo "Fehler: Verzeichnis nicht gefunden: $PROJECT_DIR"
    exit 1
fi

# Grundlegende Berechtigungen setzen
chmod 750 "$PROJECT_DIR"

# ACLs für Benutzerrollen festlegen
# Projektleiter haben volle Kontrolle
setfacl -m u:projektleiter:rwx "$PROJECT_DIR"

# Entwickler haben Lese- und Ausführungsrechte, aber keine Schreibrechte im Hauptverzeichnis
setfacl -m g:entwickler:r-x "$PROJECT_DIR"

# QA-Team hat nur Leserechte
setfacl -m g:qa:r-x "$PROJECT_DIR"

# Standardberechtigungen für neue Dateien im Verzeichnis
setfacl -d -m u:projektleiter:rwx "$PROJECT_DIR"
setfacl -d -m g:entwickler:rw- "$PROJECT_DIR"
setfacl -d -m g:qa:r-- "$PROJECT_DIR"

# Anzeige der resultierenden ACLs
echo "ACLs für $PROJECT_DIR gesetzt:"
getfacl "$PROJECT_DIR"

10.7.12 Sonderfall: Root-Berechtigungen

Der Root-Benutzer (UID 0) hat uneingeschränkten Zugriff auf das System. In Skripten, die erweiterte Berechtigungen benötigen, gibt es verschiedene Ansätze:

10.7.12.1 1. sudo verwenden:

#!/bin/bash
# Die operation_as_root-Funktion führt Befehle mit Root-Rechten aus
operation_as_root() {
    local cmd="$@"
    
    if [ "$(id -u)" -eq 0 ]; then
        # Skript läuft bereits als Root
        eval "$cmd"
    else
        # Skript benötigt Root-Rechte
        sudo $cmd
    fi
}

# Beispielverwendung
echo "Installiere Anwendung..."
operation_as_root apt-get install -y beispiel-paket

echo "Erstelle System-Verzeichnis..."
operation_as_root mkdir -p /opt/beispiel
operation_as_root chown $USER:$USER /opt/beispiel

10.7.12.2 2. Das SUID-Bit (mit Vorsicht verwenden):

#!/bin/bash
# Hinweis: Dieses Skript muss mit Root-Rechten erstellt werden
# und dann das SUID-Bit erhalten:
# sudo chown root:root skript.sh
# sudo chmod 4755 skript.sh

# Prüfen, ob das Skript mit SUID läuft
if [ "$(id -u)" -ne 0 ]; then
    echo "Dieses Skript benötigt Root-Rechte."
    exit 1
fi

# Kritische Operationen durchführen
echo "Führe Systemoperation als Root durch..."
# ... spezifische Operationen ...

10.7.13 Automatisierte Berechtigungsverwaltung

Für umfassendere Verwaltung von Berechtigungen können Skripte eingesetzt werden:

#!/bin/bash
# permission_manager.sh - Verwaltung von Berechtigungen basierend auf Vorlagen

CONFIG_FILE="$1"
TARGET_DIR="$2"

if [ ! -f "$CONFIG_FILE" ] || [ ! -d "$TARGET_DIR" ]; then
    echo "Verwendung: $0 <konfigurationsdatei> <zielverzeichnis>"
    exit 1
fi

# Konfigurationsdatei laden
source "$CONFIG_FILE"

# Standardwerte, wenn nicht in der Konfiguration definiert
: "${DIR_PERM:=755}"
: "${FILE_PERM:=644}"
: "${OWNER:=$USER}"
: "${GROUP:=$(id -gn)}"

echo "Wende Berechtigungen an auf $TARGET_DIR..."
echo "  Verzeichnisberechtigungen: $DIR_PERM"
echo "  Dateiberechtigungen: $FILE_PERM"
echo "  Eigentümer: $OWNER"
echo "  Gruppe: $GROUP"

# Eigentümer und Gruppe setzen
if [ "$(id -u)" -eq 0 ]; then
    chown -R "$OWNER:$GROUP" "$TARGET_DIR"
else
    sudo chown -R "$OWNER:$GROUP" "$TARGET_DIR"
fi

# Berechtigungen für verschiedene Dateitypen setzen
find "$TARGET_DIR" -type d -exec chmod "$DIR_PERM" {} \;

# Reguläre Dateien
find "$TARGET_DIR" -type f -exec chmod "$FILE_PERM" {} \;

# Spezielle Dateitypen basierend auf Konfiguration
if [ -n "$SCRIPT_PERM" ]; then
    find "$TARGET_DIR" -type f -name "*.sh" -exec chmod "$SCRIPT_PERM" {} \;
fi

if [ -n "$CONFIG_FILE_PERM" ]; then
    find "$TARGET_DIR" -type f -name "*.conf" -name "*.cfg" -exec chmod "$CONFIG_FILE_PERM" {} \;
fi

echo "Berechtigungen erfolgreich angewendet."

Die dazugehörige Konfigurationsdatei (permission_config.sh):

# Berechtigungskonfiguration
DIR_PERM=750
FILE_PERM=640
SCRIPT_PERM=755
CONFIG_FILE_PERM=600
OWNER=appuser
GROUP=appgroup

10.7.14 Sichere Veröffentlichung von Dateien und Verzeichnissen

Bei der Bereitstellung von Dateien für andere Benutzer sind passende Berechtigungen wichtig:

#!/bin/bash
# publish_files.sh - Stellt Dateien sicher für andere bereit

SOURCE_DIR="$1"
PUBLIC_DIR="$2"
ACCESS_LEVEL="${3:-read}"  # Standard: nur Lesezugriff

if [ ! -d "$SOURCE_DIR" ] || [ -z "$PUBLIC_DIR" ]; then
    echo "Verwendung: $0 <quellverzeichnis> <öffentliches_verzeichnis> [access_level]"
    echo "  access_level: read (Standard), write, execute"
    exit 1
fi

# Zielverzeichnis erstellen, falls es nicht existiert
mkdir -p "$PUBLIC_DIR"

# Dateien kopieren
rsync -av --progress "$SOURCE_DIR/" "$PUBLIC_DIR/"

# Berechtigungen basierend auf Zugriffsebene setzen
case "$ACCESS_LEVEL" in
    read)
        # Lesezugriff für alle
        find "$PUBLIC_DIR" -type d -exec chmod 755 {} \;
        find "$PUBLIC_DIR" -type f -exec chmod 644 {} \;
        ;;
    write)
        # Lese-/Schreibzugriff für Gruppe
        find "$PUBLIC_DIR" -type d -exec chmod 775 {} \;
        find "$PUBLIC_DIR" -type f -exec chmod 664 {} \;
        ;;
    execute)
        # Lese-/Ausführungszugriff für alle, Schreibzugriff für Eigentümer
        find "$PUBLIC_DIR" -type d -exec chmod 755 {} \;
        find "$PUBLIC_DIR" -type f -name "*.sh" -exec chmod 755 {} \;
        find "$PUBLIC_DIR" -type f -not -name "*.sh" -exec chmod 644 {} \;
        ;;
    *)
        echo "Ungültige Zugriffsebene: $ACCESS_LEVEL"
        exit 2
        ;;
esac

echo "Dateien wurden veröffentlicht in: $PUBLIC_DIR"
echo "Zugriffsebene: $ACCESS_LEVEL"

10.7.15 Berechtigungsprobleme diagnostizieren

Wenn Probleme mit Berechtigungen auftreten, kann ein Diagnosewerkzeug hilfreich sein:

#!/bin/bash
# check_permissions.sh - Diagnostiziert Berechtigungsprobleme

TARGET="$1"
USER="${2:-$USER}"

if [ -z "$TARGET" ]; then
    echo "Verwendung: $0 <datei_oder_verzeichnis> [benutzer]"
    exit 1
fi

if [ ! -e "$TARGET" ]; then
    echo "Fehler: $TARGET existiert nicht."
    exit 2
fi

echo "Berechtigungsdiagnose für: $TARGET"
echo "===================================="

# Allgemeine Informationen
echo -e "\n1. Grundlegende Informationen:"
ls -la "$TARGET"

# Eigentümer und Gruppe
OWNER=$(stat -c "%U" "$TARGET")
GROUP=$(stat -c "%G" "$TARGET")
echo -e "\n2. Eigentümer: $OWNER, Gruppe: $GROUP"

# Überprüfen, ob der angegebene Benutzer der Eigentümer ist
if [ "$USER" = "$OWNER" ]; then
    echo "   $USER ist der Eigentümer."
else
    echo "   $USER ist NICHT der Eigentümer."
fi

# Überprüfen, ob der Benutzer Mitglied der Gruppe ist
if groups "$USER" | grep -q "\b$GROUP\b"; then
    echo "   $USER ist Mitglied der Gruppe $GROUP."
else
    echo "   $USER ist KEIN Mitglied der Gruppe $GROUP."
fi

# Berechtigungsstatus für den Benutzer
echo -e "\n3. Berechtigungsstatus für $USER:"
if [ -r "$TARGET" ]; then
    echo "   - Lesbar: Ja"
else
    echo "   - Lesbar: Nein"
fi

if [ -w "$TARGET" ]; then
    echo "   - Schreibbar: Ja"
else
    echo "   - Schreibbar: Nein"
fi

if [ -x "$TARGET" ]; then
    echo "   - Ausführbar/Durchsuchbar: Ja"
else
    echo "   - Ausführbar/Durchsuchbar: Nein"
fi

# Pfad-Berechtigungen prüfen
echo -e "\n4. Pfadberechtigungen:"
PARENT_DIR=$(dirname "$TARGET")
echo "   Übergeordnetes Verzeichnis: $PARENT_DIR"
if [ -x "$PARENT_DIR" ]; then
    echo "   - Durchsuchbar: Ja"
else
    echo "   - Durchsuchbar: Nein (Problem: Zugriff auf $TARGET nicht möglich)"
fi

# ACL-Informationen, falls vorhanden
echo -e "\n5. ACL-Informationen (falls unterstützt):"
if command -v getfacl >/dev/null; then
    getfacl "$TARGET" 2>/dev/null || echo "   Keine ACLs gefunden oder nicht unterstützt."
else
    echo "   getfacl nicht verfügbar. ACLs können nicht überprüft werden."
fi

# Spezielle Bits prüfen
echo -e "\n6. Spezielle Berechtigungen:"
PERM_BITS=$(stat -c "%a" "$TARGET")
if [[ $PERM_BITS =~ ^[1-7][0-7][0-7][0-7]$ ]]; then
    # 4-stellig, erstes Bit ist speziell
    SPECIAL_BIT=${PERM_BITS:0:1}
    case "$SPECIAL_BIT" in
        1) echo "   - Sticky Bit: Gesetzt" ;;
        2) echo "   - SGID: Gesetzt" ;;
        4) echo "   - SUID: Gesetzt" ;;
        3) echo "   - Sticky Bit + SGID: Gesetzt" ;;
        5) echo "   - SUID + Sticky Bit: Gesetzt" ;;
        6) echo "   - SUID + SGID: Gesetzt" ;;
        7) echo "   - SUID + SGID + Sticky Bit: Gesetzt" ;;
    esac
else
    # SELinux oder andere spezielle Kontexte
    stat -c "%C" "$TARGET" 2>/dev/null && echo "   SELinux-Kontext gefunden."
fi

echo -e "\n7. Empfehlungen:"
if [ ! -r "$TARGET" ] && [ "$USER" != "$OWNER" ] && ! groups "$USER" | grep -q "\b$GROUP\b"; then
    echo "   - Um Lesezugriff zu gewähren: sudo chmod o+r $TARGET"
    echo "   - Alternativ: sudo chown $USER:$(id -gn $USER) $TARGET"
    echo "   - Oder: sudo usermod -a -G $GROUP $USER (Benutzer zur Gruppe hinzufügen)"
fi

if [ ! -w "$TARGET" ] && [ "$USER" != "$OWNER" ] && ! groups "$USER" | grep -q "\b$GROUP\b"; then
    echo "   - Um Schreibzugriff zu gewähren: sudo chmod o+w $TARGET (nicht empfohlen)"
    echo "   - Besser: sudo chown $USER:$(id -gn $USER) $TARGET"
    echo "   - Oder für Gruppenarbeit: sudo chmod g+w $TARGET und sudo usermod -a -G $GROUP $USER"
fi

if [ -d "$TARGET" ] && [ ! -x "$TARGET" ]; then
    echo "   - Für Verzeichniszugriff: sudo chmod +x $TARGET"
fi

10.7.16 Berechtigungstemplates für verschiedene Anwendungsfälle

Je nach Anwendungsfall sind unterschiedliche Berechtigungen angebracht:

10.7.16.1 Webserver-Berechtigungen:

#!/bin/bash
# set_web_permissions.sh - Setzt Berechtigungen für Webinhalte

WEB_ROOT="$1"
if [ ! -d "$WEB_ROOT" ]; then
    echo "Verwendung: $0 <web_root_verzeichnis>"
    exit 1
fi

# Webserver-Benutzer und -Gruppe (anpassen je nach System)
if [ -f /etc/debian_version ]; then
    WEB_USER="www-data"
    WEB_GROUP="www-data"
elif [ -f /etc/redhat-release ]; then
    WEB_USER="apache"
    WEB_GROUP="apache"
else
    WEB_USER="www-data"  # Fallback
    WEB_GROUP="www-data"
fi

echo "Setze Berechtigungen für Webinhalte in $WEB_ROOT"
echo "Webserver läuft als: $WEB_USER:$WEB_GROUP"

# Eigentümer und Gruppe setzen
chown -R "$WEB_USER:$WEB_GROUP" "$WEB_ROOT"

# Basisberechtigungen
find "$WEB_ROOT" -type d -exec chmod 755 {} \;
find "$WEB_ROOT" -type f -exec chmod 644 {} \;

# Spezielle Verzeichnisse mit Schreibzugriff für den Webserver
for dir in "$WEB_ROOT/uploads" "$WEB_ROOT/cache" "$WEB_ROOT/logs"; do
    if [ -d "$dir" ]; then
        chmod -R 775 "$dir"
        echo "Schreibrechte für $dir gesetzt."
    else
        mkdir -p "$dir"
        chown "$WEB_USER:$WEB_GROUP" "$dir"
        chmod 775 "$dir"
        echo "Verzeichnis $dir erstellt mit Schreibrechten."
    fi
done

# Konfigurationsdateien schützen
find "$WEB_ROOT" -name "*.conf" -o -name "config.php" -o -name "wp-config.php" -exec chmod 640 {} \;
echo "Konfigurationsdateien geschützt."

echo "Webserver-Berechtigungen erfolgreich gesetzt."