4 Punkte von GN⁺ 2023-08-29 | 1 Kommentare | Auf WhatsApp teilen
  • GNU awk durchläuft automatisch stdin und Dateien und erledigt CLI-Textarbeit, indem es Filterung, Ersetzung und Feldverarbeitung in Einzeilern kombiniert
  • /regexp/ und !/regexp/ sind Kurzsyntax zur Prüfung der aktuellen Eingabezeile; ist die Bedingung wahr, wird standardmäßig $0 ausgegeben
  • sub ersetzt nur den ersten Treffer, gsub alle Treffer; wird das Ziel ausgelassen, wird die Ersetzung auf die aktuelle Eingabezeile $0 angewendet
  • Dank feldweiser Trennung anhand von Leerraum und $N, NF, $NF eignet sich awk besonders gut für feldbasierte Verarbeitung, etwa zum Auswählen bestimmter Spalten oder zum Anwenden von Bedingungen
  • Indem man Fragmente der Form cond{action} aneinanderreiht und BEGIN{} sowie END{} ergänzt, lassen sich über einfache Filter hinaus kleine Programme mit Strings, Zahlen und assoziativen Arrays schreiben

Wie awk ausgeführt wird

  • awk kann wie grep oder sed Text filtern und bietet zusätzlich Programmierfunktionen für komplexere Verarbeitung
  • Die Eingabe kann über stdin oder aus Dateien kommen; standardmäßig wird sie zeilenweise durchlaufen und dabei werden Bedingungen und Aktionen angewendet
  • Auf die gesamte Eingabezeile greift man über die spezielle Variable $0 zu
    • Der präzise Begriff ist Eingabe-Record, in diesem Kapitel wird es aber zeilenbasiert erklärt

Filterung und Standardausgabe

  • Reguläre Ausdrücke werden in der Form /.../ geschrieben; um zu prüfen, ob ein String auf einen regulären Ausdruck passt, verwendet man die Syntax string ~ /regexp/
  • Die Gegenbedingung wird mit string !~ /regexp/ ausgedrückt
  • Lässt man den zu prüfenden String weg, ist $0 das Ziel
  • Gibt es nur eine Bedingung und keine Aktion, wird bei wahrer Bedingung automatisch $0 ausgegeben
    • awk '/regexp/' ist die Kurzform von awk '$0 ~ /regexp/{print $0}'
    • awk '!/regexp/' ist die Kurzform von awk '$0 !~ /regexp/{print $0}'
  • In den Beispielen werden nur Zeilen mit at ausgegeben oder nur Zeilen, die kein e enthalten, um den grundlegenden Filterablauf zu zeigen

Idiomatische Ausgabe mit 1

  • In Bedingungsausdrücken werden von 0 verschiedene Zahlen und nichtleere Strings als wahr ausgewertet
  • awk '1' ist ein idiomatischer Ausdruck für eine Bedingung, die für jede Eingabezeile immer wahr ist
  • Fehlt eine Aktion, führt eine wahre Bedingung zur Ausgabe von $0; daher gibt awk '1' die komplette Eingabe unverändert aus
  • Das Ergebnis ist identisch zu awk '{print $0}' oder einfach cat

Ersetzung: sub und gsub

  • awk stellt für Suche und Ersetzung die Funktionen sub und gsub bereit
  • sub(/:/, "-") ersetzt in jeder Eingabezeile nur das erste : durch -
  • gsub(/:/, "-") ersetzt in jeder Eingabezeile alle : durch -
  • Beide Funktionen erhalten als erstes Argument den passenden regulären Ausdruck und als zweites den Ersetzungsstring
  • Wird kein Eingabestring separat angegeben, ist das Standardziel $0
  • Ist die Ersetzung erfolgreich, wird die Zieleingabe verändert
  • Das 1 nach dem Ersetzungsblock wird als Bedingung außerhalb des Blocks interpretiert und bewirkt, dass das veränderte $0 erneut ausgegeben wird
    • awk '{sub(/:/, "-")} 1' liefert dasselbe Ergebnis wie awk '{sub(/:/, "-"); print $0}'
    • Auch bei bloßem print ist das Standardausgabeziel $0

Wann man grep, sed oder awk wählen sollte

  • Für einfache Zeilenfilterung kann das für diesen Zweck optimierte grep schneller sein als sed oder awk
  • Für Ersetzungsaufgaben kann sed schneller sein als awk
  • Die Funktionen der Werkzeuge entsprechen sich nicht immer 1:1
    • Um grep -o mit sed oder awk nachzubauen, sind mehr Schritte nötig
    • Rekursive Suche bietet nur grep
  • Eine passende Diskussion dazu gibt es unter unix.stackexchange: When to use grep, sed, awk, perl, etc

