• Willkommen im Linux Club - dem deutschsprachigen Supportforum für GNU/Linux. Registriere dich kostenlos, um alle Inhalte zu sehen und Fragen zu stellen.

awk

abgdf

Guru
Hier im Forum tauchen häufiger kleine Programmierfragen mit Daten in csv-Textdateien auf. Da ich primär Python und Perl einsetze, habe ich meist Lösungsskripte in diesen Sprachen vorgeschlagen.
Meist machen andere dann weitere Vorschläge, vielleicht was mit grep und sed, oder eben auch mit awk (siehe auch hier).
awk scheint für diese Art Aufgabe besonders geeignet zu sein. Dazu ist der Interpreter relativ klein.
Also hab' ich mir mal awk ein bißchen angesehen. Mir fällt auf:

1. awk erwartet stets eine csv-Textdatei zur Verarbeitung. Auf die einzelnen Zeilen in dieser Datei bezieht sich dann der awk-Code.
Dieser kann dadurch relativ kurz sein, eben weil er sich auf dieses Szenario bezieht. Das ist ähnlich wie das, was bei Perl-Einzeilern durch die Optionen "perl -pe ..." und "perl -pi" erreicht wird.
Ich mag das nicht so gern (bei awk nicht, aber auch nicht bei Perl): Es wird eine Situation impliziert, die nicht aus dem Code selbst ersichtlich ist. Das widerspricht dem Zen of Python ("import this" an der Python-Eingabeaufforderung):
Explicit is better than implicit.
Gut, awk ist viel älter als Python. Trotzdem ist das nicht nur zunächst schwer zu lernen, sondern auch später (jedenfalls für mich) relativ schwer, immer in diesem Szenario zu denken.
Ich ziehe Code vor, der bei Null beginnt und sich dann aus sich selbst heraus schlüssig entwickelt. Auch wenn er dadurch ein bißchen länger wird.

2. Wenn die zu verarbeitende Datei z.B. so aussieht (Beispiel von hier):
Code:
Susanne 15.0
Thomas  23.0
Richard  0.0
Birgit  -2.0
Helmut  31.0
kann man recht leicht die Summe der Werte berechnen:
Code:
#!/usr/bin/awk -f
{ sum += $2 }
END { print sum }
Das erinnert an eine Tabellenkalkulation. Offenbar ist awk so eine Art Tabellenkalkulation für die Konsole. ;)
 
A

Anonymous

Gast
abgdf schrieb:
Das erinnert an eine Tabellenkalkulation. Offenbar ist awk so eine Art Tabellenkalkulation für die Konsole. ;)
das beschreibt schon mal ganz gut mindestens 70% von dem was es ausgezeichnet kann, aber nicht für die Konsole das ist zu ungenau, sondern als Batchverarbeitung.

vom Prinzip ist awk nicht schwer zu lernen.
Es besteht im Wesentlichen aus einem oder mehreren Zeilen die aufgebaut sind

Code:
Bedingung { Befehlskette }
Es gibt 2 spezielle Bedingungen BEGIN und END , sind diese definiert, dann wird diese Befehlkette entweder gleich nach dem Start oder bevor das Programm beendet wird ausgeführt.

Alles anderen Bedingung + Befehlsketten laufen über jede einzelne Zeile der Eingabedateien.
Es ist also eine interne nicht sichtbare Schleife im Programmablauf vorhanden, so wie in sed oder grep oder anderen Programmen auch.

Der gesamte Programmablauf sieht in normalen Programmiersprachen in etwa so aus, aber geschrieben wird nur das Rote, und zwar nur dann wenn man es braucht, es muss also nicht wirklich zwingend irgendwas davon vorhanden sein.
BEGIN { Befehlskette }

for LINE in EINGABEDATEINEN {

if (Bedingung in LINE) { Befehlskette }
if (Bedingung in LINE) { Befehlskette }
if (Bedingung in LINE) { Befehlskette }
....

}
END { Befehlskette }

Die in der unsichtbaren Schleife befindlichen Zeilen werden in der vorhandenen Reihenfolge auf alle Zeilen der Dateien ausgeführt, werden mehrere Dateien angegeben dann auf alle Zeilen in jeder Datei. Mit Hilfe der Bedingungen regelt man jetzt was wann gemacht wird. Dazu hat man verschiedene Möglichkeiten entweder man wertet in der Bedingung Variablen aus, oder man wertet die jeweilige Zeile oder Teile davon aus, zB durch Reguläre Ausdrücke die dann auf Teile oder die Ganze Zeile jeweils angewendet werden. Nur wenn die Bedingung für diese Zeile erfüllt ist wird für diese Zeile die Befehlskette durchlaufen. Ist keine Bedingung für eine Befehlskette vorhanden, wird die Befehlskette bei jeder Zeile gemacht. Ist keine Befehlskette für eine Bedingung angegeben, dann wird für jede Zeile für die die Bedingung zutrifft, die ganze Zeile an die Standardausgabe geschickt.

