Bash-Parameter Expansion: Best Practices für Geschwindigkeit?

510
runlevel0

Ich habe mich nur gefragt, ob jemand Best Practices kennt oder ob es eine Dokumentation zu diesem Thema gibt:

Das Szenario sucht / grepping in den Protokolldateien. Um meine Meinung zu machen, werde ich verwenden ls. Nehmen wir also an, dass ich lseine Reihe von Dateien im Verzeichnis aufliste

/var/log/remote/serverX.domain.local/ps/ps2.log.2014-mm-dd.gz

Wo mm und dd Monats- und Tagzahlen sind, gibt es neben serverX auch eine ganze Reihe von Servern (für das Beispiel verwende ich 4,5,9,10 (dies sind echte Server)

Ich habe ls mit der Zeit ausgeführt, indem ich zuerst eine Liste von Parametern in geschweiften Klammern verwendete und sie später in ein Sternchen umwandelte, um die Unterschiede zu sehen. Ich habe natürlich nicht erwartet, dass der Stern besser abschneidet.

 emartinez@serverlog:~$ time ls /var/log/remote/server.domain.local/ps/ps2.log.2014-10-0.gz /var/log/remote/server10.domain.local/ps/ps2.log.2014-10-01.gz  ... /var/log/remote/server5.domain.local/ps/ps2.log.2014-10-02.gz  real 0m0.004s user 0m0.010s sys 0m0.000s 

Dann ersetze ich die letzte geschweifte Klammer durch ein Sternchen:

time ls /var/log/remote/server.domain.local/ps/ps2.log.2014-10-0*.gz 

Und ich bekomme folgende Statistiken:

 real 0m0.028s user 0m0.020s sys 0m0.020s 

Dies ist ein großer Unterschied, obwohl es nur zwei Optionen gibt, da die verfügbaren Daten nur 01 und 02 vom Oktober sind.

Ich habe den Test erneut ausgeführt, aber dieses Mal habe ich die Monate für eine Liste ersetzt, die Ergebnisse sind konsistent:

ps2.log.2014--0.gz : real 0m0.010s ps2.log.2014--0*.gz : real 0m0.168s 

Das ist ein großer Unterschied für nur ein Sternchen !!! Es macht Sinn, dass dies langsamer ist, aber gibt es Benchmarks, wie viel langsamer und gibt es Best Practices, die irgendwo beschrieben werden?

2

2 Antworten auf die Frage

2
rici

Es scheint, als prefix-*sollte es leicht sein, daraus zu wechseln, zum Beispiel prefix-1 prefix-2, da wir es gewohnt sind, Verzeichnisse sortiert zu sehen. Es stellt sich jedoch heraus, dass nur wenige Dateisysteme tatsächlich sortierte Dateinamenlisten erzeugen können, und außerdem gibt es keine Standard-API für die Abfrage von sortierten Dateinamenlisten.

Wenn ein Programm - wie beispielsweise lsoder in diesem Zusammenhang bash- eine Liste von Dateinamen benötigt, muss es die gesamte Verzeichnisliste lesen, die in einer zufälligen Reihenfolge erstellt wird (oft hängt die Reihenfolge mit der Erstellungszeit zusammen, manchmal auch mit dem Erstellungszeitpunkt) Es basiert auf einem Hash des Dateinamens, aber in ziemlich keinem Fall handelt es sich um eine einfache alphabetische Reihenfolge. Um sie aufzulösen prefix-*, müssen Sie das gesamte Verzeichnis lesen und jeden Dateinamen anhand des Musters prüfen. Da das teuerste Teil dieser Prozedur das Lesen des Verzeichnisses ist, macht es wenig aus, wie komplex das Muster ist oder wie viele Dateinamen mit dem Muster übereinstimmen.

Zusammenfassend wird die Erweiterung des Pfadnamens ("Auflösen von Globs") in einem großen Verzeichnis langsam sein. Das ist ein Grund, um große Verzeichnisse zu vermeiden, und nicht als Grund, um Globs zu vermeiden.

Aber es gibt eine weiteren wichtigen Datenpunkt: prefix-ist nicht Pfadname Expansion. Es handelt sich hierbei um " Klammererweiterung " und um eine Erweiterung des Posix-Shell-Standards (obwohl fast alle Shells es implementieren). Es gibt eine Reihe von Unterschieden zwischen der Erweiterung der Klammer und der Erweiterung des Pfadnamens. Ein wichtiger und relevanter Unterschied besteht jedoch darin, dass die Erweiterung der Klammer nicht von der Existenz von Dateien abhängt . Brace Expansion ist eine einfache Stringoperation.

Daher prefix-wird immer erweitert prefix-1 prefix-2, unabhängig davon, ob diese Dateien vorhanden sind oder nicht. Das heißt, es kann erweitert werden, ohne das Verzeichnis zu lesen und ohne stateine Datei zu erstellen. Das wird natürlich schnell gehen. Es gibt jedoch einen Nachteil: Es gibt keine Möglichkeit festzustellen, ob das Ergebnis echten Dateien entspricht.

Betrachten Sie das folgende einfache Beispiel:

$ mkdir test && cd test $ touch file1 file2 file4 $ ls file* file1 file2 file4 $ ls file[1234] file1 file2 file4 $ ls file ls: cannot access file3: No such file or directory file1 file2 file4 

Schlusspunkt: Die Erweiterung des Pfadnamens erfolgt durch die Shell, nicht durch ls. Mit der Erweiterung des Pfadnamens könnten wir genauso gut Folgendes verwenden echo:

$ echo file* file1 file2 file4 $ echo file[1234] file1 file2 file4 

Und echodie Liste wird etwas schneller erstellt, da nur echodie Argumente gedruckt werden müssen, während ls(die dieselben Argumente erhalten) statjedes Argument vorhanden ist, um zu überprüfen, ob es sich um eine Datei handelt. Das stat- was kein billiger Aufruf ist - ist bei einer Erweiterung des Pfadnamens völlig überflüssig, da die Shell die Verzeichnisliste bereits verwendet hat, um die Dateiliste zu filtern, und daher ist jeder übergebene Dateiname lsbekannt. (Es sei denn, der Glob hat überhaupt keine Dateien gefunden.)

Außerdem ist echo ein bashintegriertes System, sodass es aufgerufen werden kann, ohne einen untergeordneten Prozess zu erstellen.

Im Falle der Klammerausdehnung führt dies echojedoch nicht zum gleichen Ergebnis:

$ echo file file1 file2 file3 file4 

Wir könnten also die lsFehlerausgabe in den Bit-Bucket umleiten:

$ ls file file1 file2 file4 

In diesem Fall sind die statAufrufe nicht redundant, da die Dateinamen nicht von der Shell überprüft wurden.

Wenn Ihre Verzeichnisse nicht wirklich riesig sind, wird dies alles keinen großen Unterschied machen und der Glob wird viel einfacher zu schreiben sein. Wenn Ihre Verzeichnisse sehr umfangreich sind, sollten Sie sie in kleinere Unterverzeichnisse aufteilen.

Zum Beispiel anstelle von Pfaden wie:

/var/log/remote/serverX.domain.local/ps/ps2.log.2014-mm-dd.gz 

Du könntest benutzen:

/var/log/remote/serverX/domain.local/ps/ps2.log.2014-mm-dd-gz 

Wenn Sie die Protokolle für immer aufbewahren, möchten Sie möglicherweise das Jahr extrahieren, um die Verzeichnisgröße nicht unendlich zu vergrößern:

/var/log/remote/2014/serverX/domain.local/ps/ps2.log.2014-mm-dd-gz 

( 2014wird absichtlich wiederholt.)

Das Scherben der Verzeichnisse ist in der Regel ein großer Gewinn, da es einen Mechanismus zur Optimierung des Globings bietet. Wie oben erwähnt, kann die Schale nicht optimiert werden

/var/log/remote/server[2357].domain.local/ps/ps2.log.2014-10-*-gz 

aber es kann optimieren

/var/log/remote/server[2357]/domain.local/ps/ps2.log.2014-10-*-gz 

Im zweiten Fall muss server[2357]nur mit den Verzeichnisnamen abgeglichen werden. Sobald dies geschehen ist, müssen ps2.log.2014-10-*-gznur die Dateinamen in den übereinstimmenden Verzeichnissen abgeglichen werden.

Vielen Dank Kumpel! Tolle Lektüre. Ich kann Sie leider nicht wählen, da mein Vertreter in diesem Forum nur 6 ist. Vielen Dank noch einmal! runlevel0 vor 9 Jahren 0
1
Dennis

Die Shell-Erweiterung wird immer in einer bestimmten Reihenfolge ausgeführt. Die Klammererweiterung wird zuerst ausgeführt, die Dateinamenerweiterung wird zuletzt ausgeführt.

Also ein Befehl wie

echo * 

wird zuerst erweitert

echo 1* 2* 3* 

dann wird die Dateinamen Expansion durchgeführt für 1*, 2*und 3*. Bei jeder Erweiterung werden alle Dateinamen im Verzeichnis durchsucht und mit dem Muster verglichen.

Je mehr Wörter und / oder wie viele Dateien im Verzeichnis vorhanden sind, desto langsamer wird dies. Auch in einem leeren Verzeichnis

shopt -s nullglob # print nothing for non-matching words echo * # prints nothing shopt -u nullglob # back to the default 

dauert fast fünf Sekunden auf meiner Maschine. Dies ist nicht überraschend, wenn Sie bedenken, dass die Dateinamenerweiterung eine Million Mal ausgeführt wird ...

Eine viel schnellere Alternative ist, die Kombination beider Typen der Shell-Erweiterung möglichst zu vermeiden .

Der Befehl

echo [1-1000000]* # also prints nothing 

sucht nach den gleichen Dateinamen, verwendet jedoch ein einzelnes Muster. Dies dauert 33 Millisekunden auf meiner Maschine.

Die Verwendung eckiger Klammern anstelle von geschweiften Klammern hat zusätzliche Vorteile:

$ touch 13 $ echo * 13 13 $ echo [1..20]* 13 

Beim ersten Ansatz wurde die Datei zweimal gefunden, da sie mit den Mustern 1*und übereinstimmt 13*. Dies geschieht nicht bei einer "reinen" Dateinamenerweiterung.

Thaks viel auch !! Wie ich oben kommentiert habe, habe ich nicht genug Vertreter, um Sie abzustimmen. Beide Antworten sind äußerst aufschlussreich und auch nützlich. runlevel0 vor 9 Jahren 0