Feldbasierte Verarbeitung

  • awk wird besonders häufig für feldbasierte Verarbeitung verwendet
  • Standardmäßig teilt es eine Eingabezeile anhand von Leerraum auf; auf jedes Feld greift man mit $N zu
    • $2 ist das zweite Feld
    • $NF ist das letzte Feld
    • NF ist die Gesamtzahl der Felder der aktuellen Eingabezeile
  • Das Beispiel table.txt zeigt kompakt spaltenorientierte Verarbeitung
    • Ausgabe des zweiten Feldes jeder Zeile
    • Ausgabe nur der Zeilen, deren letztes Feld negativ ist
    • Ersetzung von b durch B nur im ersten Feld
  • Die Beispieldateien befinden sich im Verzeichnis example_files

Struktur eines Einzeilers

  • Ein typischer awk-Einzeiler hat die folgende Form
awk 'cond1{action1} cond2{action2} ... condN{actionN}'
  • Fehlt die Bedingung, wird die Aktion immer ausgeführt
  • Fehlt die Aktion, wird bei wahrer Bedingung $0 ausgegeben
  • Innerhalb eines Blocks können mehrere Anweisungen mit Semikolon ; getrennt werden
  • Werden mehrere Blöcke aneinandergereiht, lassen sich in den meisten Einzeilern bedingte Aktionen ohne explizites if ausdrücken
    • awk '$NF<0' table.txt gibt nur Zeilen aus, deren letztes Feld negativ ist
  • BEGIN{} wird vor dem Einlesen der Eingabe ausgeführt
  • END{} wird ausgeführt, nachdem die gesamte Eingabe verarbeitet wurde
  • Details zu Operatoren und Bedingungsausdrücken finden sich im gawk manual: Operators und im gawk manual: Truth Values and Conditions

Strings und Zahlen

  • awk bestimmt den Typ von Werten abhängig vom Kontext, damit sich auf der CLI kurze Lösungen schreiben lassen
  • String-Literale werden in doppelte Anführungszeichen gesetzt
  • Für Zahlen können Ganzzahlen, Fließkommazahlen und wissenschaftliche Notation verwendet werden
  • BEGIN{} kann auch verwendet werden, wenn ein awk-Programm ohne externe Eingabe ausgeführt wird
  • Variablen können Zahlen und Strings speichern
    • Beispiel: a=5; b=2.5; print a+b
    • Werden Strings direkt nebeneinandergestellt, werden sie verkettet
  • Nicht initialisierte Variablen verhalten sich im String-Kontext wie ein leerer String und im Zahlenkontext wie 0
  • Wird ein String in einem numerischen Ausdruck verwendet, wird er zwangsweise in eine Zahl umgewandelt
    • Beginnt der String nicht mit einer gültigen Zahl, wird er als 0 behandelt
    • Führende Leerzeichen werden ignoriert
  • Wird an eine Zahl ein String angehängt, wird die Zahl in einen String umgewandelt
  • Weitere Details stehen im gawk manual: Constant Expressions und im gawk manual: How awk Converts Between Strings and Numbers

Arrays

  • Arrays in awk sind assoziative Arrays und funktionieren als Schlüssel-Wert-Paare
  • Schlüssel können Zahlen oder Strings sein, numerische Schlüssel werden intern jedoch in Strings umgewandelt
  • Auch mehrdimensionale Arrays sind möglich
  • Im Beispiel werden Werte mit String-Schlüsseln wie student["id"] und student["name"] gespeichert und abgerufen
  • Ob ein Schlüssel existiert, wird in der Form "id" in student geprüft
  • Weitere Details stehen im gawk manual: Arrays

Übungen und nächste Schritte

  • Dieses Kapitel führt kurz in die grundlegende Kurzsyntax von awk, in Filterung, Ersetzung, Feldverarbeitung, Typumwandlung und Arrays ein
  • Das nächste Kapitel behandelt reguläre Ausdrücke; die hier vorgestellten Funktionen werden dort in den Beispielen weiter verwendet
  • Man kann die Syntax einüben, indem man etwa Feldnummern ändert oder andere Eingaben wie negative oder Fließkomma-Felder ausprobiert
  • Eine interaktive TUI-App zum Üben lässt sich aus dem Repository AwkExercises installieren; die Nutzung ist in app_guide.md beschrieben
  • Alle Übungsaufgaben sind in Exercises.md gesammelt, die Lösungen stehen in Exercise_solutions.md