Die "unsichtbare Schleife hat wie in jeder anderen Programmiersprache auch Abbruch und Steuerbefehle, wie exit (Gehe sofort zu END oder Progammende) ; next (führe alle folgenden Befehlzeilen für diese Zeile nicht mehr aus, lade nächste Zeile und beginne bei der ersten Bedingung); nextfile (beginne sofort mit der ersten Zeile der nächsten Datei in der ersten Befehlszeile.)

Was der am meisten benutzte Vorteil von awk ist, es wird automatisch jeweils für die aktuelle Zeile entsprechend der definierten Feld-Trenner-Zeichen jede Zeile automatisch in Variablen auf die einzelnen Felder der Zeile gelegt, und es gibt auch automatische Variablen wie zB die Anzahl der Felder dieser Zeile, der Dateiname der gerade bearbeitet wird, die Zeilennummer die gerade in Bearbeitung ist, usw.

alles andere ist nicht unähnlich vielen Programmiersprachen und ähnelt sehr der C Syntax. zB. zwischen den Befehlen ";" oder NEWLINE, Befehlsböcke in { } einschließen, usw. Es gibt vordefiniert, automatische und frei wählbare Variablen und Konstanten (allerdings kennt awk nur 2 Typen, entweder Zahl (intern ist das alles Gleitkomma) oder String und dazu eine "integierte automatische Typenanpassung" ), es gibt Arrays, eingebaute Mathematische- und Stringfunktionen, Funktionen für Ein- und Ausgabe (schreiben lesen, print, ...) Funktionen können auch selbst definiert werden, es gibt Schleifen, Verzweigungen und und und ....

Für alle die hier mitlesen wollen und die das auch Interessiert.
eine ganz kleine Vorstellung und Einführung für den Anfang.
eine sehr gute Einführung mit allem was man jemals gebrauchen wird wenn man wirklich hin und wieder mal awk programmieren will.

robi
 

framp

Moderator
Teammitglied
:thumbs: Sehr guter Beitrag Robi, der zusammen mit dem Wikibeitrag einen sehr schnell mal in die Geheimnisse von awk einfuehrt. Von dessen Existenz wusste ich bislang auch noch nicht.
 

framp

Moderator
Teammitglied
Habe diesen Thread mal genutzt mal wieder in "The awk Programming Language" von Aho, Kernighan und Weinberger reinzusehen. Anbei ein paar Zeilen daraus von mir ins Deutsche übersetzt:
The awk Programming Language schrieb:
... Entstehungsgeschichte von awk:
Awk war ursprünglich von den Autoren als Teil eines Experiments designed und implementiert 1977 um zu sehen wie man die Unix Tools sed und grep verallgemeinern kann um mit Zahlen sowie Text umzugehen, da wir Interesse an regulären Ausdruecken und programmierbare Editoren hatten. Eigentlich war es dafür gedacht nur sehr kurze Programme zu schreiben aber seine Möglichkeiten begeisterten Benutzer sehr bald die dann sehr grosse Programme entwickelten....
 

utopos

Member
robi, Dein Beispiel aus dem anderen Faden hat mich nochmals in Nachdenken gestürzt.
Ich äußere jetzt ganz unsortiert, was mir u.a. dazu eingefallen ist.

Hier haben wir drei Varianten, dieselbe Aufgabe zu lösen:

Code:
head -2385 faust.txt | tail -1

Code:
awk 'NR==2385{print $1}' faust.txt

Code:
perl -ne 'print $_ if $# = 2385' faust.txt


Spontan denke ich, dass alle drei etwas ineffizient sind (dafür schnell zu schreiben). Die letzten beiden aus dem gleichen Grunde, da alle Zeilen durchgegangen werden, ob es nötig ist oder nicht. Besser wäre also etwas wie

Code:
perl -ne 'if ($# = 2385) {print $_; last}' faust.txt

was nur unwesentlich länger ist. Geht das auch in den anderen beiden Varianten?
 
A

Anonymous

