Hallo, Am Mit, 15 Sep 2010, Andre Tann schrieb:
sorry, ich kapier gar nix, und hake nochmal nach:
David Haller, Mittwoch 15 September 2010:
1 find . -type f -exec gawk ' 2 function endfile() { 3 if( words[1] && words[2] && words[3] ) { 4 print lastfile; 5 } 6 } 7 lastfile != FILENAME { 8 endfile(); 9 words[1] = 0; words[2] = 0; words[3] = 0; 10 lastfile = FILENAME; 11 } 12 /wort1/ { words[1]=1; } 13 /wort2/ { words[2]=1; } 14 /wort3/ { words[3]=1; } 15 END { endfile(); } 16 ' '{}' +
In Zeile 10 wird lastfile = FILENAME gesetzt, und erst dann ist Zeile 7 erfüllt. Wie kann es dazu kommen, daß Zeile 7 erfüllt ist, ohne daß Zeile 10 vorher durchlaufen wurde?
Das dient dazu, zu erkennen, ab wann eine neue Datei bearbeitet wird, und genau das war mein Denkfehler, der in der Version unten drin ist (siehe dort). Also, du hast z.B. im mailspool die Dateien 1-60, davon übergibt find sagen wir zuerst 40 und dann 20 an gawk. Bei Datei 1 ist lastfile leer und FILENAME == "1". Zeile 7 greift, aber words[] ist noch leer. endfile wird nicht aufgerufen und words[] zurückgesetzt. lastfile wird auf FILENAME ("1") gesetzt. Zeilen 12 - 14 greifen evtl. und words[] wird befüllt (oder auch nicht). Und bei allen Zeilen der Datei ist nun lastfile == FILENAME, Zeile 7 greift nicht. END {} greift _NICHT_. Also weiter mit der nächsten Datei. lastfile == "1", FILENAME == "2", words[] ist mit den Treffern aus Datei 1 "gefüllt". Durch Zeile 7 wird erkannt, daß nicht mehr Datei 1 gelesen wird. Also: endfile aufrufen und gucken, ob's Treffer in Datei 1 gab , ggfs. den Dateinamen (lastfile == "1") dazu ausgeben. Dann noch lastfile auf den neuen Dateinamen FILENAME == "2" setzen und Datei 2 durchgehen (analog zu Datei 1). Am Ende von Datei 40 greift dann Zeile 7 nicht mehr (FILENAME ist immer noch == lastfile == "40"). Dafür greift aber die END Bedingung, also auch hier noch nach Treffern gucken und ggfs. lastfile == FILENAME == "40" ausgeben. Und fertig. Beim zweiten Schwung Dateien das gleiche, aber eben mit Dateien 41 - 60. Ich hoffe du kannst das nachvollziehen ;) Oh, und du kannst die Bedingung, was vorkommen muß oder darf auch noch ausgestalten, z.B.: function endfile() { if( words[1] && ( words[2] || words[3] ) && words[4] ) { print lastfile; } } Also 1 UND ( 2 ODER 3 ) UND 4 in bel. Reihenfolge(!). Deswegen müßte man auch bei perl mit einem Hash arbeiten (genau wie hier im awk) oder mit einer komplexen RE (die alle Permutationen der Reihenfolge enthalten muß) die ganze Datei angucken (das ist lahm und braucht Speicher. Für obige nur 3 Bedingungen wären das (in perl): m/ 1.*(2|3).*4 | 1.*4.*(2|3) | (2|3).*1.*4 | (2|3).*4.*1 | 4.*(2|3).*1 | 4.*1.*(2|3) /xgs; Bei 5 Bed. wären es schon 5! = 120 Regexe die man hinschreiben (oder generieren müßte). Und bei 10 Bed. is wohl Feierabend: 10! = 3,628,800 Viel Spaß (und grottige Performance, wenn du auch nur 120 REs mit ner ganzen Datei vergleichst)! Mit nem Hash und zeilenweiser Suche in awk (hier eben "array") oder perl ist sowas aber kein Problem, wie ich ja demonstriere. Und wenn was auf einer Zeile vorkommen muß, mußt du eben die passende RE dazu angeben, z.B. für 'wort1': /^From: .*atann@/ { words[1] = 1; } Du kannst das Array "words" auch mit Strings indizieren (wenn du es explizit auswertest, sonst brauchst du noch ein Array der Indices, das wird ein bissl kompliziert jetzt, aber wenn du eben "explizit" darauf zugreifst geht genauso: 2 function endfile() { 3 if( words["From"] && words["Subject"] ) { 4 print lastfile; 5 } 6 } 7 lastfile != FILENAME { 8 endfile(); 9 words["From"] = 0; words["Subject] = 0; 10 lastfile = FILENAME; 11 } 12 /^From: .*atann@/ { words["From"]=1; } 13 /^Subject: .*(foo|bar)/ { words["Subject"]=1; } (wobei man evtl. die Variable words in "found" umbenennen sollte ;) Bei Mails mußt du auch noch beachten, daß die Header (auch From und natürlich Subject) kodiert sein müssen (Quoted-Printable oder Base64). In dem Fall ist es dann einfacher doch perl zu verwenden, damit kannst du die Header bequem parsen und dekodieren und dann durchsuchen, was ich z.B. mit den c't-Register-Updates mache [1].
Ich sehe, daß Dein Skript funktioniert. Folgendes Skript funktioniert aber auch:
find ~/Mail -type f -exec gawk ' /wort1/ { words[1]=1; } /wort2/ { words[2]=1; } /wort3/ { words[3]=1; } END { if ( words[1] && words[2] && words[3] ) { print FILENAME; } }' '{}' \;
Das ist viel einfacher als Deines, auch wenn Deines etwas schneller ist: Deines braucht 3:59, um meinen Mailspool umzugraben, meines 4:35.
Der Denkfehler hier ist: END greift nur bei der letzten Datei eines Schwungs, und words[] enthält Treffer aus allen Dateien (1-40, 41-60), ausgegeben wird aber jew. der letzte Dateiname (40, 60) wenn in allen Dateien eines Schwungs die Such-Regexe "trafen" egal wie diese Treffer über die Dateien verteilt waren. Meinetwegen z.B. "wort1" in Datei 1, wort2 in Datei 2 ... Ausgegeben wird aber "40", selbst wenn in der gar nix passte. Solltest du evtl. sogar anhand von ein paar (ausgewählten) Dateien testen können ;) Achso: awk kann auch einen Stringvergleich (-> vgl. 'fgrep'): $0 == "wort1" { ...; } (dann muß aber "wort1" die gesamte Zeile sein, ggfs. kann man das aber ausnützen, z.B. bei Mails direkt das From: angucken): gawk -F':' ' ### : als Feldtrenner -> Mailheader wird am : geteilt in ### $1 und $2 (beide ohne den ":"). $1 == "From" { ### $1 = Headername if( $2 ~ /atann@/ ) { ### $2 = Headerinhalt, hier nun Suche ### per Regex mail_ist_von_mir = 1; } } /^$/ { nextfile; ### nur den Header angucken: erste Leerzeile -> ### weiter zur nächsten Datei } ### ... ' Achso, so längliche und kommentierte Scripte würde ich als Dateien speichern und ausführbar machen: ==== ~/bin/mailsuche ==== #!/usr/bin/gawk -f ... ==== und dann eben find ... -exec mailsuche '{}' + wobei man ggfs. auch noch Regexe/Suchwörter per Variable beim Aufruf übergeben könnte, wenn man das Script flexibler machen will. Kurzum: awk ist für viele Dinge ein sehr praktisches Programm bzw. genauer eine sehr praktische Scriptsprache. So, alle Klarheiten beseitigt? Geschafft vom kl. Awk-Kurs? :) Und die Behauptung anderswo im Thread, man könne nicht in mehreren Dateien nach mehreren Regexen suchen ohne die Dateien mehrfach zu lesen, habe ich wohl auch widerlegt. :P HTH, -dnh PS: mach doch noch mal nen Benchmark ggü. der grep | grep | grep Variante ;) [1] passende Mail wird in procmail identifiziert und an mein perlscript verfüttert. Das sucht dann den passenden Anhang mit dem eigentlichen Update, pfriemelt den raus und ruft dann gleich noch ctreg.pl auf um den das Update einzuspielen und Register Index zu aktualisieren ;) Wg. Details am Script bei Interesse bitte PM. --
... Bücher sind ein grässliches Medium ... Ich schätze daran die leichte Portierbarkeit vom Sofa ins Bett. [Bjoern Hoehrmann und Peter Bieling in dciwam] -- Um die Liste abzubestellen, schicken Sie eine Mail an: opensuse-de+unsubscribe@opensuse.org Um eine Liste aller verfuegbaren Kommandos zu bekommen, schicken Sie eine Mail an: opensuse-de+help@opensuse.org