Wird die Verwendung einer while-Schleife zum Verarbeiten von Text in POSIX-Shells allgemein als unangemessen angesehen?
Wie Stéphane Chazelas betonte, sind einige der Gründe, warum Shell Loop nicht verwendet wird, Konzept , Zuverlässigkeit , Lesbarkeit , Leistung und Sicherheit .
Diese Antwort erklärt die Zuverlässigkeits- und Lesbarkeitsaspekte :
while IFS= read -r line <&3; do
printf '%s\n' "$line"
done 3< "$InputFile"
Für die Leistung sind die while
Schleife und das Lesen beim Lesen aus einer Datei oder einer Pipe enorm langsam, da die eingebaute Read-Shell jeweils ein Zeichen liest.
Wie wäre es mit konzeptionellen und Sicherheitsaspekten ?
shell
text-processing
cuonglm
quelle
quelle
yes
schreibt man so schnell in eine Datei?bash
liest es jeweils eine Puffergröße, versuchen Sie esdash
zum Beispiel. Siehe auch unix.stackexchange.com/q/209123/38906Antworten:
Ja, wir sehen eine Reihe von Dingen wie:
Oder schlimmer:
(nicht lachen, ich habe viele davon gesehen).
In der Regel von Shell-Scripting-Anfängern. Das sind naive wörtliche Übersetzungen dessen, was Sie in imperativen Sprachen wie C oder Python tun würden, aber so tun Sie Dinge nicht in Shells, und diese Beispiele sind sehr ineffizient, völlig unzuverlässig (was möglicherweise zu Sicherheitsproblemen führt) und falls Sie es jemals schaffen Um die meisten Fehler zu beheben, wird Ihr Code unleserlich.
Konzeptionell
In C oder den meisten anderen Sprachen liegen die Bausteine nur eine Ebene über den Computeranweisungen. Sie teilen Ihrem Prozessor mit, was als Nächstes zu tun ist. Sie nehmen Ihren Prozessor bei der Hand und verwalten ihn im Mikromodus: Sie öffnen diese Datei, Sie lesen so viele Bytes, Sie tun dies, Sie tun das damit.
Muscheln sind eine höhere Sprache. Man kann sagen, es ist nicht einmal eine Sprache. Sie stehen vor allen Befehlszeileninterpreten. Die Aufgabe wird von den Befehlen erledigt, die Sie ausführen, und die Shell soll sie nur orchestrieren.
Eines der großartigen Dinge, die Unix eingeführt hat, waren die Pipe und die Standard-Streams stdin / stdout / stderr, die standardmäßig von allen Befehlen verarbeitet werden.
In 45 Jahren haben wir keine bessere API gefunden, um die Leistungsfähigkeit von Befehlen zu nutzen und sie bei einer Aufgabe zusammenarbeiten zu lassen. Das ist wahrscheinlich der Hauptgrund, warum die Leute heute noch Muscheln benutzen.
Sie haben ein Schneidwerkzeug und ein Transliterationswerkzeug und können einfach Folgendes tun:
Die Shell erledigt nur die Installation (Dateien öffnen, Pipes einrichten, Befehle aufrufen) und wenn alles fertig ist, fließt sie einfach, ohne dass die Shell etwas unternimmt. Die Werkzeuge erledigen ihre Arbeit gleichzeitig und effizient in ihrem eigenen Tempo, wobei genügend Puffer vorhanden sind, damit nicht einer den anderen blockiert. Es ist einfach wunderschön und doch so einfach.
Das Aufrufen eines Tools ist jedoch mit Kosten verbunden (und diese werden wir im Hinblick auf die Leistung entwickeln). Diese Tools können mit Tausenden von Anweisungen in C geschrieben sein. Es muss ein Prozess erstellt, das Tool geladen, initialisiert, dann bereinigt, der Prozess zerstört und gewartet werden.
Beim Aufrufen
cut
wird die Küchenschublade geöffnet. Nehmen Sie das Messer, verwenden Sie es, waschen Sie es, trocknen Sie es und legen Sie es wieder in die Schublade. Wenn Sie das tun:Es ist, als würde man für jede Zeile der Datei das
read
Werkzeug aus der Küchenschublade holen (ein sehr ungeschicktes, weil es nicht dafür vorgesehen ist ), eine Zeile lesen, das Lesewerkzeug waschen und es wieder in die Schublade legen. Planen Sie dann eine Besprechung für das Werkzeugecho
undcut
, holen Sie sie aus der Schublade, rufen Sie sie auf, waschen Sie sie, trocknen Sie sie, legen Sie sie wieder in die Schublade und so weiter.Einige dieser Werkzeuge (
read
undecho
) sind in den meisten Schalen gebaut, aber das macht kaum einen Unterschied hier , daecho
undcut
müssen noch in separaten Prozessen ausgeführt werden.Es ist, als würde man eine Zwiebel schneiden, aber man wäscht das Messer und legt es zwischen die Scheiben in die Küchenschublade zurück.
Hier ist es naheliegend, das
cut
Werkzeug aus der Schublade zu holen , die ganze Zwiebel in Scheiben zu schneiden und nach Beendigung der gesamten Arbeit wieder in die Schublade zu legen.In Shells, insbesondere zum Verarbeiten von Text, rufen Sie so wenige Dienstprogramme wie möglich auf und lassen sie bei der Ausführung der Aufgabe zusammenarbeiten. Führen Sie nicht Tausende von Tools nacheinander aus, während Sie darauf warten, dass jedes gestartet, ausgeführt und bereinigt wird, bevor Sie das nächste ausführen.
Lesen Sie weiter in Bruce's feiner Antwort . Die internen Tools für die einfache Textverarbeitung in Shells (mit Ausnahme von
zsh
) sind begrenzt, umständlich und im Allgemeinen nicht für die allgemeine Textverarbeitung geeignet.Performance
Wie bereits erwähnt, ist das Ausführen eines Befehls mit Kosten verbunden. Ein enormer Aufwand, wenn dieser Befehl nicht eingebaut ist, aber selbst wenn er eingebaut ist, sind die Kosten hoch.
Und Shells sind nicht so konzipiert, sie geben keinen Anspruch darauf, performante Programmiersprachen zu sein. Sie sind nicht, sie sind nur Befehlszeileninterpreter. In dieser Hinsicht wurde wenig optimiert.
Außerdem führen die Shells Befehle in separaten Prozessen aus. Diese Bausteine haben keinen gemeinsamen Speicher oder Status. Wenn Sie ein
fgets()
oderfputs()
in C ausführen, ist dies eine Funktion in stdio. stdio speichert interne Puffer für die Ein- und Ausgabe aller stdio-Funktionen, um zu vermeiden, dass teure Systemaufrufe zu oft ausgeführt werden.Der entsprechende sogar eingebauten Schale Utilities (
read
,echo
,printf
) , kann das nicht tun.read
soll eine Zeile lesen. Wenn es nach dem Zeilenumbruchzeichen steht, wird es beim nächsten Ausführen des Befehls übersehen. Soread
muss die Eingabe ein Byte nach dem anderen gelesen werden (einige Implementierungen haben eine Optimierung, wenn die Eingabe eine reguläre Datei ist, indem sie Chunks lesen und zurücksuchen, aber das funktioniert nur für reguläre Dateien undbash
liest zum Beispiel nur 128-Byte-Chunks, was bedeutet noch viel weniger als Textdienstprogramme).Das Gleiche gilt für die Ausgabeseite. Sie
echo
kann nicht nur ihre Ausgabe puffern, sondern muss sie sofort ausgeben, da der nächste Befehl, den Sie ausführen, diesen Puffer nicht freigibt.Wenn Sie Befehle nacheinander ausführen, müssen Sie natürlich auf sie warten. Es ist ein kleiner Scheduler-Tanz, der die Steuerung von der Shell über die Tools bis hin zu den Werkzeugen übernimmt. Dies bedeutet auch, dass Sie (im Gegensatz zur Verwendung lang laufender Instanzen von Tools in einer Pipeline) nicht mehrere Prozessoren gleichzeitig nutzen können, wenn diese verfügbar sind.
Zwischen dieser
while read
Schleife und dem (angeblich) Äquivalentcut -c3 < file
gibt es in meinem Schnelltest ein CPU-Zeitverhältnis von ungefähr 40000 in meinen Tests (eine Sekunde gegenüber einem halben Tag). Aber auch wenn Sie nur Shell-Builtins verwenden:(hier mit
bash
), das ist immer noch ungefähr 1: 600 (eine Sekunde gegen 10 Minuten).Zuverlässigkeit / Lesbarkeit
Es ist sehr schwer, diesen Code richtig zu machen. Die Beispiele, die ich gegeben habe, werden zu oft in freier Wildbahn gesehen, aber sie haben viele Fehler.
read
ist ein praktisches Werkzeug, das viele verschiedene Dinge kann. Es kann Eingaben vom Benutzer lesen, in Wörter aufteilen und in verschiedenen Variablen speichern.read line
ist nicht eine Eingabezeile gelesen, oder vielleicht liest er eine Linie auf eine ganz besondere Art und Weise. Es liest tatsächlich Wörter aus der Eingabe, die durch einen$IFS
Backslash getrennt sind und mit denen die Trennzeichen oder das Newline-Zeichen ausgeblendet werden können.Mit dem Standardwert von
$IFS
, bei einer Eingabe wie:read line
speichert"foo/bar baz"
in$line
, nicht" foo\/bar \"
wie man erwarten würde.Um eine Zeile zu lesen, benötigen Sie tatsächlich:
Das ist nicht sehr intuitiv, aber so ist es, denken Sie daran, dass Muscheln nicht so verwendet werden sollten.
Gleiches gilt für
echo
.echo
erweitert Sequenzen. Sie können es nicht für beliebige Inhalte wie den Inhalt einer zufälligen Datei verwenden. Du brauchstprintf
stattdessen hier.Und natürlich gibt es das typische Vergessen, eine Variable zu zitieren, in die jeder hineinfällt. Es ist also mehr:
Nun noch ein paar Vorsichtsmaßnahmen:
zsh
, dass dies nicht funktioniert, wenn die Eingabe NUL-Zeichen enthält, während zumindest GNU-Textdienstprogramme das Problem nicht hätten.Wenn wir einige der oben genannten Probleme angehen möchten, wird dies folgendermaßen aussehen:
Das wird immer weniger lesbar.
Es gibt eine Reihe anderer Probleme bei der Übergabe von Daten an Befehle über die Argumente oder beim Abrufen ihrer Ausgabe in Variablen:
-
(oder+
manchmal) beginnenexpr
,test
...Sicherheitsaspekte
Wenn Sie anfangen, mit Shell- Variablen und Argumenten für Befehle zu arbeiten , geben Sie ein Minenfeld ein.
Wenn Sie vergessen, Ihre Variablen in Anführungszeichen zu setzen , das Ende der Optionsmarkierung zu vergessen , in Gebietsschemata mit Mehrbyte-Zeichen zu arbeiten (heutzutage die Norm), werden Sie mit Sicherheit Fehler einführen, die früher oder später zu Schwachstellen werden.
Wenn Sie Loops verwenden möchten.
TBD
quelle
cut
zum Beispiel ist effizient.cut -f1 < a-very-big-file
ist effizient, so effizient, wie Sie es in C schreiben würden. Was fürchterlich ineffizient und fehleranfällig ist, ruftcut
für jede Zeile einera-very-big-file
in einer Shell-Schleife auf, auf die in dieser Antwort verwiesen wird. Das stimmt mit Ihrer letzten Aussage über das Schreiben von unnötigem Code überein, die mich denken lässt, dass ich Ihren Kommentar vielleicht nicht verstehe.Was das Konzept und die Lesbarkeit betrifft, sind Shells normalerweise an Dateien interessiert. Ihre "adressierbare Einheit" ist die Datei und die "Adresse" ist der Dateiname. Shells bieten alle Arten von Methoden zum Testen der Existenz von Dateien, des Dateityps und der Dateinamenformatierung (beginnend mit Globbing). Shells haben nur sehr wenige Grundelemente für den Umgang mit Dateiinhalten. Shell-Programmierer müssen ein anderes Programm aufrufen, um mit Dateiinhalten umzugehen.
Aufgrund der Ausrichtung der Datei und des Dateinamens ist die Textbearbeitung in der Shell, wie Sie bereits bemerkt haben, sehr langsam, erfordert jedoch auch einen unklaren und verzerrten Programmierstil.
quelle
Es gibt einige komplizierte Antworten, die viele interessante Details für die Geeks unter uns liefern, aber es ist wirklich ganz einfach - die Verarbeitung einer großen Datei in einer Shell-Schleife ist einfach zu langsam.
Ich denke, dass der Fragesteller an einer typischen Art von Shell-Skript interessiert ist, die mit einem Kommandozeilen-Parsing, Umgebungseinstellungen, Überprüfen von Dateien und Verzeichnissen und etwas mehr Initialisierung beginnen kann, bevor er zu seinem Hauptjob übergeht: Durchlaufen eines großen Skripts zeilenorientierte Textdatei.
Für die ersten Teile (
initialization
) spielt es normalerweise keine Rolle, dass Shell-Befehle langsam sind - es werden nur ein paar Dutzend Befehle ausgeführt, möglicherweise mit ein paar kurzen Schleifen. Selbst wenn wir diesen Teil ineffizient schreiben, dauert es normalerweise weniger als eine Sekunde, um die gesamte Initialisierung durchzuführen, und das ist in Ordnung - es passiert nur einmal.Wenn wir uns jedoch mit der Verarbeitung der großen Datei befassen, die Tausende oder Millionen von Zeilen enthalten kann, ist es nicht in Ordnung, dass das Shell-Skript einen signifikanten Bruchteil einer Sekunde (selbst wenn es nur ein paar Dutzend Millisekunden dauert) für jede Zeile benötigt. Das kann sich auf Stunden summieren.
Dann müssen wir andere Tools verwenden, und das Schöne an Unix-Shell-Skripten ist, dass sie es uns sehr einfach machen, das zu tun.
Anstatt eine Schleife zu verwenden, um jede Zeile zu betrachten, müssen wir die gesamte Datei durch eine Pipeline von Befehlen leiten . Dies bedeutet, dass die Shell die Befehle nicht Tausende oder Millionen Mal aufruft, sondern nur einmal. Es ist wahr, dass diese Befehle Schleifen haben, um die Datei zeilenweise zu verarbeiten, aber es handelt sich nicht um Shell-Skripte, und sie sind schnell und effizient gestaltet.
Unix hat viele wundervolle eingebaute Werkzeuge, von einfachen bis hin zu komplexen, mit denen wir unsere Pipelines bauen können. Normalerweise beginne ich mit den einfachen und verwende komplexere, wenn nötig.
Ich würde auch versuchen, mich an Standard-Tools zu halten, die auf den meisten Systemen verfügbar sind, und versuchen, meine Nutzung portabel zu halten, obwohl dies nicht immer möglich ist. Und wenn Ihre Lieblingssprache Python oder Ruby ist, macht es Ihnen vielleicht nichts aus, wenn Sie sicherstellen, dass es auf jeder Plattform installiert ist, auf der Ihre Software ausgeführt werden soll :-)
Einfache Werkzeuge umfassen
head
,tail
,grep
,sort
,cut
,tr
,sed
,join
(beim Einarbeiten 2 - Dateien) undawk
Einzeiler, unter vielen anderen. Es ist erstaunlich, was manche Leute mit Pattern Matching undsed
Befehlen anfangen können.Wenn es komplexer wird und Sie wirklich eine Logik auf jede Zeile anwenden müssen,
awk
ist dies eine gute Option - entweder ein Einzeiler (einige Leute setzen ganze awk-Skripte in 'eine Zeile', obwohl das nicht sehr gut lesbar ist) oder in a kurzes externes Skript.Da
awk
es sich um eine interpretierte Sprache (wie Ihre Shell) handelt, ist es erstaunlich, dass sie zeilenweise so effizient verarbeitet werden kann, aber sie wurde speziell dafür entwickelt und ist wirklich sehr schnell.Und dann gibt es noch
Perl
eine Vielzahl anderer Skriptsprachen, die sehr gut Textdateien verarbeiten können und auch viele nützliche Bibliotheken enthalten.Und schließlich gibt es ein gutes altes C, wenn Sie maximale Geschwindigkeit und hohe Flexibilität benötigen (obwohl die Textverarbeitung etwas mühsam ist). Aber es ist wahrscheinlich eine sehr schlechte Zeit, ein neues C-Programm für jede andere Dateiverarbeitungsaufgabe zu schreiben, auf die Sie stoßen. Ich arbeite viel mit CSV-Dateien, daher habe ich mehrere allgemeine Dienstprogramme in C geschrieben, die ich in vielen verschiedenen Projekten wiederverwenden kann. Tatsächlich erweitert dies die Palette der 'einfachen, schnellen Unix-Tools', die ich von meinen Shell-Skripten aus aufrufen kann, so dass ich die meisten Projekte nur durch das Schreiben von Skripten bewältigen kann.
Einige abschließende Hinweise:
export LANG=C
, oder viele Tools behandeln Ihre normalen ASCII-Dateien als Unicode, wodurch sie viel langsamer werdenexport LC_ALL=C
Sie auch, ob Siesort
unabhängig von der Umgebung eine konsistente Reihenfolge erzielen möchten.sort
Ihre Daten benötigen , wird dies wahrscheinlich mehr Zeit (und Ressourcen: CPU, Speicher, Festplatte) in Anspruch nehmen als alles andere. Versuchen Sie daher, die Anzahl dersort
Befehle und die Größe der zu sortierenden Dateien zu minimierenquelle
Ja aber...
Die richtige Antwort von Stéphane Chazelas basiert auf Shell - Konzept der jeden Text Operation auf bestimmte Binärdateien zu delegieren, wie
grep
,awk
,sed
und andere.Da bash in der Lage ist, eine Menge Dinge selbst zu erledigen, kann es sein , dass das Fallenlassen von Gabeln schneller erfolgt (sogar als das Ausführen eines anderen Interpreters für alle Aufgaben).
Schauen Sie sich zum Beispiel diesen Beitrag an:
https://stackoverflow.com/a/38790442/1765658
und
https://stackoverflow.com/a/7180078/1765658
testen und vergleichen ...
Na sicher
Es wird nicht auf Benutzereingaben und Sicherheit geachtet !
Schreiben Sie keine Webanwendung unter bash !!
Bei vielen Serververwaltungsaufgaben, bei denen bash anstelle von shell verwendet werden kann , kann die Verwendung von builtins bash sehr effizient sein.
Meine Bedeutung:
Das Schreiben von Tools wie bin utils ist nicht die gleiche Arbeit wie die Systemadministration.
Also nicht die gleichen Leute!
Wo Sysadmins Bescheid wissen müssen
shell
, können sie mit seinem bevorzugten (und bekanntesten) Tool Prototypen schreiben .Wenn dieses neue Hilfsprogramm (Prototyp) wirklich nützlich ist, könnten einige andere Leute ein spezielles Werkzeug entwickeln, indem sie eine geeignetere Sprache verwenden.
quelle
bash
. (über 3 mal so schnell mit ksh93 in meinem Test auf meinem System).bash
ist in der Regel die langsamste Schale. Evenzsh
ist in diesem Skript doppelt so schnell. Sie haben auch ein paar Probleme mit nicht zitierten Variablen und der Verwendung vonread
. Sie veranschaulichen hier also tatsächlich viele meiner Punkte.sh
, Awk , Sed ,grep
,ed
,ex
,cut
,sort
,join
... alle mit mehr Zuverlässigkeit als Bash oder Perl.bash
standardmäßig nicht installiert.bash
wird meist nur auf Apple MacOS und GNU - Systemen (ich nehme an das ist , was Sie nennen fand große Distributionen ), obwohl viele Systeme auch haben es als optionales Paket (wiezsh
,tcl
,python
...)