ubuntuusers.de

Script mit zwei for-Schleifen - Verständgnisproblem

Status: Gelöst | Ubuntu-Version: Kubuntu 11.10 (Oneiric Ocelot)
Antworten |

newbie

Anmeldungsdatum:
23. März 2006

Beiträge: 965

Hi,

das ist ein Script aus einem Buch:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#!/usr/bin/awk -f
#
# Programmname: countwords.awk
{
# Durchläuft alle Felder einer Zeile
for(f=1; f <= NF; ++f)
field[$f]++
}
END {
for(word in field)
print word, field[word]
}

Diese Stelle der ersten for-Schleife verstehe ich leider überhaupt nicht:

1
field[$f]++

Im Buch steht folgendes zu der ersten for-Schleife: "Jedes Wort wird hier als Wortindex für ein Array benutzt und inkrementiert (um eins erhöht). Existiert ein solcher Index noch nicht, wird dieser neu erzeugt und um den Wert eins erhöht. Existiert bereits ein entsprechender »Wortindex«, so wird nur die Speicherstelle zum zugehörigen Wort um eins erhöht".

Und das habe ich leider überhaupt nicht verstanden. Wo wird hier überprüft ob etwas existiert? Eine if-Bedingung gibt es hier nicht. Und warum wird hier ein Präinkrement verwendet und nicht ein Postinkrement? Bei for-Schleifen benutze ich immer zusätzliche Variablen, in die ich dann Werte zwischenspeichere und auch if-Bedingungen. Ich vermute, dass man diese Zeile auch so umschreiben kann?

In der zweiten Schleife wird der Indexname(das Wort selbst) und dessen Häufigkeit ausgegeben. Wie bzw. wo werden Wörter der Variable "word" zugewiesen? Ausgabe sieht dann so aus:

1
2
3
4
5
6
./countwords.awk mrolympia.dat
Dorian 1
2000 1
2001 1
USA 5
...

Wäre super, wenn mir jemand helfen könnte dieses Beispiel zu verstehen. Danke!

track

Avatar von track

Anmeldungsdatum:
26. Juni 2008

Beiträge: 7174

Wohnort: Wolfen (S-A)

Bau in Dein Skript doch mal ein paar Debug-Ausgaben ein. Dann wird es schnell deutlich:

#!/usr/bin/awk -f
#
# Programmname: countwords.awk
  {
	# Durchläuft alle Felder einer Zeile
	for(f=1; f <= NF; ++f)  {
		print "f: " f ;
		print "field vorher: " field[f] ;

		field[f]++;
		print "field nachher: " field[f] ;
		}
	}

END {
	for(word in field)
		print word, field[word]
	}

Bitte beachte, dass unter awk die Variablen ohne "$" davor angesprochen werden (im Gegensatz zur Bash).

Und in der for-Schleife ist es egal, ob prä- oder postincrementiert wird, weil die 3. Position in der Klammer sowieso vor jeder Runde komplett ausgeführt wird.

LG,

track

Edit: 3 Tippfehler im Skript korrigiert.

Vain

Avatar von Vain

Anmeldungsdatum:
12. April 2008

Beiträge: 2510

Servus,

newbie schrieb:

Und das habe ich leider überhaupt nicht verstanden. Wo wird hier überprüft ob etwas existiert? Eine if-Bedingung gibt es hier nicht.

das passiert implizit von awk. Wenn du auf irgendetwas zugreifst, das es noch nicht gibt, wird es als leerer String oder bei Zahlen als 0 gewertet.

Und warum wird hier ein Präinkrement verwendet und nicht ein Postinkrement?

Das ist doch ein Postinkrement?

Bei for-Schleifen benutze ich immer zusätzliche Variablen, in die ich dann Werte zwischenspeichere und auch if-Bedingungen. Ich vermute, dass man diese Zeile auch so umschreiben kann?

Sicher. Der Witz ist aber, dass das unnötig ist, denn awk nimmt dir diese Arbeit ab.

In der zweiten Schleife wird der Indexname(das Wort selbst) und dessen Häufigkeit ausgegeben. Wie bzw. wo werden Wörter der Variable "word" zugewiesen?

Im Schleifenkopf. „for (foo in bar)“ weißt nach und nach der Variablen „foo“ einen Index-Wert aus dem Array „bar“ zu.


@track: Das Dollar-Zeichen ist da wohl gewollt. Die Wörter im Text sollen gezählt werden.

track

Avatar von track

Anmeldungsdatum:
26. Juni 2008

Beiträge: 7174

Wohnort: Wolfen (S-A)

@track: Das Dollar-Zeichen ist da wohl gewollt. Die Wörter im Text sollen gezählt werden.

Ach so .... dann wird bei f= 1, 2, 3 ... daraus $1, $2, $3, ... , also die Positions-Variablen ...?
Ok, das kann man auch machen. - die zählen dann damit, dass sie die jedes Mal um 1 erhöhen ...?

Trotzdem: mit Debug-Ausgaben kann man sich die Übersicht verschaffen. Dann eben:

#!/usr/bin/awk -f
#
# Programmname: countwords.awk
  {
	# Durchläuft alle Felder einer Zeile
	for(f=1; f <= NF; ++f)  {
		print "f: " f ;
		print "$f = " "$" f ": " $f ;
		print "field vorher: " field[$f] ;

		field[$f]++;
		print "field nachher: " field[$f] ;
		}
	}

END {
	for(word in field)
		print word, field[word]
	}

track

newbie

(Themenstarter)

Anmeldungsdatum:
23. März 2006

Beiträge: 965

Hi und danke für die Antworten!

@track,

