Größenänderungen von Ordnern über die Zeit automatisch erfassen

Tanne

Cadet 4th Year
Registriert
Apr. 2024
Beiträge
78
Hallo,

gibt es ein Programm/Befehl, mit dem man automatisch feststellen kann, wie sich über die Zeit die Größe eines Ordners verändert und an welchen Unterordnern/Dateien das liegt (also welche sich in der Größe ändern/neu hinzukommen/wegfallen)? Also nicht jedes Detail, aber vielleicht ab 50 MB Größenänderung...
Also irgendwas, was alle paar Stunden/Tage/... z.B. mitprotokolliert und vergleicht
 
Bau Dir ein Script und lass es taeglich laufen.

https://sixpak.de/ordnergroessen-in-linux/

Das Ergebnis laesst Du in eine Datei schreiben.

Welche Ordner sind das konkret wo die genannten 50 MB was ausmachen?
 
Hab einfach mal ChatGPT dafür genutzt mir ein bash Script zu schreiben und getestet auf Linux bash shell, funktioniert für das was du brauchst denke ich:


Bash:
#!/bin/bash

# Ordnerpfad, den du überwachen möchtest
ORDNER="/pfad/zum/ordner"
# Pfad zur Protokolldatei
LOG_FILE="/pfad/zur/logdatei.txt"
# Temporäre Datei, die die vorherigen Dateigrößen speichert
PREV_FILE="/tmp/prev_sizes.txt"
# Temporäre Datei, die die aktuellen Dateigrößen speichert
CUR_FILE="/tmp/current_sizes.txt"

# Funktion zur Berechnung der Ordnergröße im menschenlesbaren Format
function ordner_groesse {
    du -sh "$ORDNER" | awk '{print $1}'
}

# Funktion zur Auflistung der Dateigrößen im menschenlesbaren Format
function dateien_groessen {
    find "$ORDNER" -type f -exec stat --format="%s %n" {} \; | awk '{ size=$1/1024; printf "%.2f KB %s\n", size, $2 }' | sort > "$CUR_FILE"
}

# Prüfe, ob es die vorherige Dateigrößendatei gibt, falls nicht erstelle sie
if [[ ! -f "$PREV_FILE" ]]; then
    touch "$PREV_FILE"
fi

# Speichere die aktuellen Dateigrößen
dateien_groessen

# Berechne die Ordnergröße
ORDNER_GROESSE=$(ordner_groesse)

# Falls es keine vorherige Logdatei gibt, erstelle sie
if [[ ! -f "$LOG_FILE" ]]; then
    echo "Logfile created on $(date)" > "$LOG_FILE"
fi

# Füge die aktuelle Ordnergröße in die Protokolldatei hinzu
echo "$(date): Ordnergröße: $ORDNER_GROESSE" >> "$LOG_FILE"

# Vergleiche die Dateigrößen
echo "Dateien mit Größenänderungen:" >> "$LOG_FILE"
diff "$PREV_FILE" "$CUR_FILE" | grep "^[><]" >> "$LOG_FILE"

# Kopiere die aktuellen Dateigrößen für den nächsten Durchlauf
cp "$CUR_FILE" "$PREV_FILE"

----

Mit Filter:

Bash:
#!/bin/bash

# Ordnerpfad, den du überwachen möchtest
ORDNER="/pfad/zum/ordner"
# Pfad zur Protokolldatei
LOG_FILE="/pfad/zur/logdatei.txt"
# Temporäre Datei, die die vorherigen Dateigrößen speichert
PREV_FILE="/tmp/prev_sizes.txt"
# Temporäre Datei, die die aktuellen Dateigrößen speichert
CUR_FILE="/tmp/current_sizes.txt"
# Minimum Änderungsgröße in MB
MIN_CHANGE_MB=50

# Funktion zur Berechnung der Ordnergröße im menschenlesbaren Format
function ordner_groesse {
    du -sh "$ORDNER" | awk '{print $1}'
}

# Funktion zur Auflistung der Dateigrößen in Bytes (um spätere Berechnung in MB zu ermöglichen)
function dateien_groessen {
    find "$ORDNER" -type f -exec stat --format="%s %n" {} \; | sort > "$CUR_FILE"
}