1 Kommentare

 
GN⁺ 2023-08-29
Hacker-News-Kommentare
  • Ich mag awk und nutze es ziemlich häufig; einer meiner Hauptanwendungsfälle ist, es wie ein sed mit Zustand zu verwenden.
    Zum Beispiel, um eine Zeile nur dann zu matchen, wenn sie nach einer bestimmten vorherigen Zeile kommt – nützlich für schnelle, provisorische Linter.
    Kürzlich habe ich eine Prüfung gebaut, die in Migrationsdateien nach CREATE INDEX ohne CONCURRENTLY sucht, was bei sehr großen Tabellen problematisch sein kann. SQL-Anweisungen können sich über mehrere Zeilen erstrecken, daher war simples Matching schwierig.
    awk kann über mehrere Zeilen hinweg Zustände verfolgen, etwa „befinde mich in einer create-Anweisung“ oder „erstelle gerade einen Index“, und ein grob zusammengebautes Skript läuft damit seit etwa einem Jahr zuverlässig.

    • Irgendwann sollte ich awk lernen, aber bis dahin habe ich mir die tiefergehenden zustandsbasierten Funktionen von sed angeeignet.
      Wenn man die aktuelle Zeile nur ausgeben will, wenn die vorherige Zeile einem bestimmten Muster entspricht, geht das etwa mit sed -ne 'x' -e '/PREV/ {x; /CURR/ p; x}'.
      Beispiel: echo -e "PREV\nCURR\nCURR\nCURR\nPREV\nRED" | sed -ne 'x' -e '/PREV/ {x; /CURR/ p; x}' gibt nur CURR aus.
      Das nutzt den Hold Buffer von sed; mit -n wird die Standardausgabe unterdrückt, und mit p werden nur die benötigten Zeilen ausgegeben.
      x tauscht die aktuelle Zeile mit dem Hold Buffer, und im Block /PREV/ { ... } wird erneut getauscht; ausgegeben wird nur, wenn die aktuelle Zeile CURR enthält.
      Das letzte x dient dazu, für überlappende Matches wieder zurückzutauschen.
      Natürlich ist ein awk-Skript sehr wahrscheinlich viel einfacher und direkter, aber mit sed geht es eben auch.
      Die Zeit, das zu lernen, wäre vielleicht besser in awk investiert gewesen, aber das Tutorial https://www.grymoire.com/Unix/Sed.html ist so gut, dass ich fast alles, was ich über sed weiß, dort gelernt habe.
    • Es wäre schön, wenn du ein Beispiel teilen könntest, wie du mit awk den Zustand von SQL verfolgst.
  • Wer diesen Thread liest, dürfte sich auch für „Ask HN: Share a shell script you like“ von vor zwei Wochen interessieren.
    Mit 78 Kommentaren war es nicht so lebhaft wie erhofft, ist aber als Referenz brauchbar: https://news.ycombinator.com/item?id=37112991

  • Neben Lisp und Ähnlichem habe ich mir auch ein kleines Interesse an Awk bewahrt und 2022 cppawk gebaut: https://www.kylheku.com/cgit/cppawk/about/
    cppawk erweitert Awk um Präprozessor-Funktionen.
    Es gibt ein Loop-Makro mit Unterstützung für mehrere Klauseln; durch Kombinieren von Klauseln lassen sich parallele Iteration oder Iteration über kartesische Produkte erzeugen, und eigene Erweiterungen sind ebenfalls möglich.
    Mit fünf einfachen Makros kann man eine neue Klausel definieren.
    Wenn man Awk verwendet, kann das nützlich sein; es ist in mehreren Manpages dokumentiert und verfügt über Unit-Tests, die unter gawk und mawk laufen.

  • Vielleicht spricht da nur mein Gefühl als alter Systemadministrator, aber ich sehe nicht recht, welchen Vorteil awk gegenüber dem einfachen Schreiben derselben Dinge in Perl hat.
    Ich habe viele furchtbare Shell-Skripte von Junior-Systemadministratoren gesehen und dachte jedes Mal: „Der Textverarbeitungsteil wäre mit Perl viel sauberer gewesen.“

    • Wenn Perl in der jeweiligen Umgebung immer verfügbar ist, ist das ziemlich plausibel.
      Für mich ist awk eine der wenigen Sprachen, die ich monatelang liegen lassen kann und nach 10 Minuten wieder im Gefühl habe.
      Es hat etwas Intuitives und passt natürlich gut zu den üblichen Kommandozeilen-Tools.
    • Wenn es um Skripte geht, würde ich bei Awk vs. Perl eher Perl oder Python bevorzugen.
      In diesem Beitrag geht es allerdings um kurze Einzeiler für provisorische Zwecke, und in solchen Fällen sind sed/awk besser als Perl.
      Wenn man Perl bereits kennt, kann man natürlich einfach weiter Perl verwenden, ohne noch ein weiteres Tool zu lernen.
    • Die Stärke von Awk ist die gratis mitgelieferte Leseschleife, Feldaufteilung und das Pattern-/Bedingungs-Matching-Modell.
      Als Sprache selbst ist es „ganz okay“, aber ausreichend brauchbar.
      Mit Perl kann man natürlich alles ebenfalls machen, muss aber den Boilerplate-Code selbst schreiben, den awk gratis übernimmt.
      Die Vorteile der Perl-Sprache sind für mich nicht groß genug, um awk aufzugeben, und da ich awk hauptsächlich für Datenverarbeitung und einmalige Dateibearbeitung statt für „Scripting“ nutze, vermisse ich die Tiefe von Perl nicht.
      Wenn ich tiefere Funktionen brauche, gehe ich woandershin.
    • Einen besonderen technischen Vorteil gibt es nicht; Perl ist in seinen Fähigkeiten bewusst ziemlich nah an einer Obermenge von awk.
      Weil die heutige Generation Perl nicht gelernt hat, scheint sie die Idee der Stream-Verarbeitung mit awk neu zu entdecken.
      awk war nach Maßstäben der späten 1970er eine hervorragende Idee und eine echte Innovation, die es wert war, nachgeahmt zu werden.
      Später hat Perl das nachgeahmt und übertroffen, ist aber in Vergessenheit geraten; deshalb wirkt es ein wenig peinlich, dass awk nun wiederentdeckt wird.
    • Das ist nicht sarkastisch gemeint: Mich interessiert, warum Perl für Skripte besser sein soll als Python.
  • Ein weniger bekannter Punkt bei gawk ist, dass es üblicherweise mit nützlichen Erweiterungen ausgeliefert wird.
    Man bekommt Zugriff auf Funktionen wie readdir(), ord(), chr(), gettimeofday() und sleep().
    https://www.gnu.org/software/gawk/manual/html_node/Extension-Samples.html

  • awk-Einzeiler sind wirklich mächtig
    Die schwierige Frage ist, ob es sich lohnt, in komplexere awk-Programmierung zu investieren
    Wenn eine Verarbeitungsaufgabe komplizierte Logik erfordert, kann awk sie zwar liefern, aber in der kurzen, schwer verständlichen Art aus den frühen Tagen der Computertechnik
    Entscheidet man sich dagegen für eine moderne Alternative, bringt auch das Aufwand mit sich; insbesondere kann sie wie pandas nicht gerade intuitiv sein und zusätzlich Performance-Probleme verursachen

    • Mein großes Problem sind Bibliotheken
      Selbst eine gemeinsame Datei mit persönlichen Hilfsfunktionen fühlt sich nicht wirklich gut unterstützt an, und es scheint auch keine naheliegende Methode zu geben, Third-Party-Bibliotheken einzubinden
      Wenn ich Helper-Funktionen brauche und anfange, den Code auf mehrere Zeilen aufzuteilen, wechsle ich normalerweise zu Python
      Dann wird das Programm zwei- bis dreimal so groß, und ich seufze
      Ruby ist ein hervorragender awk-Ersatz, aber wenn die Kolleginnen und Kollegen Ruby nicht kennen, kann man kaum erwarten, dass sie es warten
    • Vor ChatGPT mochte ich schwer verständliche awk/sed-Einzeiler nicht
      Heute kann man solche schwer lesbaren Befehle in eine KI einfügen, und sie erklärt sie Schritt für Schritt ziemlich gut, damit ist es okay geworden
      Wegen der Testbarkeit mit Unit-Tests bevorzuge ich trotzdem ein paar Zeilen Python, aber bei schnellen Datenaufbereitungen braucht man manchmal von vornherein keine Unit-Tests
  • Eine neue Version des E-Books „CLI text processing with GNU awk“ wurde veröffentlicht
    Man kann GNU awk anhand von Hunderten Beispielen und Übungen Schritt für Schritt von Grundlagen bis Fortgeschrittenem lernen; ausführlich behandelt werden Feldverarbeitung, Filtern, Verarbeitung mehrerer Dateien, Lösungen, die von mehreren Records abhängen, Record- und Feldvergleiche zwischen Dateien, Duplikatsuche unter Beibehaltung der Eingabereihenfolge, reguläre Ausdrücke und mehr
    PDF/EPUB: https://learnbyexample.gumroad.com/l/gnu_awk (kostenlos bis 31. August 2023)
    Web-Version: https://learnbyexample.github.io/learn_gnuawk/
    Markdown-Quellen und Beispieldateien: https://github.com/learnbyexample/learn_gnuawk
    Interaktive TUI-App zum Üben: https://github.com/learnbyexample/TUI-apps/blob/main/AwkExercises
    Als Bundle gibt es Magical one-liners, eine Sammlung von grep-, sed-, awk-, perl- und ruby-Einzeilern, für 5 $: https://learnbyexample.gumroad.com/l/oneliners/new_awk_release, sowie ein Bundle aller 13 E-Books für 12 $: https://learnbyexample.gumroad.com/l/all-books/new_awk_release
    Feedback ist sehr hilfreich, ganz gleich ob zu Tippfehlern, Codefehlern, gelungenen oder weniger gelungenen Teilen
    Frühere Diskussionen gibt es unter https://news.ycombinator.com/item?id=15549318 und https://news.ycombinator.com/item?id=22758217

    • Mich würde interessieren, wie viele Leute für Material wie Magical one-liners tatsächlich bezahlen und ob schon einmal ein „erst anschauen, dann Bezahlen wählen“-Modell ausprobiert wurde
      Es könnte durchaus nützlich genug sein, um 5 oder 15 $ zu zahlen, aber es könnte auch nichts anderes sein als das, was ich ohnehin schon in meiner persönlichen Einzeiler-Datei gespeichert habe, daher zögere ich, nur zum Nachsehen zu bezahlen
    • Man sieht schön, dass viel Arbeit hineingeflossen ist, und besonders die TUI-Anwendung gefällt mir
      Ich frage mich, ob die Web-Version auch nach dem 31. August weiterhin kostenlos bleibt
  • Ich habe früher einmal Code-Golf für Herzen mit AWK zeichnen gemacht: https://gist.github.com/auselen/906a53b47a7d616b080dbef85eb8f776

  • In 99,9 % der awk-Nutzung geht es darum, Zeilen aufzuteilen und dabei aufeinanderfolgende Leerzeichen zu verwerfen
    Beispiel: echo "key: value" | awk '{print $1}'
    Eine einfachere Alternative wäre schön

    • https://github.com/c-blake/bu/blob/main/doc/cols.md könnte man in Betracht ziehen
      Es ist in Nim geschrieben, aber das muss keine große Hürde sein, und auch die anderen Tools in bu/ könnten interessant sein
    • Mit cut geht es auch: echo "key: value" | cut -wf 2
      Ob das tatsächlich einfacher ist, ist allerdings diskutabel
      Beim Nachsehen stellte sich heraus, dass GNU cut kein -w hat, das ist also BSD-spezifisch
      Am Ende ist es wohl besser, awk zu verwenden
    • https://github.com/sstadick/hck und https://github.com/theryangeary/choose sind ebenfalls einen Blick wert
      Beide sind cut/awk-Alternativen und unterstützen auch Regex-basierte Trennung, aber führende/abschließende Leerzeichen entfernen sie, soweit ich mich erinnere, nicht
      Mein eigenes https://github.com/learnbyexample/regexp-cut ist ein cut-ähnliches Tool, das awk verwendet und Regex-basierte Trennung, negative Indizes usw. bietet; dank des Standardverhaltens von awk behandelt es auch führende/abschließende Leerzeichen
    • Vermutlich war echo "key: value" | awk '{print $2}' gemeint
  • Vor einiger Zeit habe ich ein aus Bash portiertes diff2html-Skript in awk geschrieben, und aus naheliegenden Gründen war es deutlich schneller.
    Im Vergleich zum Bash-Skript war die awk-Version viel leichter zu lesen, und ich konnte die Sprache an einem Abend lernen, debuggen, die Bugs verstehen und beheben.
    Ich weiß nicht, ob das idiomatisches awk ist, aber es fühlte sich wirklich wie eine ziemlich gute Sprache an.
    https://github.com/berry-thawson/diff2html/blob/master/diff2html.sh