Löschen Sie alle Dateien außer der aktuellsten n für jede Gruppe von Dateien mit demselben Präfix in einem Verzeichnis.

Löschen Sie alle Dateien außer der aktuellsten n für jede Gruppe von Dateien mit demselben Präfix in einem Verzeichnis.

Meine Frage unterscheidet sich ein wenig von einigen älteren Fragen, in denen lediglich nach dem „Löschen aller Dateien außer den aktuellsten nin 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 prefixTeil, und wir können davon ausgehen, dass in jedem prefixkeine Ziffern oder Bindestriche vorkommen.

bashIch möchte in einem Skript folgendes tun :

  1. Gehen Sie das angegebene Verzeichnis durch, identifizieren Sie alle vorhandenen „Gruppen“ und löschen Sie für jede Dateigruppe alle nDateien außer den aktuellsten der Gruppe.

  2. 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.bz2regulä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 derPREFIXES
  • Durchlaufen Sie alles PREFIXES:
  • Kalkulieren ALL_FILESmitPREFIX
  • Überprüfen Sie, ob die Menge ALL_FILESkleiner 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_FILESdie Liste und prüfen Sie, ob die angegebene Datei nicht vorhanden ist KEEP. 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 whileSchleife an xargs -d '/n' rmdie 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.bz2die zwischen foo-1-1.tar.bz2und 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 lsnicht 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, :sersetzen den ersten -durch /und :hextrahieren 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, Omsortieren 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 rmdie 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))])

verwandte Informationen