# Prüfe, ob es die vorherige Dateigrößendatei gibt, falls nicht erstelle sie
if [[ ! -f "$PREV_FILE" ]]; then
    touch "$PREV_FILE"
fi

# Speichere die aktuellen Dateigrößen
dateien_groessen

# Berechne die Ordnergröße
ORDNER_GROESSE=$(ordner_groesse)

# Falls es keine vorherige Logdatei gibt, erstelle sie
if [[ ! -f "$LOG_FILE" ]]; then
    echo "Logfile created on $(date)" > "$LOG_FILE"
fi

# Füge die aktuelle Ordnergröße in die Protokolldatei hinzu
echo "$(date): Ordnergröße: $ORDNER_GROESSE" >> "$LOG_FILE"

# Funktion zum Filtern von Änderungen ab 50 MB
function filter_groessenaenderungen {
    # Vergleiche die beiden Dateilisten (vorher und aktuell)
    while IFS= read -r line; do
        # Extrahiere Dateigröße und Dateiname
        SIZE_PREV=$(echo "$line" | awk '{print $1}')
        FILE=$(echo "$line" | awk '{print $2}')

        # Prüfe, ob diese Datei in der aktuellen Liste ist
        SIZE_CUR=$(grep "$FILE" "$CUR_FILE" | awk '{print $1}')
    
        if [[ -n "$SIZE_CUR" ]]; then
            # Berechne die Größenänderung in MB
            DIFF_MB=$(echo "($SIZE_CUR - $SIZE_PREV) / 1048576" | bc -l)

            # Prüfe, ob die Größenänderung mindestens 50 MB beträgt
            if (( $(echo "$DIFF_MB >= $MIN_CHANGE_MB" | bc -l) )); then
                echo "$(date): $FILE hat sich um $DIFF_MB MB geändert" >> "$LOG_FILE"
            fi
        fi
    done < "$PREV_FILE"
}

# Führe die Filterung durch und logge nur Änderungen ab 50 MB
echo "Dateien mit Größenänderungen über 50 MB:" >> "$LOG_FILE"
filter_groessenaenderungen

# Kopiere die aktuellen Dateigrößen für den nächsten Durchlauf
cp "$CUR_FILE" "$PREV_FILE"

----

abspeicher als size.sh und ausführtbar machen mit chmod +x size.sh. mehrfaches ausführen logt die änderungen in der log file der dateien.

dann zb mit cronjob täglich einmal um 0 uhr ausführen lassen

Code:
crontab -e

0 0 * * * /pfad/zu/size.sh
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: butchooka
dernettehans schrieb:
Hab einfach mal ChatGPT dafür genutzt mir ein Script zu schreiben
Tipp am Rande: Code in einen Code-Block packen, am besten noch mit der richtigen Sprache versehen, dann wird er besser lesbar.

Ich habe mir zu diesem Zweg ein kleines Wrapperskript namens FileTree geschrieben, das unten drunter tree aufruft (tree kann auch Ordnergrößen aufsummieren) und die Ausgabedatei nach dem eingelesenen Verzeichnis und dem aktuellen Datum benennt. Es hat diverse Optionen, komprimiert die Ausgaben und legt nach dem Erstellen einen Symlink ohne Datum im Namen auf die neueste Version an. Gibt man kein Verzeichnis an, liest es angeschlossene Wechselmedien aus.

Ich lege mir mit dem Skript (semi-)regelmäßig Dateien an, um sie dann in einem Editor miteinander vergleichen zu können. Ich sehe dadurch, welche Dateien dazugekommen oder verschwunden sind und ich sehe die Größen von Ordnern und Dateien. Insbesondere nutze ich das mit externen Speichermedien und meinem NAS, damit ich nicht dauernd meine Platten anstecken oder das NAS hochfahren muss, um zu sehen, was drauf ist.

Achtung: die Unterstriche am Anfang der Zeilen 22 bis 40 müssen durch Tabs ersetzt werden (nicht Leerzeichen!). Leider kann man die hier nicht eingeben.

