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

2

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

Das Szenario sucht / greift in den Protokolldateien. Um meinen Standpunkt zu verdeutlichen, werde ich verwenden ls. Nehmen wir also an, ich führe lseine Reihe von Dateien im Verzeichnis aus

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

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

Ich habe ls mit der Zeit ausgeführt und zuerst eine Liste von Parametern in geschweiften Klammern verwendet und sie später in ein Sternchen geändert, um die Unterschiede zu erkennen. Ich habe natürlich nicht damit gerechnet, dass der Stern eine bessere Leistung bringt.

   emartinez@serverlog:~$ time ls /var/log/remote/server{4,5,9,10}.domain.local/ps/ps2.log.2014-10-0{1,2}.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{4,5,9,10}.domain.local/ps/ps2.log.2014-10-0*.gz

Und ich bekomme die folgenden Statistiken:

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

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

Ich habe den Test erneut ausgeführt, aber dieses Mal habe ich die Monate durch eine Liste {1..12} ersetzt, da die Ergebnisse konsistent sind:

ps2.log.2014-{1..12}-0{1,2}.gz : real 0m0.010s
ps2.log.2014-{1..12}-0*.gz     : real 0m0.168s

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

runlevel0
quelle

Antworten:

2

Es mag prefix-*zum Beispiel so aussehen, als ob es einfach sein sollte, sich zu verwandeln prefix-1 prefix-2, da wir es gewohnt sind, Verzeichnislisten sortiert zu sehen. Es stellt sich jedoch heraus, dass nur sehr wenige Dateisysteme tatsächlich sortierte Dateinamenlisten erstellen können und dass es keine Standard-API gibt, um nach sortierten Dateinamenlisten zu fragen.

Wenn ein Programm - wie zum Beispiel lsoder bash- eine Liste von Dateinamen benötigt, muss es die gesamte Verzeichnisliste lesen, die in zufälliger Reihenfolge erstellt wird (oft hängt die Reihenfolge mit der Erstellungszeit zusammen; manchmal) Es basiert auf einem Hash des Dateinamens, aber in keinem Fall ist es eine einfache alphabetische Reihenfolge. Um dies zu beheben prefix-*, müssen Sie das gesamte Verzeichnis lesen und jeden Dateinamen anhand des Musters überprüfen. Da der kostspieligste Teil dieser Prozedur das Lesen des Verzeichnisses ist, spielt es keine Rolle, wie komplex das Muster ist oder wie viele Dateinamen mit dem Muster übereinstimmen.

Zusammenfassend wird die Pfadnamenerweiterung ("Auflösen von Globs") in einem großen Verzeichnis langsam sein. Das ist ein Grund, große Verzeichnisse zu vermeiden, und kein Grund, Globs zu vermeiden.

Es gibt aber noch einen anderen wichtigen Datenpunkt: Es prefix-{1,2}handelt sich nicht um eine Pfadnamenerweiterung. Es handelt sich um eine " Klammererweiterung " und eine Erweiterung des Posix-Shell-Standards (obwohl fast alle Shells dies implementieren). Es gibt eine Reihe von Unterschieden zwischen der Erweiterung von geschweiften Klammern und der Erweiterung von Pfadnamen. Ein wichtiger und relevanter Unterschied besteht jedoch darin, dass die Erweiterung von geschweiften Klammern nicht von der Existenz von Dateien abhängt . Die Klammererweiterung ist eine einfache Zeichenfolgeoperation.

Folglich prefix-{1,2}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 speichern. Klar, das wird schnell gehen. Aber es gibt 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{1,2,3,4}
ls: cannot access file3: No such file or directory
file1 file2 file4

Letzter Punkt: Die Pfadnamenerweiterung wird von der Shell durchgeführt, nicht von ls. Mit der Pfadnamenerweiterung könnten wir genauso gut Folgendes verwenden echo:

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

Und echowird die Liste etwas schneller produzieren, weil alles echo, was zu tun ist, seine Argumente zu drucken ist, während ls(die die gleichen Argumente erhalten) zu statjedem Argument muss, um zu überprüfen, ob es eine Datei ist. Das stat- was kein billiger Anruf ist - ist bei einer Pfadnamenerweiterung völlig überflüssig, da die Shell die Verzeichnisliste bereits zum Filtern der Dateiliste verwendet hat und daher jeder übergebene Dateiname lsals vorhanden bekannt ist. (Es sei denn, der Glob hat überhaupt keine Dateien gefunden.)

Darüber hinaus ist Echo bashintegriert, sodass es aufgerufen werden kann, ohne einen untergeordneten Prozess zu erstellen.

Bei einer Klammererweiterung wird echojedoch nicht dasselbe Ergebnis erzielt:

$ echo file{1,2,3,4}
file1 file2 file3 file4

Wir könnten also verwenden ls, um die Fehlerausgabe in den Bit-Bucket umzuleiten:

$ ls file{1,2,3,4}
file1 file2 file4

In diesem Fall sind die statAufrufe nicht redundant, da die Shell die Dateinamen nie überprüft hat.

Sofern Ihre Verzeichnisse nicht wirklich riesig sind, wird dies keinen großen Unterschied machen und das Schreiben des Glob wird viel einfacher. Wenn Ihre Verzeichnisse sind wirklich riesig, sollten Sie sie in kleinere Unterverzeichnisse aufzuteilen.

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 eine unbegrenzte Vergrößerung des Verzeichnisses zu vermeiden:

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

( 2014wird absichtlich wiederholt.)

Das Sharding der Verzeichnisse ist normalerweise ein großer Gewinn, da es einen Mechanismus zur Optimierung des Globbing bietet. Wie oben erwähnt, kann die Shell nicht optimieren

/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 ein Abgleich mit den Verzeichnisnamen durchgeführt werden. Anschließend muss ps2.log.2014-10-*-gznur noch ein Abgleich mit den Dateinamen in den abgeglichenen Verzeichnissen durchgeführt werden.

rici
quelle
Vielen Dank Kumpel! Großartige Lektüre. Ich kann dich leider nicht abstimmen, da mein Repräsentant in diesem Forum nur 6 ist. Aber nochmals vielen Dank!
Runlevel0
1

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

Also ein Befehl wie

echo {1..3}*

wird zunächst auf 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 Dateien sich im Verzeichnis befinden, desto langsamer wird dies. Auch in einem leeren Verzeichnis,

shopt -s nullglob  # print nothing for non-matching words
echo {1..1000000}* # prints nothing
shopt -u nullglob  # back to the default

dauert fast fünf Sekunden auf meinem Computer. Dies ist überhaupt nicht überraschend, wenn man bedenkt, dass die Dateinamenerweiterung eine Million Mal durchgeführt wird ...

Eine viel schnellere Alternative besteht darin , beide Arten der Schalenerweiterung möglichst nicht zu kombinieren .

Der Befehl

echo [1-1000000]* # also prints nothing

Sucht nach denselben Dateinamen, verwendet jedoch ein einzelnes Muster. Dies dauert 33 Millisekunden auf meinem Computer.

Die Verwendung von eckigen Klammern anstelle von geschweiften Klammern bietet zusätzliche Vorteile:

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

Der erste Ansatz hat die Datei zweimal gefunden, da sie mit den Mustern 1*und übereinstimmt 13*. Dies geschieht nicht bei "reiner" Dateinamenerweiterung.

Dennis
quelle
Vielen Dank auch !! Wie ich oben kommentiert habe, habe ich nicht genug Repräsentanten, um dich zu wählen. Beide Antworten sind äußerst aufschlussreich und auch nützlich.
Runlevel0