Gast
utopos schrieb:
Hier haben wir drei Varianten, dieselbe Aufgabe zu lösen:
Code:
head -2385 faust.txt | tail -1
Code:
awk 'NR==2385{print $1}' faust.txt
Code:
perl -ne 'print $_ if $# = 2385' faust.txt

Spontan denke ich, dass alle drei etwas ineffizient sind (dafür schnell zu schreiben). Die letzten beiden aus dem gleichen Grunde, da alle Zeilen durchgegangen werden, ob es nötig ist oder nicht. Besser wäre also etwas wie
Code:
perl -ne 'if ($# = 2385) {print $_; last}' faust.txt
was nur unwesentlich länger ist. Geht das auch in den anderen beiden Varianten?

Code:
tail | head
dort hätte ich überhaupt keine Chance, da tail das Ende der Datei gelesen haben muss.
Code:
head | tail
macht das automatisch, aber nicht sonderlich effizient bei kleineren Dateien.
Bei awk kann ich jederzeit wenn alles erledigt ist die Hauptscheife oder das lesen der aktuellen Datei abbrechen, brauche also nicht wirklich bis ans Ende der Datei zu lesen wenn es dort nichts mehr zu finden geben kann.
Code:
awk 'NR==2385{print $1; exit}' faust.txt
Das von mir benutzte Beispiel war allerdings eine einfache Optimierung von mehreren Bashzeilen mitten aus einem größerem Script und dort war ein Abbruch an dieser Stelle nicht gefragt.

robi
 
OP
A

abgdf

Guru
utopos schrieb:
robi, Dein Beispiel aus dem anderen Faden hat mich nochmals in Nachdenken gestürzt.
Ich äußere jetzt ganz unsortiert, was mir u.a. dazu eingefallen ist.

Hier haben wir drei Varianten, dieselbe Aufgabe zu lösen:

Code:
head -2385 faust.txt | tail -1

Code:
awk 'NR==2385{print $1}' faust.txt

Code:
perl -ne 'print $_ if $# = 2385' faust.txt


Spontan denke ich, dass alle drei etwas ineffizient sind (dafür schnell zu schreiben). Die letzten beiden aus dem gleichen Grunde, da alle Zeilen durchgegangen werden, ob es nötig ist oder nicht. Besser wäre also etwas wie

Code:
perl -ne 'if ($# = 2385) {print $_; last}' faust.txt

was nur unwesentlich länger ist. Geht das auch in den anderen beiden Varianten?
Ich sag's nur ungern, aber bitte teste doch ein bißchen vor dem Posten:
Code:
awk 'NR==2385{print $1}' faust.txt
Wenn Du dasselbe wie in der tail-Zeile darüber erreichen willst, mußt Du "$0" ausgeben.
Code:
perl -ne 'print $_ if $# = 2385' faust.txt
Ich denke, hier fehlt ein Gleichheitszeichen. Außerdem redest Du von "$.", nicht von "$#".
Code:
perl -ne 'if ($# = 2385) {print $_; last}' faust.txt
S.o. Außerdem dazu "perldoc perlvar":
You can adjust the counter by assigning to $., but this will not actually move the seek pointer.
"seek()" (perldoc -f seek) ist wahrscheinlich die einzige Möglichkeit, sich schneller in einer Datei zu bewegen als die Zeilen einzeln von oben einzulesen. Leider ist "seek()" ziemlich unbequem.
 

utopos

Member
robi schrieb:
Code:
awk 'NR==2385{print $1; exit}' faust.txt

Aha!

abgdf schrieb:
Ich sag's nur ungern, aber bitte teste doch ein bißchen vor dem Posten:

Stimmt, so was kann man machen ...

abgdf schrieb:
Wenn Du dasselbe wie in der tail-Zeile darüber erreichen willst, mußt Du "$0" ausgeben.

OK.

abgdf schrieb:
"seek()" (perldoc -f seek) ist wahrscheinlich die einzige Möglichkeit, sich schneller in einer Datei zu bewegen als die Zeilen einzeln von oben einzulesen. Leider ist "seek()" ziemlich unbequem.

Die Bewegung in Bytes ist schon etwas abschreckend. Habe ein paar Beispiele mit

Code:
seek(@ARGV[1], 0, $irgendnezahl);

ausprobiert, vestehe aber nicht, was da eigentlich passiert bzw. mit welchen Funktionen/Variablen ich dann auf den Inhalt der Datei an der erreichten Position zugreifen kann.
 
Oben