Bash:
#!/usr/bin/env bash

# FileTree history
# 2018-02-10 initial version
# 2020-06-10 added -a option
# ????-??-?? gzip result
# 2021-03-22 put current date into output filename
# 2021-04-06 code refactoring with more functions and input sanitisation
# 2021-12-22 prefer zstd over gzip
# 2022-01-23 don't create symlink if there is no previous file
# 2022-01-30 added xz to compressors
# 2022-12-08 some cleanup, added -C and -K options
# 2022-12-09 bug fixes
# 2023-07-28 added -m and -s options, some code simplification

die() {
    echo "${@}" > /dev/stderr
    exit 1
}

usage() {
    cat <<-EOF
_Usage: $(basename "$0") [-a] [-c COMP] [-K] [-o NAME] [-s] [DIR [DIR]]

_A wrapper to tree, it writes the content of DIR into a text file.
_The file is named after DIR and the current date, and symlink to the
_most recent version is set. The file is automatically compressed to
_zstd, xz or gzip, whichever is available in that order.

_Options:
_  -a    access attributes: owner, group, permissions
_  -c    compression: zstd, xz, gz, NO (in that order for auto-select)
_  -K    do not keep backup of existing file in case of overwriting
_  -o    The destination where to write the trees.
_        Default: .
_        If it is a directory: write in there.
_        If it is a filename: use that as base. If not,
_        use the name of the source directory as name base.
_  -s    write simpler output with fewer information
_EOF
}

test_writable() {
    touch "$1" 2>/dev/null || die "Cannot create file in $(dirname "$1")."
}

# run tree and redirect output to destination file
call_to_tree() {
    local OUTPATH="$OUTDIR/$OUTNAME"
    local DATED_PATH="$OUTPATH-$TODAY"

    declare TREE_ARGS
    TREE_ARGS+=("$@")
    TREE_ARGS+=("-o")
    TREE_ARGS+=("$DATED_PATH")

    local EXT
    local PACK
    local CREATE_SYMLINK=no

    if [ "$COMPRESS" = "" ]; then
        if command -v zstd > /dev/null; then
            COMPRESS="zstd"
        elif command -v xz > /dev/null; then
            COMPRESS="xz"
        elif command -v gzip > /dev/null; then
            COMPRESS="gz"
        else
            COMPRESS="NO"
        fi
    fi

    case "$COMPRESS" in
        "zstd")
            PACK="zstd --rm -q -13"
            EXT=".zst"
            ;;
        "xz")
            PACK="xz"
            EXT=".xz"
            ;;
        "gz")
            PACK="gzip"
            EXT=".gz"
        ;;
    esac

    ls "$OUTPATH-"* > /dev/null 2>&1 && CREATE_SYMLINK=yes

    # pack yet unpacked existing file
    if [ "$COMPRESS" != "no" ]; then
        [ -f "$DATED_PATH" ] && [ ! -f "$DATED_PATH$EXT" ] && $PACK "$DATED_PATH"
    fi
    # move away existing file
    if [ -f "$DATED_PATH$EXT" ]; then
        if [ "$KEEP_OLD" = "no" ]; then
            rm -f "$DATED_PATH$EXT"
        else
            mv -f "$DATED_PATH$EXT" "$DATED_PATH.old$EXT"
        fi
    fi

    test_writable "$DATED_PATH"
    echo "Writing $MODE tree to $(realpath "$DATED_PATH")$EXT"

    BEGIN_TIME="$SECONDS"
    "${TREE_ARGS[@]}"
    END_TIME="$SECONDS"

    echo >> "$DATED_PATH"
    df -BM . | awk 'NR==2 { print "Size: " $2 "   Usage: " $3 "   Available: " $4; }' >> "$DATED_PATH"
    #echo "Duration: $((END_TIME - BEGIN_TIME)) seconds." >> "$DATED_PATH"
    [ "$PACK" ] && $PACK "$DATED_PATH"

    if [ "$CREATE_SYMLINK" = yes ]; then
        if [ -e "$OUTPATH" ] && [ ! -L "$OUTPATH" ]; then
            echo "Cannot set symlink, filename already taken."
        else
            echo "Setting symlink $OUTPATH -> $(basename "$DATED_PATH")"
            ln -sfn "$(basename "$DATED_PATH$EXT")" "$OUTPATH$EXT"
        fi
    fi

    echo "Reading of directory took $((END_TIME - BEGIN_TIME)) seconds."
}

