Meine Frage unterscheidet sich ein wenig von einigen älteren Fragen, in denen lediglich nach dem „Löschen aller Dateien außer den aktuellsten n
in einem Verzeichnis“ gefragt wird.
Ich habe ein Verzeichnis, das verschiedene „Gruppen“ von Dateien enthält, wobei jede Dateigruppe ein beliebiges Präfix gemeinsam hat und jede Gruppe mindestens eine Datei hat. Ich kenne diese Präfixe nicht im Voraus und weiß nicht, wie viele Gruppen es gibt.
EDIT: Tatsächlich weiß ich etwas über die Dateinamen, nämlich, dass sie alle dem Muster folgen prefix-some_digits-some_digits.tar.bz2
. Das einzige, was hier zählt, ist der prefix
Teil, und wir können davon ausgehen, dass in jedem prefix
keine Ziffern oder Bindestriche vorkommen.
bash
Ich möchte in einem Skript folgendes tun :
Gehen Sie das angegebene Verzeichnis durch, identifizieren Sie alle vorhandenen „Gruppen“ und löschen Sie für jede Dateigruppe alle
n
Dateien außer den aktuellsten der Gruppe.Wenn für eine Gruppe weniger als Dateien vorhanden sind
n
, tun Sie nichts für diese Gruppe, d. h. löschen Sie keine Dateien für diese Gruppe.
Was ist eine robuste und sichere Möglichkeit, das oben genannte in durchzuführen bash
? Könnten Sie die Befehle bitte Schritt für Schritt erklären?
Antwort1
Das Skript:
#!/bin/bash
# Get Prefixes
PREFIXES=$(ls | grep -Po '^(.*)(?!HT\d{4})-(.*)-(.*).tar.bz2$' | awk -F'-' '{print $1}' | uniq)
if [ -z "$1" ]; then
echo need a number of keep files.
exit 1
else
NUMKEEP=$1
fi
for PREFIX in ${PREFIXES}; do
ALL_FILES=$(ls -t ${PREFIX}*)
if [ $(echo ${ALL_FILES} | wc -w) -lt $NUMKEEP ]; then
echo Not enough files to be kept. Quit.
continue
fi
KEEP=$(ls -t ${PREFIX}* | head -n${NUMKEEP})
for file in $ALL_FILES ; do
if [[ "$KEEP" =~ "$file" ]]; then
echo keeping $file
else
echo RM $file
fi
done
done
Erläuterung:
- Berechnen Sie die Präfixe:
- Suchen Sie nach allen Dateien, die dem
something-something-something.tar.bz2
regulären Ausdruck folgen, schneiden Sie nur den ersten Teil bis zum ersten Bindestrich ab und machen Sie ihn eindeutig. - Das Ergebnis ist eine normalisierte Liste der
PREFIXES
- Suchen Sie nach allen Dateien, die dem
- Durchlaufen Sie alles
PREFIXES
: - Kalkulieren
ALL_FILES
mitPREFIX
- Überprüfen Sie, ob die Menge
ALL_FILES
kleiner ist als die Anzahl der beizubehaltenden Dateien. Wenn dies zutrifft, können wir hier aufhören, da nichts zu entfernen ist. - Berechnen Sie die Dateien
KEEP
, die die aktuellsten sindNUMKEEP
- Durchlaufen Sie
ALL_FILES
die Liste und prüfen Sie, ob die angegebene Datei nicht vorhanden istKEEP
. Wenn ja: Entfernen Sie sie.
Beispielergebnis beim Ausführen:
$ ./remove-old.sh 2
keeping bar-01-01.tar.bz2
keeping bar-01-02.tar.bz2
RM bar-01-03.tar.bz2
RM bar-01-04.tar.bz2
RM bar-01-05.tar.bz2
RM bar-01-06.tar.bz2
keeping foo-01-06.tar.bz2
keeping foo-01-05.tar.bz2
RM foo-01-04.tar.bz2
RM foo-01-03.tar.bz2
RM foo-01-02.tar.bz2
$ ./remove-old.sh 8
Not enough files to be kept. Quit.
Not enough files to be kept. Quit.
Antwort2
Wie gewünscht tendiert diese Antwort in Richtung „robust und sicher“, im Gegensatz zu „schnell und schmutzig“.
Portabilität: Diese Antwort funktioniert auf jedem System, das sh
, find
, sed
, sort
, ls
, grep
, xargs
, und enthält rm
.
Das Skript sollte bei großen Verzeichnissen nie ins Stocken geraten. Es wird keine Shell-Dateinamenerweiterung durchgeführt (was bei zu vielen Dateien zu einem Absturz führen könnte, aber das ist eine riesige Zahl).
Diese Antwort geht davon aus, dass das Präfix keinen Bindestrich () enthält -
.
Beachten Sie, dass das Skript konstruktionsbedingt nur die Dateien auflistet, die entfernt werden. Sie können das Entfernen der Dateien veranlassen, indem Sie die Ausgabe der while
Schleife an xargs -d '/n' rm
die im Skript auskommentierte Schleife weiterleiten. Auf diese Weise können Sie das Skript problemlos testen, bevor Sie den Entfernungscode aktivieren.
#!/bin/sh -e
NUM_TO_KEEP=$(( 0 + ${1:-64000} )) || exit 1
find . -maxdepth 1 -regex '[^-][^-]*-[0-9][0-9]*-[0-9][0-9]*.tar.bz2' |
sed 's/-.*//; s,^\./,,' |
sort -u |
while read prefix
do
ls -t | grep "^$prefix-.*-.*\.tar\.bz2$" | sed "1,$NUM_TO_KEEP d"
done # | xargs -d '\n' rm --
Der Parameter N (Anzahl der zu behaltenden Dateien) beträgt standardmäßig 64.000 (d. h. alle Dateien werden behalten).
Kommentierter Code
Holen Sie sich das Befehlszeilenargument und prüfen Sie durch Addition, ob eine Ganzzahl vorhanden ist. Wenn keines angegeben ist, wird der Parameter standardmäßig auf 64.000 (tatsächlich alle) gesetzt:
NUM_TO_KEEP=$(( 0 + ${1:-64000} )) || exit 1
Suchen Sie im aktuellen Verzeichnis nach allen Dateien, die dem Dateinamenformat entsprechen:
find . -maxdepth 1 -regex '[^-][^-]*-[0-9][0-9]*-[0-9][0-9]*.tar.bz2' |
Präfix abrufen: Entfernen Sie alles nach dem Präfix und entfernen Sie das "./" am Anfang:
sed 's/-.*//; s,^\./,,' |
Präfixe sortieren und Duplikate entfernen ( -u
-- unique):
sort -u |
Lesen Sie jedes Präfix und jeden Prozess:
while read prefix
do
Listen Sie alle Dateien im Verzeichnis nach Zeit sortiert auf, wählen Sie die Dateien für das aktuelle Präfix aus und löschen Sie alle Zeilen nach den Dateien, die wir behalten möchten:
ls -t | grep "^$prefix-.*-.*\.tar\.bz2$" | sed "1,$NUM_TO_KEEP d"
Kommentieren Sie zum Testen den Code aus, um die Datei zu entfernen. Verwenden Sie xargs, um Probleme mit der Befehlszeilenlänge oder Leerzeichen in Dateinamen zu vermeiden. Wenn das Skript ein Protokoll erstellen soll, fügen Sie beispielsweise Folgendes hinzu -v
: rm
. rm -v --
Entfernen Sie das, #
um den Entfernungscode zu aktivieren:
done # | xargs -d '\n' rm --
Wenn das für Sie funktioniert, akzeptieren Sie diese Antwort bitte und stimmen Sie dafür. Danke.
Antwort3
Ich gehe davon aus, dass die Dateien bei der Auflistung in lexikalischer Reihenfolge nach Präfix gruppiert sind. Das bedeutet, dass es keine Gruppen mit einem Präfix gibt, das ein Suffix einer anderen Gruppe ist, z. B. keine, foo-1-2-3.tar.bz2
die zwischen foo-1-1.tar.bz2
und stehen würden foo-1-2.tar.bz2
. Unter dieser Annahme können wir alle Dateien auflisten und wenn wir eine Änderung des Präfixes feststellen (oder für die allererste Datei), haben wir eine neue Gruppe.
#!/bin/bash
n=$1; shift # number of files to keep in each group
shopt extglob
previous_prefix=-
for x in *-+([0-9])-+([0-9]).tar.bz2; do
# Step 1: skip the file if its prefix has already been processed
this_prefix=${x%-+([0-9])-+([0-9]).tar.bz2}
if [[ "$this_prefix" == "$previous_prefix" ]]; then
continue
fi
previous_prefix=$this_prefix
# Step 2: process all the files with the current prefix
keep_latest "$n" "$this_prefix"-+([0-9])-+([0-9]).tar.bz2
done
Nun sind wir beim Problem derErmitteln der ältesten Dateien in einer expliziten Liste.
Vorausgesetzt, dass die Dateinamen keine Zeilenumbrüche oder Zeichen enthalten, die ls
nicht wörtlich angezeigt werden, kann dies wie folgt implementiert werden ls
:
keep_latest () (
n=$1; shift
if [ "$#" -le "$n" ]; then return; fi
unset IFS; set -f
set -- $(ls -t)
shift "$n"
rm -- "$@"
)
Antwort4
Ich weiß, dass dies markiert ist bash
, aber ich denke, es wäre einfacher mit zsh
:
#!/usr/bin/env zsh
N=$(($1 + 1)) # calculate Nth to last
typeset -U prefixes # declare array with unique elements
prefixes=(*.tar.bz2(:s,-,/,:h)) # save prefixes in the array
for p in $prefixes # for each prefix
do
arr=(${p}*.tar.bz2) # save filenames starting with prefix in arr
if [[ ${#arr} -gt $1 ]] # if number of elements is greather than $1
then
print -rl -- ${p}*.tar.bz2(Om[1,-$N]) # print all filenames but the most recent N
fi
done
Das Skript akzeptiert ein Argument:N(die Anzahl der Dateien)
(:s,-,/,:h)
sind Glob-Modifikatoren, :s
ersetzen den ersten -
durch /
und :h
extrahieren den Kopf (den Teil bis zum letzten Schrägstrich, der in diesem Fall auch der erste Schrägstrich ist, da es nur einen gibt)
(Om[1,-$N])
sind Glob-Qualifikatoren, Om
sortieren die Dateien beginnend mit der ältesten und [1,-$N]
wählen von der ersten bis zur N-ten bis letzten aus.
Wenn Sie mit dem Ergebnis zufrieden sind, ersetzen Sie durch print -rl
, um rm
die Dateien tatsächlich zu löschen, z. B.:
#!/usr/bin/env zsh
typeset -U prefixes
prefixes=(*.tar.bz2(:s,-,/,:h))
for p in $prefixes
arr=(${p}*.tar.bz2) && [[ ${#arr} -gt $1 ]] && rm -- ${p}*.tar.bz2(Om[1,-$(($1+1))])