Wie lösche ich Dateien aus einem Ordner mit mehr als 60 Dateien unter Unix?

7

Ich möchte ein Skript in cronjob einfügen, das zu einer bestimmten Zeit ausgeführt wird. Wenn die Anzahl der Dateien mehr als 60 beträgt, werden die ältesten Dateien aus diesem Ordner gelöscht. Zuletzt rein, zuerst raus. Ich habe versucht,

#!/bin/ksh  
for dir in /home/DABA_BACKUP  
do  
    cd $dir  
    count_files=`ls -lrt | wc -l`   
    if [ $count_files -gt 60 ];  
    then  
        todelete=$(($count_files-60))  
        for part in `ls -1rt`  
        do  
            if [ $todelete -gt 0 ]  
            then  
                rm -rf $part  
                todelete=$(($todelete-1))  
            fi  
        done  
    fi
done   

Dies sind alles Sicherungsdateien, die täglich gespeichert und benannt werden backup_$date. Ist das ok?

pmaipmui
quelle
1
Hinweise: Um nur Dateien zu zählen, benötigen Sie keine lsOptionen -lrtund um eine Liste in der for-Schleife zu erstellen, benötigen Sie keine lsOption -1. Freie Variablenerweiterungen ( "$dir"und "$part") sollten in Anführungszeichen gesetzt werden. Anstelle der Backtics verwenden $(ls | wc -l).
Janis
@ Janis, die immer noch fehlschlagen, wenn die Dateinamen Zeilenumbrüche enthalten.
Terdon
1
@Ja, ich weiß. Es gibt einfach zu viel zu reparieren.
Janis
Mein Skript ist in Ordnung ... Ich habe es gerade gemäß der letzten Antwort bearbeitet. Es werden jetzt Dateien aus dem Ordner gelöscht, in dem die Anzahl der Dateien größer als 60 ist. Zuletzt eingegebene und die erste aus dem Ordner entfernte Datei. Das, was ich wollte, Last In First Out.
Pmaipmui
Es ist nicht okay. Es wird unterbrochen, wenn Ihre Dateinamen Leerzeichen oder Zeilenumbrüche enthalten. Es ist auch weitaus komplexer als nötig. In welchem ​​Format sind Ihre Namen? Du hast gesagt, backup_$dateaber was ist $date? Ist es 114-06-2015? Oder Sun Jun 14 15:06:53 EEST 2015? Wenn Sie uns genau sagen, was es ist, können wir Ihnen dies robuster und effizienter machen.
Terdon

Antworten:

3

Nein, zum einen wird es bei Dateinamen mit Zeilenumbrüchen kaputt gehen. Es ist auch komplexer als nötig und birgt alle Gefahren beim Parsen von ls .

Eine bessere Version wäre (mit GNU-Tools):