# parse arguments
unset ACCESSRIGHTS
unset COMPRESS
unset KEEP_OLD
unset MEDIA
MODE="detailed"

while getopts "c:ahKo:s" OPTION
do
    case $OPTION in
        c) case "$OPTARG" in
            NO|xz|zstd|gz) COMPRESS="$OPTARG" ;;
            *) die "The given compression '$OPTARG' is invalid." ;;
        esac ;;
        K) KEEP_OLD=no ;;
        a) ACCESSRIGHTS=yes ;;
        h) usage; exit 0 ;;
        o) OUTARG="$OPTARG" ;;
        s) MODE="simple" ;;
        ?) die "Unknown option '$OPTION'." ;;
    esac
done
shift $((OPTIND-1)); OPTIND=1

TODAY="$(date +%F)"

declare DIRS

if [ "$#" -eq 0 ]; then
    for SUBDIR in "/run/media/$USER/"*; do
        if mountpoint -q "$SUBDIR"; then
            printf "Detected mounted storage medium at '%s'. Read it? [Yn] " "$SUBDIR"
            read -r ANSWER
            case "$ANSWER" in
                y|Y|"") DIRS+=("$SUBDIR") ;;
            esac
        fi
        echo
    done
    if [ ! "$DIRS" ]; then
        echo "No mounted media found. Using current directory instead." > /dev/stderr
        echo
        DIRS+=("$(pwd)")
    fi
else
    while [ "$1" ]; do
        DIRS+=("$1")
        shift
    done
fi


# input sanitisation and derivation of destination paths
[ "$OUTARG" ] && OUTARG="$(realpath "$OUTARG")" || OUTARG="$(pwd)"
[ -d "$OUTARG" ] && OUTDIR="$OUTARG" || OUTDIR="$(dirname "$OUTARG")"

for DIR in "${DIRS[@]}"; do
    if [ ! -d "$DIR" ]; then
        echo "The given directory does not exist." >/dev/stderr
        continue
    fi

    if [ -d "$OUTARG" ]; then
        OUTNAME="$(basename "$(realpath "$DIR")")" # using `realpath` to catch `videotree .`
        [ "$OUTNAME" = "/" ] && OUTNAME=root
    else
        OUTNAME="$(basename "$OUTARG")"
    fi

    cd "$DIR" || exit

    if [ "$MODE" = "detailed" ]; then
        # write a very verbose file with permission and owner information
        call_to_tree tree -af --dirsfirst --du -DFins --timefmt "%Y-%m-%d %T" ${ACCESSRIGHTS:+-pug}
    else
        # write a smaller version with only file size information
        call_to_tree tree -ax --dirsfirst --du -n -h
    fi
done

echo "Done."
 
Zuletzt bearbeitet:
Donnerkind schrieb:
Tipp am Rande: Code in einen Code-Block packen, am besten noch mit der richtigen Sprache versehen, dann wird er besser lesbar.
Diese Code Block Funktionen nerven mich seit Jahren in allen möglichen Foren und funktionieren dann meist nie wie man will. Ich hatte hier nach einem Code block geschaut ist aber keiner in der Zeile oben vorhanden, wohl unter "..." versteckt, daher nicht genutzt und hatte keine Lust mich damit zu befassen.
 
