Ich habe einen Server, der jeden Tag eine Datei pro Client in ein Verzeichnis empfängt. Die Dateinamen sind wie folgt aufgebaut:
uuid_datestring_other-data
Zum Beispiel:
d6f60016-0011-49c4-8fca-e2b3496ad5a7_20160204_023-ERROR
uuid
ist ein Standardformat uuid.datestring
ist die Ausgabe vondate +%Y%m%d
.other-data
ist in der Länge variabel, enthält jedoch niemals einen Unterstrich.
Ich habe eine Datei im Format:
#
d6f60016-0011-49c4-8fca-e2b3496ad5a7 client1
d5873483-5b98-4895-ab09-9891d80a13da client2
be0ed6a6-e73a-4f33-b755-47226ff22401 another_client
...
Ich muss mit bash überprüfen, ob jede in der Datei aufgeführte UUID eine entsprechende Datei im Verzeichnis hat.
Ich bin so weit gekommen, aber ich habe das Gefühl, dass ich mit einer if-Anweisung aus der falschen Richtung komme und die Dateien im Quellverzeichnis durchlaufen muss.
Die Variablen source_directory und uuid_list wurden bereits früher im Skript zugewiesen:
# Check the entries in the file list
while read -r uuid name; do
# Ignore comment lines
[[ $uuid = \#* ]] && continue
if [[ -f "${source_directory}/${uuid}*" ]]
then
echo "File for ${name} has arrived"
else
echo "PANIC! - No File for ${name}"
fi
done < "${uuid_list}"
Wie soll ich überprüfen, ob die Dateien in meiner Liste im Verzeichnis vorhanden sind? Ich möchte die Bash-Funktionalität so weit wie möglich nutzen, bin aber nicht gegen die Verwendung von Befehlen, wenn dies erforderlich ist.
quelle
Antworten:
Gehen Sie über die Dateien und erstellen Sie ein assoziatives Array über die in ihren Namen enthaltenen UUIDs (ich habe die Parametererweiterung verwendet, um die UUID zu extrahieren). Lesen Sie die Liste, überprüfen Sie das assoziative Array für jede UUID und geben Sie an, ob die Datei aufgezeichnet wurde oder nicht.
quelle
cd
in das Verzeichnis innerhalb des Skripts gelangen kann, habe mich aber nur gefragt, um Wissen zu erlangen.file=${file##*/}
.Hier ist ein "bashy" und prägnanter Ansatz:
Beachten Sie, dass das oben Genannte zwar hübsch ist und für einige Dateien gut funktioniert, seine Geschwindigkeit jedoch von der Anzahl der UUIDs abhängt und sehr langsam ist, wenn Sie viele verarbeiten müssen. Wenn dies der Fall ist, verwenden Sie entweder die Lösung von @ choroba oder vermeiden Sie für etwas wirklich Schnelles die Shell und rufen Sie auf
perl
:Um die Zeitunterschiede zu veranschaulichen, habe ich meinen Bash-Ansatz, Chorobas und meinen Perl an einer Datei mit 20000 UUIDs getestet, von denen 18001 einen entsprechenden Dateinamen hatte. Beachten Sie, dass jeder Test ausgeführt wurde, indem die Ausgabe des Skripts auf umgeleitet wurde
/dev/null
.Meine Bash (~ 3,5 min)
Chorobas (Bash, ~ 0,7 Sek.)
Mein Perl (~ 0,1 Sek.):
quelle
cd
in das Verzeichnis im Skript gelangen kann, aber gibt es eine Methode, mit der der Dateipfad in die Suche einbezogen werden kann?${source_directory}
genau wie in Ihrem Skript verwenden."$2"
und übergeben Sie es als zweites Argument an das Skript.Dies ist reiner Bash (dh keine externen Befehle), und es ist der zufälligste Ansatz, den ich mir vorstellen kann.
Aber in Bezug auf die Leistung ist es wirklich nicht viel besser als das, was Sie derzeit haben.
Es wird jede Zeile von gelesen
path/to/file
; für jede Zeile, wird es das erste Feld speichert in$uuid
und gibt eine Meldung aus, wenn eine Datei , um die Pattern - Matchingpath/to/directory/$uuid*
wird nicht gefunden:Nennen Sie es mit
path/to/script path/to/file path/to/directory
.Beispielausgabe unter Verwendung der Beispieleingabedatei in der Frage in einer Testverzeichnishierarchie, die die Beispieldatei in der Frage enthält:
quelle
Hier geht es nicht darum, Fehler zu melden, die die Shell für Sie meldet. Wenn Sie versuchen,
<
eine nicht vorhandene Datei zu öffnen, beschwert sich Ihre Shell. Tatsächlich wird der Fehlerausgabe das Skript$0
und die Zeilennummer, in der der Fehler aufgetreten ist, vorangestellt. Dies sind gute Informationen, die standardmäßig bereits bereitgestellt werden. Machen Sie sich also keine Sorgen.Sie müssen die Datei auch nicht so zeilenweise aufnehmen - sie kann sehr langsam sein. Dies erweitert das Ganze in einem einzigen Schuss auf ein durch Leerzeichen getrenntes Array von Argumenten und behandelt jeweils zwei Argumente. Wenn Ihre Daten mit Ihrem Beispiel übereinstimmen,
$1
sind sie immer Ihre UUID und$2
Ihre$name
. Wenn Siebash
eine Übereinstimmung mit Ihrer UUID eröffnen können - und nur eine solche Übereinstimmung existiert -,printf
geschieht dies. Andernfalls ist dies nicht der Fall und die Shell schreibt Diagnosen an stderr, warum.quelle
unset IFS
stellt sicher, dass$(cat <uuid_file)
auf Leerraum aufgeteilt wird. Muscheln teilen sich$IFS
unterschiedlich auf, wenn sie nur aus Leerzeichen bestehen oder nicht gesetzt sind. Solche geteilten Erweiterungen haben niemals Nullfelder, da alle Leerraumsequenzen nur als ein einziges Feldtrennzeichen stehen. Solange es in jeder Zeile nur zwei nicht durch Leerzeichen getrennte Felder gibt, sollte es funktionieren, denke ich. inbash
, sowieso.set -f
stellt sicher, dass die nicht zitierte Erweiterung nicht für Globs interpretiert wird, und set + f stellt sicher, dass die späteren Globs vorhanden sind.<>
da dadurch eine nicht vorhandene Datei erstellt wird.<
werde berichten, wie ich es gemeint habe. Das mögliche Problem dabei - und der Grund, warum ich es überhaupt falsch verwendet habe<>
- ist, dass es hängen bleibt, wenn es sich um eine Pipe-Datei ohne Lesegerät oder wie ein zeilengepufferter char dev handelt. Dies könnte vermieden werden, indem die Fehlerausgabe expliziter behandelt und ausgeführt wird[ -f "$dir/$1"* ]
. Wir sprechen hier von UUIDs, daher sollte es niemals auf mehr als eine einzelne Datei erweitert werden. es ist aber ein bisschen nett, wie es die fehlgeschlagenen Dateinamen so an stderr meldet.<>
wäre daher immer noch auf diese Weise verwendbar ...<>
ist besser, wenn der Glob in ein Verzeichnis erweitert wird, da unter Linux das Lesen / Schreiben erfolgt scheitern und sagen - das ist ein Verzeichnis.bash
ein Umleitungsglob nur akzeptiert wird, wenn er nur mit einer Datei übereinstimmt. sieheman bash
unter Umleitung.Die Art und Weise, wie ich es angehen würde, besteht darin, zuerst UUIDs aus der Datei zu holen und dann zu verwenden
find
Zur besseren Lesbarkeit
Beispiel mit einer Liste von Dateien in
/etc/
, die nach den Dateinamen passwd, group, fstab und THISDOESNTEXIST suchen.Da Sie das Verzeichnis ist flach erwähnt haben, könnten Sie die verwenden
-printf "%f\n"
Option , um Dateinamen selbst nur druckenDies führt nicht dazu, dass fehlende Dateien aufgelistet werden.
find
Der kleine Nachteil ist, dass es Ihnen nicht sagt, ob es keine Datei findet, sondern nur, wenn es mit etwas übereinstimmt. Was man jedoch tun könnte, ist die Ausgabe zu überprüfen - wenn die Ausgabe leer ist, fehlt eine DateiBesser lesbar:
Und so funktioniert es als kleines Skript:
Man könnte es
stat
als Alternative verwenden, da es sich um ein flaches Verzeichnis handelt, aber der folgende Code funktioniert nicht rekursiv für Unterverzeichnisse, wenn Sie sich jemals dazu entschließen, diese hinzuzufügen:Wenn wir die
stat
Idee aufgreifen und damit arbeiten, können wir den Exit-Code von stat als Hinweis darauf verwenden, ob eine Datei vorhanden ist oder nicht. Tatsächlich wollen wir dies tun:Probelauf:
quelle