#!/bin/ksh  
for dir in /home/DABA_BACKUP/*
do
    ## Get the file names and sort them by their
    ## modification time
    files=( "$dir"/* );
    ## Are there more than 60?
    extras=$(( ${#files[@]} - 60 ))
    if [ "$extras" -gt 0 ]
    then
    ## If there are more than 60, remove the first
    ## files until only 60 are left. We use ls to sort
    ## by modification date and get the inodes only and
    ## pass the inodes to GNU find which deletes them
    find dir1/ -maxdepth 1 \( -inum 0 $(\ls -1iqtr dir1/ | grep -o '^ *[0-9]*' | 
        head -n "$extras" | sed 's/^/-o -inum /;' ) \) -delete
    fi
done

Beachten Sie, dass dies voraussetzt, dass sich alle Dateien im selben Dateisystem befinden und unerwartete Ergebnisse liefern können (z. B. das Löschen falscher Dateien), wenn dies nicht der Fall ist. Es funktioniert auch nicht gut, wenn mehrere Hardlinks auf denselben Inode verweisen.

terdon
quelle
Vielen Dank @terdon. Ich habe gerade mein Skript gemäß Ihrer Lösung geändert. Es funktioniert reibungslos. Vielen Dank an alle für Ihre wertvollen Bemühungen. Könntest du mir bitte einen Gefallen tun? Wenn möglich, teilen Sie bitte einige Links zum Schreiben von Shell-Skripten.
Pmaipmui
1
Ich kann kaum glauben, dass das erwähnte Datumsformat Nainita (130615, 140615) automatisch gut sortiert wird, wie Sie annehmen ... Versuchen Sie es mit den Daten 140615 und 130715. Die Standardausgabe ist 130715, gefolgt von 140615.
Lambert
1
@mikeserv Sie erhöhen zwei gültige Punkte. War der snarky Sarkasmus wirklich nötig, um sie zu machen? Warum musst du alles in einen Kampf verwandeln? Alles, was Sie tun mussten, ist auf meine Fehler hinzuweisen, und ich hätte sie gerne zugegeben, aber Sie haben sich entschieden, anzugreifen, anstatt zu lehren.
Terdon
1
@mikeserv du warst jetzt snarky und es war unangebracht. Sicher wissen Sie jetzt, dass ich absolut kein Problem damit habe, zuzugeben, dass ich falsch lag. Und ich habe mich hier sehr geirrt. Alles, was Sie tun mussten, war darauf hinzuweisen. Wie auch immer, siehe aktualisierte Antwort, es wird dir gefallen, es wird analysiert ls.
Terdon
1
@mikeserv Ich benutze nicht -l. Ich habe auch keine Ahnung, warum Sie Solaris erwähnen. Ich bin mit Ihrer Meinung zu diesem Beitrag genauso vertraut wie mit meiner. Lassen Sie es uns nicht noch einmal aufwärmen. Ich bin, findweil das der beste Weg ist, Dateien durch Inodes zu löschen. Ich würde mich freuen, von einem besseren zu hören (und das wäre ein wirklich konstruktiver Kommentar). Und ja, dies ist keine gute Antwort und ich würde es lieber nicht akzeptieren (und ich habe dies geschrieben, bevor ich Ihren letzten Kommentar gesehen habe). Da es jedoch akzeptiert wird, habe ich zumindest versucht, es i) funktionieren zu lassen, im Gegensatz zur vorherigen Version und ii) robust.
Terdon
3
#! /bin/zsh -
for dir (/home/DABA_BACKUP/*) rm -f $dir/*(Nom[61,-1])

Für die zsh-Unwissenden ;-):

  • for var (list) cmd: kurze Version der for var in list; do cmd; doneSchleife (erinnert an die perlSyntax).
  • $dir: zshVariablen haben zitiert , wie sie in anderen Shells nicht brauchen , wie zshhat explizite split und globBetreiber macht so nicht implizite Split + glob auf Parameter Expansion.
  • *(...): glob mit glob qualifiers :
  • N: nullglob: Die glob zu nichts expandiert statt einen Fehler zu erhöhen , wenn es nicht übereinstimmt.
  • m: O rder die generierten Dateien auf m NDERUNG Zeit (jüngste zuerst).
  • [61,-1]: Wählen Sie aus dieser geordneten Liste die 61. bis letzte aus.

Entfernt also im Grunde alle bis auf die 60 jüngsten Dateien.

Stéphane Chazelas
quelle
Könnten Sie das den Unwissenden erklären? Ich gehe davon aus, dass Sie irgendwie nach Datum sortieren, damit Sie nicht die Probleme haben, die meine Antwort hat, oder? Ist es das, was das NOmtut?
Terdon
@terdon, siehe bearbeiten. Ich hatte tatsächlich die falsche Logik (umgekehrt). Sollte sein om, zuerst mit dem Jüngsten zu sortieren (wie in ls -t).
Stéphane Chazelas
Sehr schön danke! Könnten Sie sich meine aktualisierte Antwort ansehen? Ich denke, es sollte i) jetzt funktionieren und ii) mit jedem Dateinamen robust sein. Ich würde es begrüßen, wenn Sie auf Dateinamen hinweisen könnten, die es beschädigen würden.
Terdon
1

So erhalten Sie eine Liste der ältesten zu löschenden Einträge (wobei die 60 neuesten Einträge beibehalten werden):

ls -t | tail -n +61

Beachten Sie, dass das Hauptproblem Ihres Ansatzes auch hier noch angegangen werden muss: Wie gehe ich mit Dateien mit Zeilenumbrüchen um, falls es darauf ankommt? Andernfalls können Sie einfach Folgendes verwenden (um Ihr recht komplexes Programm zu ersetzen):

cd /home/DABA_BACKUP || exit 1
ls -t | tail -n +61 | xargs rm -rf


Hinweis: Da Sie anscheinend tägliche Backups haben, können Sie möglicherweise auch einen Ansatz verwenden, der auf den Dateidaten und basiert find. wie in:

find /home/DABA_BACKUP -mtime +60 -exec ls {} +

(wo der lsBefehl - nach sorgfältiger Prüfung der korrekten Funktion! - durch den entsprechenden rmBefehl ersetzt würde).

Janis
quelle
1
Beachten Sie, dass bei der Verwendung xargsauch davon ausgegangen wird, dass der Dateiname kein Leerzeichen, Tabulatoren, Zeilenumbrüche (andere Formen von Leerzeichen je nach Gebietsschema und Xargs-Implementierung), einfache Anführungszeichen, doppelte Anführungszeichen und Backslash enthält. Möglicherweise möchten Sie der rm-Cmdline ein - hinzufügen, um Probleme mit Dateien zu vermeiden, deren Name mit - beginnt. (wahrscheinlich kein Problem für das OP, aber es lohnt sich, es hier für alle zu erwähnen, die mit einem ähnlichen Bedarf hierher kommen).
Stéphane Chazelas
1
rm60()( IFS=/; set -f; set $(
        set +f; \ls -1drt ./*)
        while shift &&
              [ $# -gt 60 ]
        do    [ -d "${1%?.}" ] ||
              rm "./${1%?.}"   || exit
        done
)

Dies wird für Sie funktionieren. Es werden die ältesten Dateien im aktuellen Verzeichnis bis zu einer Zählung von 60. löschen Sie dies tun , werden durch das Parsen ls robust , und es wird es tun , ohne irgendwelche Annahmen über Ihre Dateinamen zu machen - sie könnten etwas genannt werden und müssen nicht durch Daten genannt werden. Dies funktioniert nur für eine Auflistung des aktuellen Verzeichnisses und für den Fall, dass Sie ein POSIX lsinstalliert haben (und nicht durch eine böse Shell-Funktion maskiert aliassind , aber es wird abgedeckt) .

Die obige Lösung wendet nur einige sehr grundlegende Shell-Aufteilungen auf einige sehr grundlegende Unix-Pfadnamen an. Es stellt sicher, dass lsalle Nicht-Punkt-Dateien im aktuellen Verzeichnis eine pro Zeile wie folgt aufgelistet werden:

./oldestfile
./second-oldestfile

Nun, jeder von ihnen könnte auch neue Zeilen dazwischen haben, aber das wäre kein Problem. Denn in diesem Fall würden sie wie folgt aufgelistet:

./oldest
file
./s
econd

old
est
file
./third

...und so weiter. Und die Zeilenumbrüche stören uns sowieso nicht - weil wir uns nicht auf sie aufteilen. Warum sollten wir? Wir arbeiten mit Pfadnamen, wir sollten uns auf den Pfadbegrenzer aufteilen, und so machen wir das : IFS=/.

Das klappt ein bisschen komisch. Am Ende haben wir eine Argumentliste, die so aussieht:

<.> <file1\n.> <file2\n.> ... <filelast>

... aber das ist eigentlich sehr gut für uns, weil wir unsere Argumente, die von der Shell als Dateien behandelt werden (oder, falls wir dies vermeiden möchten, Symlinks) , so lange verzögern können , bis wir dazu bereit rmsind.

Sobald wir unsere Dateiliste haben, müssen wir nur noch shiftunser erstes Argument entfernen. Überprüfen Sie, ob wir derzeit mehr als 60 Argumente haben, und lehnen Sie es wahrscheinlich in rmein untergeordnetes Verzeichnis ab (obwohl das natürlich ganz bei Ihnen liegt). und ansonsten rmunser erstes Argument abzüglich der letzten beiden Zeichen. Wir haben keine Sorgen zu machen über die letzte letzte Argument - die aus der Anlage ersichtliche Zeit nicht hat - weil wir nie dorthin zu gelangen, und stattdessen bei 60 beenden Wenn wir es bis hierher für eine Iteration gemacht haben , dann haben wir einfach versuchen Sie es erneut und durchlaufen Sie die Arg-Liste auf diese Weise, bis wir sie zu unserer Zufriedenheit beschnitten haben.

Wie bricht das? Meines Wissens nicht, aber ich habe es zugelassen - wenn zu irgendeinem Zeitpunkt ein unerwarteter Fehler auftritt, bricht die Schleife und die Funktion gibt eine andere als 0 zurück.

Und so lskönnen Sie Ihre Auflistung im aktuellen Verzeichnis ohne Probleme für Sie erledigen. Sie können es zulassen, dass Ihre Argumente für Sie sortiert werden, solange Sie sie zuverlässig abgrenzen können. Aus diesem Grund funktioniert dies nur für das aktuelle Verzeichnis wie geschrieben - mehr als ein Trennzeichen in einer Pfadzeichenfolge würde eine andere Begrenzungsstufe erfordern, die durch doppeltes Ausklammern für alle außer dem letzten in NUL-Felder erfolgen könnte , aber das ist mir jetzt egal.

mikeserv
quelle
-1

Wenn Sie wissen, dass alle Dateien den Namen backup_ * haben, sollten Sie dies in den Befehl ls aufnehmen, damit Sie nur diese und keine Dateien verarbeiten, die versehentlich im Verzeichnis landen. Dann wird ls in einer Pipe verwendet, es wird nur 1 Datei pro Zeile aufgelistet und dann nur gezählt, ohne dass eine Sortierung erforderlich ist

count_files=$(ls -U backup_* | wc -l)

und

for part in $(ls -rt backup_*);do
    rm -rf "$part"
    todelete=$(($todelete-1))
    if [[ $todelete -eq 0 ]]; then
        break
    fi
done
Mikkel Alan Stokkebye Christia
quelle
1
Es wird allgemein empfohlen, das Parsen der lsAusgabe in Skripten zu vermeiden . Sie könnten findstattdessen verwenden.
Erathiel
@Erathiel - was genau findbietet hier, was vorzuziehen ist ls? Einmal schrieb jemand einen ziemlich fehlerbehafteten Blog-Beitrag über das Parsenls und aus irgendeinem Grund behandelt ihn die gesamte Linux-Community wie den Pentateuch. Schauen Sie, die wenigen gültigen Punkte, die im Blog-Beitrag gemacht wurden, gelten auch für finddiesen Fall.
Mikeserv