
Mir wurde ein Problem vorgelegt und meine Lösung hat die anfänglichen Testfälle bestanden, fiel aber beim Absenden zu 50 % durch.
Das Problem: Ein Verzeichnis enthält eine Reihe von Dateien und Ordnern. Einige dieser Dateien sind unterschiedliche Protokolltypen: error.log, error.log.1, error.log.2, access.log.1, access.log.2 usw. Der Inhalt dieser Dateien ist auf den folgenden Tag ausgerichtet, sodass „cat error.log.1“ „Protokolle von Tag 2“ usw. enthält.
Die Aufgabe besteht darin, nur die Nummer am Ende der Protokolle zu erhöhen und den restlichen Inhalt des Verzeichnisses unverändert zu lassen. Erstellen Sie außerdem für jeden Protokolltyp eine leere Datei.
Zum Beispiel:
./
example_dir
example2_dir
error.log
error.log.1
info.log.20
access.log.1
readme.txt
Das Skript ändert das Verzeichnis in:
./
example_dir (unchanged)
example2_dir (unchanged)
error.log (empty)
error.log.1 (originally error.log)
error.log.2 (originally error.log.1)
info.log (empty)
info.log.21 (originally info.log.20)
access.log (empty)
access.log.2 (originally access.log.1)
readme.txt (unchanged)
Bedingungen: Anzahl der Dateien im Verzeichnis < 1000, Max. Anzahl der Dateien pro Typ < 21
Meine Lösung:
#!/bin/bash
declare -a filenames
# Renaming in ascending order will lead to overwrite; so start rename from the bottom
files=$(find . -maxdepth 1 -name "*.log.*" -exec basename {} \; | sort -rn)
for i in $files; do
currentFileNumber=$(echo -e "$i" | sed -e 's/[^0-9]*//g') # Extract the current number from the filename
fileName=$(echo -e "$i" | sed -e 's/\.[0-9]*$//g') # Extract the name without the trailing number
newFileNumber=$(("$currentFileNumber" + 1)) # Increment the current number
mv "$i" "$fileName.$newFileNumber" # Rename and append the incremented value
if [[ ! ${filenames[*]} =~ ${fileName} ]] # Store names of existing types to create empty files
then
filenames=("${filenames[@]}" "${fileName}")
fi
# Could make use of [[ -e "$fileName.log" ]] instead of an array, but won't pass the test for some reason
done
for j in "${filenames[@]}"; do touch "$j"; done # Create the empty files
unset filenames
Die Testfälle, bei denen ich fehlgeschlagen bin, wurden nicht angezeigt, daher bin ich mir nicht wirklich sicher, wie ich das besser lösen kann.
Antwort1
Das war eine lustige Übung, also hier ist meine Lösung.
#/bin/bash
log_names=$(for logfile in $(find . -type f -name '*.log*'); do echo ${logfile%.[0-9]*}; done | sort -u)
for name in $log_names; do
echo "Processing $name"
i=20
until [[ "$i" -eq 0 ]]; do
if [[ -f "$name.$i" ]]; then
next_num=$((i+1))
mv -v "$name.$i" "$name.$next_num"
fi
i=$((i-1))
done
if [[ -f "$name" ]]; then
mv -v "$name" "$name.1"
fi
touch "$name"
done
Die Variable log_names verwendet einen find
Befehl, um die Liste der Protokolldateien abzurufen. Dann wende ich eine Zeichenfolgensubstitution an, um das numerische Suffix zu entfernen. Danach sortiere und entferne ich die Duplikate.
An diesem Punkt erhalte ich eine Liste der eindeutigen Protokolldateinamen im Verzeichnis: ./access.log ./error.log ./info.log
.
Anschließend verarbeite ich jeden Namen nacheinander in einer for
Schleife.
Nun wurde uns für jede Datei mitgeteilt, dass die maximal mögliche Zahl 20 ist. Wir beginnen dort und verwenden eine until
Schleife zum Herunterzählen.
Die mv
Logik ist einfach: Wenn „Dateiname.Nummer“ vorhanden ist, verschieben Sie es nach „Dateiname.(Nummer+1)“.
Wenn die until
Schleife beendet ist (i = 0), haben wir möglicherweise noch eine nicht rotierte Datei übrig – die ohne das numerische Suffix. Wenn das der Fall ist, verschieben wir sie nach filename.1.
Der letzte Schritt besteht darin, eine leere Datei mit zu erstellen touch
.
Beispielausführung:
$ ls
access.log.1 error.log error.log.1 example_dir example2_dir info.log.20 readme.txt rotate.bash
$ bash rotate.bash
Processing ./access.log
'./access.log.1' -> './access.log.2'
Processing ./error.log
'./error.log.1' -> './error.log.2'
'./error.log' -> './error.log.1'
Processing ./info.log
'./info.log.20' -> './info.log.21'
$ ls -1
access.log
access.log.2
error.log
error.log.1
error.log.2
example_dir
example2_dir
info.log
info.log.21
readme.txt
rotate.bash
Antwort2
@Haxiel hat eine Lösung gepostet. Diese ähnelt der, die ich im Sinn hatte, und der, die ich als „am einfachsten“ beschrieben habe. Ich hätte eher eine for
Schleife als die until
Schleife verwendet.
Dabei wird fast die minimale Anzahl externer Prozesse verwendet, einer mv
für jede vorhandene Datei und einer touch
am Ende, um die neuen Dateien zu erstellen. (Das Touch-Verfahren könnte durch eine Schleife ersetzt werden, die die Dateien mithilfe einer Umleitung erstellt, um die Anzahl externer Prozesse um 1 zu reduzieren.)
#!/bin/bash
shopt -s nullglob # Reduce the number of things we have to work with
# get a list of the files we want to work with.
files=( *.log *.log.[1-9] *.log.[1-9][0-9] )
# reverse the list into rfiles, getting rid of non-file things
rfiles=()
for ((i=${#files[@]}-1;i>=0;i--)) ; do
if [ -f "${files[i]}" ] ; then
rfiles+=("${files[i]}")
fi
done
# exit early if there is nothing to do
if [ ${#rfiles[@]} -eq 0 ] ; then
exit 0
fi
# an array of the files we need to create
typeset -A newfiles
# Loop over the reversed file list
for f in "${rfiles[@]}"; do
# Get everything up to the last "log"
baseName=${f%log*}log
# Remove up to the last "log" and then the optional "."
currentFileNum=${f#"$baseName"}
currentFileNum=${currentFileNum#.}
mv -v "$f" "$baseName.$((currentFileNum+1))"
# record the name to make the new files
newfiles[$baseName]=1
done
# Create all the needed new files, using the names stored in the array
touch "${!newfiles[@]}"
Die Reihenfolge, in der die Dinge dabei ablaufen, unterscheidet sich von der Lösung von @Haxiel. Diese verschiebt zuerst alle Dateien mit zweistelligen Nummern, dann alle Dateien mit einstelligen Nummern und schließlich die Dateien mit der Endung „.log“, anstatt alle Dateien mit dem gleichen Anfangsteil zusammen zu verarbeiten.
In der ursprünglichen Frage hieß es, dass es weniger als 1.000 Dateien und weniger als 21 Versionen pro Datei gibt. Es wurde nicht gesagt, was zu tun ist, wenn es mehr als diese Zahl gibt. Diese Lösung berücksichtigt bis zu 100 Versionen pro Datei und kann durch einfaches Erweitern des Musters auf 1.000 oder mehr erweitert werden.
Die Anzahl der Dateien wird durch die für Bash verfügbare Speichermenge begrenzt.
Ich glaube, dass dies eine bessere Lösung ist, da nur versucht wird, vorhandene Dateien zu verarbeiten, anstatt N Dateien für jeden Namen auszuprobieren. Wenn N klein ist (z. B. 21), spielt dies keine Rolle.