kannst du bitte in deinem Script alle Fehler korrigieren? Ich habe versucht, aber finde nicht alle. Kann das leider nicht ausführen.

@Vain Ich bin leider Anfänger was Programmieren angeht. Die einzige Sprache, in der ich kleine Vorkenntnisse habe ist Java. Und AWK-Syntax ist leider ganz anders als Java. AWK → Hieroglyphen ☺

AWK hat ja folgenden Aufbau:

Muster {Aktion}

Das was in der Datei steht gehört alles zum Aktion-Teil, richtig?

So weit ich verstanden habe, muss man Variablen nicht initialisieren. Sobald ich eine Variable im Aktionsteil verwende, wird sie einfach erstellt und ggf. mit einer 0 oder " " belegt. In der Variable NF steht die Anzahl(als Integerzahl) von allen Wörtern bzw. Feldern einer Zeile. AWK bearbeitet eine Zeile nach der anderen. Wird jetzt JEDE for-Schleife(auch im END-Teil) jedes Mal neu gestartet, sobald AWK eine neue Zeile einliest? Also kann man AWK als eine große do-while Schleife betrachten?

Vielen Dank!

PS: @track: Danke! Jetzt läuft das Programm.

PPS: Mit Präinkrement meinte ich diesen Teil: ++f

Vain

Avatar von Vain

Anmeldungsdatum:
12. April 2008

Beiträge: 2510

newbie schrieb:

AWK hat ja folgenden Aufbau:

Muster {Aktion}

Das was in der Datei steht gehört alles zum Aktion-Teil, richtig?

[...]

Wird jetzt JEDE for-Schleife(auch im END-Teil) jedes Mal neu gestartet, sobald AWK eine neue Zeile einliest?

Es gibt zwei Blöcke, was man durch striktere Einrückung auch leicht sehen kann:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#!/usr/bin/awk -f

# Erster Block, trifft auf jede Zeile zu:
{
    # Durchläuft alle Felder einer Zeile
    for(f=1; f <= NF; ++f)
        field[$f]++
}

# Zweiter Block, wird einmalig nach der letzten Zeile ausgeführt:
END {
    for(word in field)
        print word, field[word]
}

Also kann man AWK als eine große do-while Schleife betrachten?

Im Prinzip schon.

Du kannst ja mehrere Blöcke mit verschiedenen Bedingungen haben. Es wird dann eine Zeile gelesen und die ganzen Blöcke (außer BEGIN und END) durchgegangen, ob ein Block passt. Jeder passende Block wird ausgeführt, die Reihenfolge entspricht der in deiner Datei.

PPS: Mit Präinkrement meinte ich diesen Teil: ++f

Oh, richtig, das habe ich gar nicht wahrgenommen gestern. Im Schleifenkopf ist es einfach egal, welche Variante du nimmst.

Viele Leute argumentieren, dass ein Präinkrement schneller sei: Ein Postinkrement muss eine temporäre Kopie des alten Wertes anlegen, den eigentlichen Wert erhöhen und dann die Kopie zurückgeben. Ein Präinkrement kann direkt den Wert erhöhen und dann den zurückgeben. Man würde sich also die Kopie sparen.

Hier hat sich das mal jemand näher bei C++ und dem int-Typ angeschaut und kam zu dem Ergebnis, dass es im Hinblick auf den generierten Code keine Rolle spielt. Ich würde auch bei C und C++ davon ausgehen, dass der Compiler dir das ohnehin passend optimiert (selbst, falls es bei int relevant wäre), denn diese Compiler sind heutzutage ziemlich schlau. 😉

Wie der Unterschied bei awk aussieht, kann ich dir nicht sagen, da weiß vielleicht track was. Selbst wenn es einen gibt, in diesem kleinen Skript verlörest du vielleicht ein paar Nanosekunden und/oder hast ganz andere Flaschenhälse.

track

Avatar von track

Anmeldungsdatum:
26. Juni 2008

Beiträge: 7174

Wohnort: Wolfen (S-A)

Vain schrieb:

Wie der Unterschied bei awk aussieht, kann ich dir nicht sagen, da weiß vielleicht track was.

Nee, weiß er auch nicht. 🤓
Ich habe mich nämlich immer nur aus Benutzersicht mit awk beschäftigt, die Interna kenne ich nicht.

Aber gut, man kann es ja ganz einfach benchmarken: (auf meinem 1 GHz Athlon)

track@lucid:~$ time gawk 'BEGIN{ for( i=1; i<100000000; ++i ) j= i }'

real	1m3.820s
user	1m2.568s
sys	0m0.504s
track@lucid:~$ time gawk 'BEGIN{ for( i=1; i<100000000; i++ ) j= i }'

real	1m7.275s
user	1m4.528s
sys	0m0.544s

track@lucid:~$ time mawk 'BEGIN{ for( i=1; i<100000000; ++i ) j= i }'

real	0m44.477s
user	0m37.962s
sys	0m0.352s
track@lucid:~$ time mawk 'BEGIN{ for( i=1; i<100000000; i++ ) j= i }'

real	0m43.366s
user	0m37.822s
sys	0m0.352s 

Da ist tatsächlich ein Unterschied von einigen Prozent, wenn er sonst nichts macht. Eigentlich hätte ich 0 Unterschied erwartet (denn es sollte ja egal sein, ob man eine Zahl erhöht, bevor man nichts mit ihr macht, oder danach), aber irgendwie wird das wohl der internen Umsetzung geschuldet sein.

Ok ? 😉

track

newbie

(Themenstarter)

Anmeldungsdatum:
23. März 2006

Beiträge: 965

Vielen Dank für eure ausführliche Antworten! Fragen geklärt ☺.

Antworten |