Oh, das ist noch zu hoch für mich mit Skripten (auch das nachvollziehen von den geposteten Skripten).
Ich hab jetzt nur ganz rudimentär das:
Code:
sudo du -s /* > /pfad/zu/ordnergroessendatei
speicher es in immer neuer Datei ab und vergleiche die erste Spalte von Hand (erstmal nur für das root-Verzeichnis).

Die erste Spalte mit den Ordnergrößen schaffe ich auch schon auszugeben, damit ich sie mit einer vorigen Datei könnte, wenn ich denn wüsste, wie:
Code:
cat /pfad/zu/ordnergroessendatei | cut -d$'\t' -f 1

Da ich mehrmals heute Platz freischaufeln musste, weil nicht mehr viel Platz ist, hab ich das ChatGPT-Skript nun doch probiert. Erst mit einem einfachen Ordner, das ging, mit "/" kommt er aber nicht zum Ende.
Na ja, mit meiner Methode ist bisher kein wesentlicher Unterschied rausgekommen.

Nachtrag: hab das Skript nun abgebrochen, vermutlich sinds zuviele Dateien, die er so vergleicht. Es kamen auch ganz viele Meldungen, dass er in find auf Ordner/Dateien in /proc/... nicht zugreifen kann, Permission denied. Da ist du ganz elegant und schnell über den ganzen Ordner mit einer einzelnen Meldung weggehuscht.
 
Zuletzt bearbeitet:
@Tanne Ich hab ehrlich gesagt kein einzigen Satz oder Wort verstanden was du da geschrieben hast oder was du sagen willst. Wieso zum Teufel hast du es mit "/" probiert!? das macht ja mal sowas von keinen Sinn.
 
Warum nicht? Mir wird für "/" weniger verfügbarer Speicher angezeigt (und ist auch rot in dolphin) und ich weiß nicht, an welchem Unterordner das liegt. Aber ich bin auch Anfänger, von daher bleibt mir auch nicht mehr als probieren und fragen.
 
Zuletzt bearbeitet:
Wieso sagst du das dann nicht? Dazu macht deine Fragestellung ja gar kein Sinn!

Dann gib einfach ein:

Code:
du -h --max-depth=1 /

siehst immer nur eine ebene, siehst dann wo du hin gehn kannst was am größten ist.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: butchooka
Da kommt das gleiche raus wie bei mir. Aber es kommt ja auf die Unterschiede im Zeitvergleich an und da merke ich bisher trotzdem keine Unterschiede.
 
Deine Festplatte wird immer kleiner vom freichen Platz aber du merkst "trotzdem keine Unterschiede". Aha. Gute Nacht. hast du es auch als sudo ausgeführt, du wirst jawohl anhand der Verzeichnisgröße direkt sehn wo der Platz hin gegang ist und dann in das Verzeichnis noch ml du machen und noch mal.
 
Tanne schrieb:
Nachtrag: hab das Skript nun abgebrochen, vermutlich sinds zuviele Dateien, die er so vergleicht. Es kamen auch ganz viele Meldungen, dass er in find auf Ordner/Dateien in /proc/... nicht zugreifen kann, Permission denied.
Das war mir in dem Skript auch aufgefallen. Das du muss ein -x bekommen, damit es nicht über Dateisystemgrenzen hinweg geht.
 
  • Gefällt mir
Reaktionen: Tanne und dernettehans
Tanne schrieb:
Warum nicht? Mir wird für "/" weniger verfügbarer Speicher angezeigt (und ist auch rot in dolphin) und ich weiß nicht, an welchem Unterordner das liegt.
Aha. Warum solltest du das nicht sehen können? Hab mal fix getestet:
Code:
❯ sudo du -hsx --exclude=/proc /*
4,0K    /bin
71M     /boot
0       /dev
11M     /etc
862M    /home
4,0K    /lib
4,0K    /lib64
0       /mnt
0       /opt
16K     /root
1,6M    /run
4,0K    /sbin
0       /srv
0       /sys
4,0K    /tmp
6,7G    /usr
69M     /var
Liefert das passende Ergebnis. Falls erforderlich, kannst du das x weglassen, solltest du Subvolumes verwenden deren Speicherplatzverbrauch du ebenfalls sehen möchtest. Das Ergebnis kannst du dann auf die jeweiligen Unterordner eingrenzen. Oder du installierst dir ncdu und hast das Ganze in einer menügeführten Anwendung.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: Tanne und dernettehans
Zurück
Oben