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.
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"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 ../configFolgende 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.
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."
fiEine 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 |
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# 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"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"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: gzFü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äumenDer Befehl mktemp erstellt standardmäßig Dateien im
System-Temp-Verzeichnis (üblicherweise /tmp) mit
einzigartigen Namen.
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" {} \;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 {} \+# 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"# 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."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 0Beim 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.
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 |
*)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?)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.[...])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 SyntaxDie Bash bietet auch erweiterte Globbing-Muster, die jedoch standardmäßig deaktiviert sind. Sie müssen aktiviert werden mit:
# Erweiterte Globbing-Muster aktivieren
shopt -s extglobMit 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])*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."Beim Umgang mit Wildcards in Skripten gibt es wichtige Feinheiten zu beachten:
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."
fiUmgang 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"
doneRekursives 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 globstarGlobbing-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)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.txtBrace 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}Obwohl Globbing-Muster ähnlich wie reguläre Ausdrücke aussehen, gibt es wichtige Unterschiede:
*,
? und [...] mit einer anderen Semantik als
RegEx.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 |
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 0Bei 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.
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.
# 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 -7Der 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 {} \;-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 {} \+xargsFü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 {}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"Einige Standardbefehle unterstützen rekursive Operationen direkt durch spezielle Optionen:
# Rekursives Kopieren eines Verzeichnisses mit allen Unterverzeichnissen
cp -r /quellverzeichnis /zielverzeichnis
# Rekursives Kopieren mit Erhaltung von Attributen
cp -a /quellverzeichnis /zielverzeichnisDie Option -a (archivieren) ist eine Kombination aus
-r (rekursiv), -p (Erhaltung von
Dateieigenschaften) und weiteren Optionen und wird oft für Backups
verwendet.
# 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/verzeichnisBeim 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."# 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 {} \;# 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--recursive bei GNU-BefehlenViele 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' {} \;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 globstarDie 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.
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 -5Das 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"
doneFü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)/Rekursive Operationen können bei großen Verzeichnisstrukturen ressourcenintensiv sein. Hier einige Strategien zur Optimierung:
# 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 "*/.*/*"# Alle Verzeichnisse außer './temp' und './cache' durchsuchen
find . \( -name temp -o -name cache \) -prune -o -type f -name "*.py" -print# 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#!/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#!/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#!/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 0Die 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.
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:
-maxdepth
oder -mindepth-name, -size, -type)-print, -delete,
-exec)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*"# 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 cDiese können kombiniert werden, um z.B. alle nicht-regulären Dateien zu finden:
find /pfad/zum/verzeichnis -not -type fDie Größeneinheiten bei find sind:
c: Bytesk: Kilobytes (1024 Bytes)M: Megabytes (1024 Kilobytes)G: Gigabytes (1024 Megabytes)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 -10MEin 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)}'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 +14Fü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 -30Noch 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/zeitstempelEin 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"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 -4000Ein 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"# 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 -nogroupEin 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
;;
esacDie 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"# 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# 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 -deleteEin 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."
fiDie 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"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 -printEin 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" {} \;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
;;
esacNeben find gibt es weitere nützliche Befehle für
spezialisierte Suchen:
# Schnelle Suche nach Dateinamen
locate filename.txt
# Aktualisieren der locate-Datenbank
sudo updatedbDer 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# Installation (Ubuntu/Debian)
# sudo apt install fzf
# Interaktive Dateisuche
fzf
# Mit Vorschau
fzf --preview 'cat {}'
# Als Filter in Pipe
find . -type f | fzfTemporä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.
Temporäre Dateien sollten folgende Eigenschaften haben:
Unter Linux und anderen Unix-ähnlichen Betriebssystemen ist
/tmp das Standardverzeichnis für temporäre Dateien:
/tmp oft als
tmpfs-Dateisystem im RAM# Einfacher Zugriff auf /tmp
echo "Temporäre Daten" > /tmp/meine_temp_datei
# Prüfen des Dateisystem-Typs von /tmp
df -T /tmpNeben /tmp gibt es auch /var/tmp, das
ebenfalls für temporäre Dateien gedacht ist, aber in der Regel zwischen
Systemneustarts erhalten bleibt.
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.
#!/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 0Die 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 TERMHäufig verwendete Signale für trap:
EXIT: Wird ausgelöst, wenn das Skript beendet wird
(normal oder durch Fehler)INT: Wird ausgelöst, wenn der Benutzer mit Ctrl+C
abbricht (SIGINT)TERM: Wird ausgelöst, wenn das Skript mit Befehlen wie
kill beendet wird (SIGTERM)ERR: Wird ausgelöst, wenn ein Befehl einen Fehler
zurückgibt (nicht Null)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 0Manchmal 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 schreibbarTemporäre Dateien können Sicherheitsrisiken darstellen, wenn sie nicht korrekt verwaltet werden:
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 sieIn 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"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 0Die 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 erstelltEin 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 ...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.logFü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
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 0Fü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"' EXITHä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 0Bei 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 0In 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 0Bash 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.
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.
Bevor wir uns mit den konkreten Methoden befassen, sollten wir uns der potenziellen Risiken bewusst sein:
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"
fiBei 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
fiEine 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
fiDie 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."
fiEine 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."
fiFü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"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
fiDie atomare Ersetzung mit mv ist wichtig, da sie
sicherstellt, dass die Datei zu keinem Zeitpunkt in einem inkonsistenten
Zustand ist.
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.bakEin 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 -5Bei 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"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
fiBei 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."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."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
;;
esacDas 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
fiZusammenfassend lassen sich folgende Best Practices für den sicheren Umgang mit Dateien in Shell-Skripten festhalten:
Überprüfen Sie immer die Existenz von Dateien und Verzeichnissen, bevor Sie sie manipulieren.
Validieren Sie Variablen, besonders wenn sie für Löschvorgänge verwendet werden.
Erstellen Sie Sicherungskopien vor kritischen Änderungen oder Löschvorgängen.
Verwenden Sie atomare Operationen wie
mv für die Ersetzung von Dateien.
Implementieren Sie Transaktionsmechanismen für komplexe Änderungen.
Verwenden Sie Sperren bei gleichzeitigem Zugriff durch mehrere Prozesse.
Protokollieren Sie wichtige Änderungen für die spätere Nachverfolgung.
Behandeln Sie vertrauliche Daten besonders sorgfältig und verwenden Sie sichere Löschmethoden.
Implementieren Sie Rückfalllösungen für den Fall, dass etwas schiefgeht.
Testen Sie kritische Skripte in einer sicheren Umgebung, bevor Sie sie in Produktion einsetzen.
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.
Das Unix-Berechtigungsmodell basiert auf drei Berechtigungstypen und drei Benutzerklassen:
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.txtDas 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)
Der chmod-Befehl (change mode) wird verwendet, um
Dateiberechtigungen zu ändern:
# 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.txtJeder 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!)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.txtFü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/Neben den grundlegenden Berechtigungen gibt es drei spezielle Berechtigungen:
# 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 setzenDie speziellen Berechtigungen sind mächtig, aber potenziell gefährlich, wenn sie falsch verwendet werden:
# 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#!/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."# /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 /tmpDie 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 027Die umask ist eine Bitmaske, die von den maximalen Standardberechtigungen (666 für Dateien, 777 für Verzeichnisse) subtrahiert wird:
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.txtIn 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 ...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"Der Root-Benutzer (UID 0) hat uneingeschränkten Zugriff auf das System. In Skripten, die erweiterte Berechtigungen benötigen, gibt es verschiedene Ansätze:
#!/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#!/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 ...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=appgroupBei 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"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"
fiJe nach Anwendungsfall sind unterschiedliche Berechtigungen angebracht:
#!/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."