Dateien löschen mit bash

Hi,

mein Problem ist etwas umfangreicher als es der Topititel aussagt. Ich habe ein Script, dass mir ein paar Standards von ETSI herunterlädt. Mit pdfinfo wird anschliessend der Titel der pdf-Datei ausgelesen und als Dateiname gesetzt.

Nun habe ich verschiedene Versionen eines Standards vorliegen. Zum Beispiel:

ETSI EN 302 571 V0.0.2 - abc.pdf
ETSI EN 302 571 V1.1.1 - abc.pdf
ETSI EN 302 571 V1.2.0 - abc.pdf
ETSI EN 302 571 V1.2.1 - abc.pdf
ETSI EN 302 571 V2.0.0 - abc.pdf 
ETSI EN 302 636-4-1 V1.2.0 - xyz.pdf
ETSI EN 302 636-4-1 V1.2.1 - xyz.pdf

Mein Script soll jetzt von jedem Standard nur die letzte Version behalten und alle anderen Dateien löschen. Für das obige Beispiel beufeutet das, dass ich nur folgende Dateien behalten will:

ETSI EN 302 571 V2.0.0 - abc.pdf
ETSI EN 302 636-4-1 V1.2.1 - xyz.pdf

Mir fehlt so ein bisschen die Idee, wie ich das angehen könnte. Vielleicht hat ja hier jemand eine Idee dazu?

1 Like

Habe zum Test dieses Script geschrieben, das die Zeilen mit den Dateien ausgibt, die gelöscht werden sollen, so dass die zwei Dateien aus Deinem Beispiel übrig bleiben. Dabei nehme ich an, dass die Zeilen, so wie in Deinem Beispiel, bereits nach Standard und Version sortiert sind.

#!/bin/bash

LISTE="ETSI EN 302 571 V0.0.2 - abc.pdf
ETSI EN 302 571 V1.1.1 - abc.pdf
ETSI EN 302 571 V1.2.0 - abc.pdf
ETSI EN 302 571 V1.2.1 - abc.pdf
ETSI EN 302 571 V2.0.0 - abc.pdf 
ETSI EN 302 636-4-1 V1.2.0 - xyz.pdf
ETSI EN 302 636-4-1 V1.2.1 - xyz.pdf"

awk '{if(NR>1 && S==$4) print L; S=$4; L=$0}' <<< "$LISTE"

Die Ausgabe sieht dann so aus:

ETSI EN 302 571 V0.0.2 - abc.pdf
ETSI EN 302 571 V1.1.1 - abc.pdf
ETSI EN 302 571 V1.2.0 - abc.pdf
ETSI EN 302 571 V1.2.1 - abc.pdf
ETSI EN 302 636-4-1 V1.2.0 - xyz.pdf

Für Deinen Bedarf genügt letzte Zeile (also die mit “awk” beginnt) und anstelle von

<<< "$LISTE"

kannst Du Deinen Scriptaufruf einfügen, z.B., falls Dein Script “mein_script” heißt,

awk '{if(NR>1 && S==$4) print L; S=$4; L=$0}'  < <(mein_script)

(beachte: zwischen diesen beiden < < ist ein Leerzeichen)
oder pipen, z.B.

mein_script | awk '{if(NR>1 && S==$4) print L; S=$4; L=$0}'

oder Du baust das direkt in Dein Script ein.

Um nur die Dateinamen anstelle der kompletten Zeilen auszugeben - ich nehme an, dass die Dateinamen ohne Leerzeichen sind und am Ende jeder Zeile stehen -, musst Du $0 durch $NF ersetzen.
Die Ausgabe kannst Du dann zum Löschen der Dateien nutzen.

Oh, danke. Ich probier das mal aus.

Irgendwie macht das Script nicht das, was es machen soll.

Ich lese mir die Dateinamen ein, ersetze alle Leerzeichen durch ein * und packe alle Dateinamen in eine Liste. Danach führe ich das awk-Kommando aus. Damit wollte ich sehen, ob alles klappt, bevor ich das eigentliche Löschen einbauen. Aber mir wird nix ausgegeben:

#!/bin/bash

cd ETSI_ITS_Standards || exit

nameList=""

for file in *.pdf
    do
        file=$(basename "$file")
        file=${file// /*}
        nameList="$nameList $file"
    done
awk '{if(NR>1 && S==$4) print L; S=$4; L=$0}' <<< "$nameList"

Hier ist mal der Rest vom Script, der mir die Dateien vom Server holt und dann umbenennt:

#!/bin/bash

# Create folder for the standards (as subfolder of the path of the script)
specFolder="ETSI_ITS_Standards"

# List of relevant standards --> e.g. ETSI EN 302 636-5-1 needs to be converted manually to en_3026360501
listOfStandards=("en_302571" "en_3026360401" "en_3026360501" "en_3026360601" "en_30263702" "en_30263703" "en_302663" "en_302931" "ts_10153901" "ts_10153903" "ts_1026360501" "ts_102731" "ts_10286801" "ts_10287103" "ts_10289401" "ts_10289402" "ts_102940" "ts_102941" "ts_102942" "ts_102943" "ts_103097" "ts_10324601" "ts_10324603")

echo -e "Betrete Verzeichnis: $specFolder\n"
if ! [ -d $specFolder ]
then
    mkdir $specFolder
fi
cd ETSI_ITS_Standards || exit

# Download files
for stand in "${listOfStandards[@]}"
  do
    IFS=_ read -r specType specNumber <<< "$stand"
    tmpNumb=$( echo "$specNumber" |cut -c1-4 )
    url="http://www.etsi.org/deliver/etsi_$specType/${tmpNumb}00_${tmpNumb}99/${specNumber}/"
    wget -r --no-parent -l 0 -nd -nc -e robots=off -A .pdf "$url"
 done

# Rename files
for file in *.pdf
  do
    pdfTitle=$(pdfinfo -meta "$file"| grep Title)                               # PDF-Titel aus Meta-Daten auslesen
    newFileName="ETSI ${pdfTitle#* }.pdf"                                       # aus Titel Dateinamen bauen
    fileName=${newFileName//;/ -}                                               # Semikolon durch Leerzeichen plus Bindestrich ersetzen
    fileName=${fileName//\//_}                                                  # Slash im Namen durch Unterstrich ersetzen
    IFS=' ' list=($fileName)                                                    # Namen am Leerzeichen aufteilen, um späteren Dateinamen zu kürzen
    tmpPart1="${list[0]} ${list[1]} ${list[2]} ${list[3]} ${list[5]}"           # Wesentliche Blöcke des Namens zusammenfügen
    tmpPart2=${fileName##*-}                                                    # Name kürzen
    tmpPart3=${tmpPart2##*:}                                                    # Name weiter kürzen
    fileName="${tmpPart1} -${tmpPart3}"                                         # Name neu zusammenbauen
    fileName=${fileName//-/- }                                                  # fehlende Leerzeichen ersetzen
    fileName=$(echo "$fileName"|sed 's/ \+/ /g')                                # doppelte Leerzeichen entfernen
    mv "$file" "$fileName"                                                      # Datei umbenennen
  done

Der Fehler in Deinem ersten Script liegt darin, dass die Dateinamen in der Variablen $nameList nicht durch Zeilenumbrüche, sondern durch Leerzeichen getrennt und die Leerzeichen in den Dateinamen durch * ersetzt werden. Damit kann das awk-Script nichts anfangen, da es standardmäßig Leerzeichen als Spalten-Trennzeichen und Zeilenumbrüche (\n) als Zeilen-Trennzeichen nimmt (falls nicht anders definiert).

Eigentlich sollte dieser Aufruf genügen:

#!/bin/bash
cd ETSI_ITS_Standards || exit
awk '{if(NR>1 && S==$4) print L; S=$4; L=$0}' < <(ls *.pdf)

Auf die for-Schleife und auf basename kann verzichtet werden, da vorher in das Verzeichnis mit den PDF-Dateien gewechselt worden ist.

Beachte auch: Mit cd ETSI_ITS_Standards wird in das Unterverzeichnis des Verzeichnisses gewechselt, aus welchem heraus das Script aufgerufen wurde, das ist also eine relative Pfadangabe.
Ist dieser Pfad korrekt, d.h. wird das Script immer aus dem richtigen Verzeichnis heraus aufgerufen?
Falls nicht, sollte eine absolute Pfadangabe benutzt werden.

Falls die Korrektur nichts bringt, überprüfe, ob die Ausgabe ohne den AWK-Aufruf, also

cd ETSI_ITS_Standards; ls -1 *.pdf

eine Liste zeigt, die der Ausgabe aus Deinem Eingangs-Posting gleicht.
Falls nicht, poste bitte die Ausgabe (bzw. einen Teil davon).

Soll der Code des ersten Scripts hier rein?
Falls ja, wie soll das dann ablaufen?
Zuerst alles herunterladen und umbenennen, und anschließend überflüssige Versionen löschen?
Falls ja, kannst Du das Löschen auch in das awk-Script einbauen (an Stelle des print-Befehls einen system-Befehl) und am Ende Deines zweiten Scripts diese Zeile anhängen:

awk '{if(NR>1 && S==$4) system("rm \""L"\""); S=$4; L=$0}' < <(ls *.pdf)
1 Like

Danke für die Mühe. Ich bin gerade dabei, das ganze umzusetzen. Ich habe auch die Vorgehensweise etwas geändert. Das Scritp macht folgendes:

  • Anlegen des Ordners ETSI_ITS_Standards (als Unterordner der Ordners, wo das Script liegt)
  • Wechsle in das Verzeichnis (Das Script wird immer pasend aufgerufen, so dass die relative Pfadangabe passt)
  • Download der Standards
  • Namen der PDF anpassen, damit awk funktiniert
  • alte Standards löschen
  • restliche pdf umbenennen aus pdf Meta-Infos

Nach dem Download habe ich folgende Dateien vorliegen (ich lade zum Testen nur einen Teil der Standards):

en_302571v000002c.pdf
en_302571v010101p.pdf
en_302571v010101v.pdf
en_302571v010200a.pdf
en_302571v010201p.pdf
en_302571v020000a.pdf
en_302663v010200a.pdf
en_302663v010201p.pdf
en_302663v010201v.pdf

Da awk Leerzeichen braucht, pass ich die Namen an:

for file in *.pdf
    do
        name=$(basename "$file")
        IFS=v read -r specNumber specVersion <<< "$name"
        specVersion=${specVersion//[!0-9]/}
        name="$specNumber $specVersion"
        name=${name//_/ }
        mv "$file" "$name.pdf"
    done

Jetzt heißen die Dateien wie folgt:

en 302571 000002.pdf
en 302571 010101.pdf
en 302571 010200.pdf
en 302571 010201.pdf
en 302571 020000.pdf
en 302663 010200.pdf
en 302663 010201.pdf

Hier liegt die Änderung zu vorher. Vorher hab ich die Dateien gleich nach dem Meta-Informationen benannt. Das mach ich jetzt erst, wenn die anderen Aktionen abgeschlossen sind.

Auf die Dateien, die jetzt wie oben benannt sind, lass ich awk los:

awk '{if(NR>1 && S==$4) system("rm \""L"\""); S=$4; L=$0}' < <(ls *.pdf)

Heraus kommt:

en 302663 010201.pdf

Awk hat also bei dem Standard 302663 alles richtig gemacht und mir nur die Version mit der größten Versionsnummer behalten. Allerdings hat es bei dem Standard 302571 alle Dateien gelöscht.

Das Testscript (mit einer kleineren Auswahl an Standards) sieht dann so aus:

#!/bin/bash

specFolder="ETSI_ITS_Standards"

# List of relevant standards --> e.g. ETSI EN 302 636-5-1 needs to be converted manually to en_3026360501
listOfStandards=("en_302571" "en_302663")

if ! [ -d $specFolder ]
then
    mkdir $specFolder
fi
cd ETSI_ITS_Standards || exit

# Download files
for stand in "${listOfStandards[@]}"
  do
    IFS=_ read -r specType specNumber <<< "$stand"
    tmpNumb=$( echo "$specNumber" |cut -c1-4 )
    url="http://www.etsi.org/deliver/etsi_$specType/${tmpNumb}00_${tmpNumb}99/${specNumber}/"
    wget -r --no-parent -l 0 -nd -nc -e robots=off -A .pdf "$url"
 done
for file in *.pdf
    do
        name=$(basename "$file")

        IFS=v read -r specNumber specVersion <<< "$name"
        specVersion=${specVersion//[!0-9]/}
        name="$specNumber $specVersion"
        name=${name//_/ }
        mv "$file" "$name.pdf"
    done

awk '{if(NR>1 && S==$4) system("rm \""L"\""); S=$4; L=$0}' < <(ls *.pdf)

#TODO: rename files with title-tag

Ich versuche mal zu erklären, was das awk-Script macht. Das dürfte dir das Ganze etwas leichter machen.

Zuerst mal zu den Varablen, die dort genutzt werden:
NR ist die aktuelle Zeilennummer, beginnend mit 1. $0 ist die komplette aktuelle Zeile und $4 ist die 4. Spalte der aktuellen Zeile. Diese Variablen werden automatich von awk gesetzt, Zeilen werden durch \n und Spalten durch Leerzeichen getrennt (da es hier nicht anders definiert wurde).
S und L sind benutzerdefinierte Variablen, wobei in S (mit dem Befehl S=$4 ) die letzte Standard-Nummer als Zeichenkette gespeichert wird, welche als Vergleichswert in der if-Abfrage dient, und in L wird die letzte Zeile (auch als Zeichenkette) gespeichert, die der Dateiname ist, der dann vom print- bzw. system-Befehl aufgerufen wird.

Der Ablauf sieht so aus:
Wenn die Zeilennummer 1 ist oder S sich ändert (das wird in der ersten Zeile nicht geprüft, weil NR gleich 1 ist), wird die if-Bedingung nicht erfüllt und dann werden nur die S-Variable und die L-Variable gesetzt. Das gleiche gilt, wenn die Zeilennummer größer 1 ist und S sich ändert (also bei einer neuen Standard-Nummer).
Nur wenn die Zeilennummer größer 1 ist und sich S nicht ändert (also die Bedingung (NR>1 && S==$4) erfüllt wird), wird der Befehl (hier system("rm \""L"\"") ) Ausgeführt.

Effektiv werden dadurch alle gleichen Standard-Nummern gelöscht, ausser der letzten jeder Serie. Dabei wird nicht geprüft, welche Version das ist, da davon ausgegangen wird, dass der ls-Befehl die Ausgabe nach Namen in aufsteigender Reihenfolge sortiert und dadurch die Version mit der höchsten Nummer die jeweils letzte Position einer Standard-Serie hat.

Wenn also die Standard-Nummer (nach der geprüft wird) jetzt in Spalte 2 und nicht mehr in 4 ist, muss das im Script angepasst werden:

awk '{if(NR>1 && S==$2) system("rm \""L"\""); S=$2; L=$0}' < <(ls *.pdf)
1 Like

Hervoragend. Mit der Erklärung weiss ich jetzt, was awk hier genau macht und es funktioniert alles.

Danke sehr.

awk, sed und regexp sind für mich immernoch Zauberei, aber so langsam steig ich dahinter.

Gut, freut mich.

Habe bei mir immer die awk-Manpage offen, wenn ich Scripte. Der Einstieg war für mich auch nicht ganz leicht, aber mittlerweile nutzte ich awk viel, da es für kleinere Aufgaben schnell und effektiv ist und auch auf ausgefallenen und leistungsschwachen Systemen funzt, wie meinem alten Sat-Reciever DreamBox 7020-S (noch keine HDTV-Unterstützung). :slight_smile:

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.

Forum kindly sponsored by