pantomime
Anmeldungsdatum: 20. März 2013
Beiträge: 411
|
Hallo, ich bin dabei, mich in das Thema RegEx reinzufuchsen. Und finde kein Beispiel, in dem gezeigt wird, wie man mit einer Standardsyntax Dateien filtert/ findet die bspw. zwei Zeichenfolgen "schwarz" UND "weiss" irgendwo in der Datei enthalten. Es gibt Beispiele, in den mit
schwarz|weiss
Treffer mit "schwarz" ODER "weiss" filtert. Gibt es keine Standard-UND Syntax in RegEx, um Filter mit "schwarz" UND "weiss" platzsparend zu bauen? Muss ich jeweils die Ausgabe der Suche anch "schwarz" im nächsten Schritt nochmal mit "weiss" filtern, um das gewünschte Ergebnis zu bekommen? gruss, pantomime
Moderiert von sebix: Thema in einen passenden Forenbereich verschoben. Bitte beachte die als wichtig markierten Themen („Welche Themen gehören hier her und welche nicht?“) in jedem Forenbereich. Danke.
|
shiro
Anmeldungsdatum: 20. Juli 2020
Beiträge: 1097
|
Hallo pantomime, ich muss leider gestehen, dass ich dich eventuell nicht korrekt verstanden habe. Ich nehme daher mal folgenden Fall an:
echo "Dies ist ein Text ohne Suchworte
2. Zeile nur mit weiss und
3. Zeile nur mit schwarz sowie
4. Zeile mit schwarz und weiss sowie
5. Zeile mit weiss und schwarz
Was soll gefunden werden" | grep -E "schwarz.*weiss|weiss.*schwarz"
4. Zeile mit schwarz und weiss sowie
5. Zeile mit weiss und schwarz
Ist es das was du meinst? Es wäre hilfreich, wenn du an konkreten Beispiel-Daten erläutern würdest, was du erwartest.
|
pantomime
(Themenstarter)
Anmeldungsdatum: 20. März 2013
Beiträge: 411
|
Danke für die Rückfrage shiro, shiro schrieb: Es wäre hilfreich, wenn du an konkreten Beispiel-Daten erläutern würdest, was du erwartest.
Ich möchte bspw. nur die Dateien aus dem Ordner (und evtl. Unterordner, spielt an dieser Stelle aber keine Rolle) als Suchergebnis bekommen, die die Zeichenfolge "schwarz" UND "weiss" enthalten. Dabei können "schwarz" und "weiss" an unterschiedlichen Stellen im Text, in unterschiedlichen Zeilen stehen, also nicht unbedingt als zusammenhängender String "schwarz weiss". So wie ich in der Websuchmaschine als Suchfilter definiere:
Internet-Zugang kostenlos Hauptsache das Wort "kostenlos" kommt zusammen mit "Internet-Zugang" in einer Webseite vor. Gruss, pantomime
|
shiro
Anmeldungsdatum: 20. Juli 2020
Beiträge: 1097
|
Oh je, mit einer einzigen Regex sehe ich da schwarz ☺ Du meinst eventuell so was:
find ./ -name "*fname*" -print0 | while read -d $'\x00' f; do c1=$(grep -c -i "schwarz" $f); c2=$(grep -c -i "weiss" $f); [ $c1 -ne 0 ] && [ $c2 -ne 0 ] && echo "--- OK --- $f"; done
Statt des "while read" kann man die Aktion auch in einen "-exec" Ausdruck packen. Ich habe diese Form gewählt, da ich denke, dass sie leichter Verständlich ist. Alternativ kannst du auch folgendes machen (geht schneller):
grep -l -i "schwarz" *fname* >$$
grep -l -i "weiss" *fname" | join - $$
rm $$
|
user_unknown
Anmeldungsdatum: 10. August 2005
Beiträge: 17574
Wohnort: Berlin
|
Hier schlägt die Stunde des find-Experten 😉
| find -type f -exec grep -q schwarz {} ";" -exec grep -q weiss {} ";" -print
|
Viele wissen, dass man mehrere Optionen von find verketten kann, aber probieren zu ihrem Nachteil nie aus, ob es auch mit -exec und Konsorten funktioniert. Ruft man grep -q Suchwort File auf, so schaltet das -q Grep quiet (leise, geräuschlos). Grep gibt aber true oder false zurück, abhängig vom Ergebnis, und so kann man mehrere Filter hintereinander schalten. Allerdings verschluckt grep dann auch den Dateinamen, nicht nur die Fundstellen - man könnte meinen aus einer Art Trotzreaktion, wenn man es nicht besser wüsste. 😉 Deswegen muss man find am Ende noch -print mitgeben, für alle Files, die es soweit durch die Pipeline geschafft haben. Statt -print funktionieren natürlich auch die Printvarianten oder ein -ls, wenn man tippfaul ist oder an Details zu den Files. Vor -type f setzt man bei Bedarf das Verzeichnis, in dem die Suche stattfinden soll, per Default wird . angenommen.
|
user_unknown
Anmeldungsdatum: 10. August 2005
Beiträge: 17574
Wohnort: Berlin
|
Es geht aber auch mit verschachteltem Grep (apropos Shiro:"leichter verständlich"):
| grep -l weiss $(grep -rl --include "*.txt" schwarz)
|
Das -rl steht nicht für real-life oder rechts-links, sondern für rekursives Suchen in Unterordnern (r) und (l) wie "mach eine Filename-Liste". Die durchsucht dann das äußere grep. Für die rekursive Suche ist die Angabe von Filemustern nutzlich, mit "--include", das hätte in meinem Testdir sonst ewig gedauert. Ohne include: | grep -l weiss $(grep -rl schwarz "*.txt")
|
würde grep nur in Unterverzeichnissen suchen, die auf "*.txt" matchen, was selten das ist, was man möchte.
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11225
Wohnort: München
|
Das ist letztendlich ein Permutationsproblem - für einfachere Fälle braucht man nur zwei alternative Ausdrücke - z.B. wenn man ohne Rücksicht auf Groß- und Kleinschreibung suchen will:
$ shopt -s globstar
$ grep -Poil '(schwarz.*weiß)|(weiß.*schwarz)' **/*.txt
Wenn man über komplette Dokumente suchen will, bietet sich ein Multi-Line-Suchausdruck an - das geht z.B. mit Perl:
find -type f -name '*.txt' -print0 |
xargs -0 perl -n0e 'print "${ARGV}\n" if /(schwarz.*weiß)|(weiß.*schwarz)/is && close ARGV'
Je weiter man in Richtigung Suchmaschine geht, desto eher will man eine dauerhafte Datenstruktur für die indizierten Texte und eine zusätzliche Vorverarbeitung - z.B. könnte man sich überlegen die einzelnen Wörter im Text zu extrahieren und in einer Art Hashmap oder Set zu speichern, um Suchen nach beliebigen Begriffen schneller zu machen und die Wiederholungen zu zählen, um die Treffer zu gewichten.
|
user_unknown
Anmeldungsdatum: 10. August 2005
Beiträge: 17574
Wohnort: Berlin
|
shiro schrieb: Oh je, mit einer einzigen Regex sehe ich da schwarz ☺ Du meinst eventuell so was:
find ./ -name "*fname*" -print0 | while read -d $'\x00' f; do c1=$(grep -c -i "schwarz" $f); c2=$(grep -c -i "weiss" $f); [ $c1 -ne 0 ] && [ $c2 -ne 0 ] && echo "--- OK --- $f"; done
Diesen Ansatz kannst Du verbessern, in dem Du auf die Zwischenvariablen c1, c2 verzichtest, und die greps gleich verundest². Da von Ignorierung der Groß-/Kleinschreibung keine Rede war, habe ich das -i auch rausgeschmissen und das geschwätzige "–- OK –-", welches ein Ärgernis ist, wenn man das Ergebnis weiterverarbeiten will (siehe: The Art of Unix Programming, Eric S. Raymond). Beim \0 lassen sich auch 2 Bytes einsparen. ☺
Mit -q statt -c ermuntert man grep die Suche beim ersten Treffer abzubrechen und gewinnt so evtl. etwas Zeit bei großen Dateien. | find -name "*.md" -print0 | while read -d $'\0' f; do grep -q "weiss" $f && grep -q "schwarz" $f && echo "$f"; done
|
– ²verunden: UND-verknüpfen
|
user_unknown
Anmeldungsdatum: 10. August 2005
Beiträge: 17574
Wohnort: Berlin
|
seahawk1986 schrieb: Das ist letztendlich ein Permutationsproblem - für einfachere Fälle braucht man nur zwei alternative Ausdrücke - z.B. wenn man ohne Rücksicht auf Groß- und Kleinschreibung suchen will:
$ shopt -s globstar
$ grep -Poil '(schwarz.*weiß)|(weiß.*schwarz)' **/*.txt
Die Schalter -o und -l konfligieren aber miteinander, oder? Das -o sagt, nur die Matches anzeigen, das -l sagt, nur den Filename anzeigen. Das -l scheint sich so oder so - unabhängig von der Reihenfolge - durchzusetzen. Woher die Obsession kommt, die Usern ein -i unterzuschieben, frage ich mich - wohnt das Herz noch in Redmont? 😉 Eine Permutation sehe ich da nicht im Anzug, denn wenn man 100 Files hat, 10 mit "weiss" und 10 mit "schwarz", und 2 mit sowohl als auch, dann filtert meine 1. find-Lösung erst 10 aus, und durchsucht nur diese nach Matches für den zweiten Ausdruck. Ebenso sucht die verschachtelte Greplösung nur im Subset der Files, die das erste Muster erhalten. Auch das kurzschließende UND meines dritten Beitrags, der freilich nach Deinem Posting gepostet wurde, durchsucht nicht nochmal die Grundgesamtheit. Aber vielleicht meinst Du die Ausdrücke wie /(schwarz.*weiß)|(weiß.*schwarz)/ - ja, richtig, das wird schnell eklig (Suche nach 15 Ausdrücken, bspw.).
Je weiter man in Richtigung Suchmaschine geht, desto eher will man eine dauerhafte Datenstruktur für die indizierten Texte und eine zusätzliche Vorverarbeitung - z.B. könnte man sich überlegen die einzelnen Wörter im Text zu extrahieren und in einer Art Hashmap oder Set zu speichern, um Suchen nach beliebigen Begriffen schneller zu machen und die Wiederholungen zu zählen, um die Treffer zu gewichten.
Oder vielleicht speichert man die Daten in einer Datenbank, die entsprechende Tools schon mitbringt. ☺ Mir schien das aber ein ad-hoc-Problem zu sein, was die Diskussion etwas elfenbeinturmig macht, aber ich kann auch selten an Elfenbeintürmen vorbeigehen. 😉
|
shiro
Anmeldungsdatum: 20. Juli 2020
Beiträge: 1097
|
user_unknown schrieb: | find -name "*.md" -print0 | while read -d $'\0' f; do grep -q "weiss" $f && grep -q "schwarz" $f && echo "$f"; done
|
Danke für den Tip mit "grep -q". Den Switch hatte ich nicht auf dem Schirm. Aber hast du mal die obige Variante mit dem "join" ausgetestet? Diese Lösung ist bei mir ca 4x schneller als die "find/grep -q" Variante.
|
user_unknown
Anmeldungsdatum: 10. August 2005
Beiträge: 17574
Wohnort: Berlin
|
shiro schrieb: user_unknown schrieb: | find -name "*.md" -print0 | while read -d $'\0' f; do grep -q "weiss" $f && grep -q "schwarz" $f && echo "$f"; done
|
Danke für den Tip mit "grep -q". Den Switch hatte ich nicht auf dem Schirm. Aber hast du mal die obige Variante mit dem "join" ausgetestet? Diese Lösung ist bei mir ca 4x schneller als die "find/grep -q" Variante.
Die join-Lösung erfüllt ja nicht die Anforderung in Unterverzeichnissen zu suchen - einen anderen Grund, find zu benutzen sehe ich hier nicht. Außerdem ist das Quoting kaputt - man sollte immer getesteten Code mit copy-paste einfügen, um solche Probleme zu vermeiden. Außerdem verwendet es diesen -i-Schalter, von dem keine Rede war.
| t530:~ 🐧> function joinGrep () { grep -il "Git" *.md >$$; grep -il "Stefan" *.md | join - $$; }
t530:~ 🐧> time joinGrep
join: -:6: ist nicht sortiert: git.md
join: 4819:2: ist nicht sortiert: git.md
git-student.md
git.md
remote.md
real 0m0,012s
user 0m0,009s
sys 0m0,006s
|
Und schau, da sind noch häßliche Warnungen (Fehler?).
| t530:~ 🐧> time grep -il "Git" $(grep -il "Stefan" *.md)
git-student.md
git.md
remote.md
real 0m0,008s
user 0m0,001s
sys 0m0,008s
|
Ist auch nicht um Welten schneller - ist immer die Frage wie viele Millionen User das auf wieviele megabytegroße Dateien wie oft am Tag loslassen. Dieses -print0 lehne ich nicht dogmatisch ab, aber hier ist es natürlich ein sinnloser Umweg. Man kann die Files gleich dem ersten Grep verfüttern. | t530:~ 🐧> time find -name "*.md" -exec grep -iq Stefan {} ";" -exec grep -iq Git {} ";" -print
./remote.md
./git.md
./git-student.md
real 0m0,063s
user 0m0,043s
sys 0m0,021s
|
Im Vergleich mit Deiner Joinlösung langsam, und wir wissen ja, Programmierer bevorzugen schnelle Programme gegenüber korrekten. Hier der einschlägige Hinweis aus der Manpage (join):
Important: FILE1 and FILE2 must be sorted on the join fields. E.g., use "sort -k 1b,1" if 'join' has no options, or use "join -t " if 'sort' has no options. Note, comparisons honor the rules specified by 'LC_COLLATE'. If the input is not sorted and some lines cannot be joined, a warning message will be given.
Performancefragen sind ein eigenes Thema. Das kann sehr davon abhängen, wie die Dateien beschaffen sind, viele Treffer, wenig Treffer, normalverteilt, gleichverteilt, ... - evtl. Optimierungen, die man machen kann. Meist verliert man, wenn man eine Sekunde nachdenkt, schon mehr Zeit, als man wieder reinholen kann, bei solchen ad-hoc-Lösungen. Alles unter 300ms nimmt man als User gar nicht wahr, auch wenn das eine 100x schneller ist, als das andere.
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12986
|
Eine effiziente Variante, bei der die Datei nur ein Mal gelesen wird, kann so aussehen: | awk '/schwarz/{s=1} /weiss/{w=1} END {exit(s && w ? 0 : 1)}' file
|
In einer Suche kannst Du das so verwenden: | find wo/auch/immer -type f -exec awk '/schwarz/{s=1} /weiss/{w=1} END {exit(s && w ? 0 : 1)}' {} \; -print
|
|
pantomime
(Themenstarter)
Anmeldungsdatum: 20. März 2013
Beiträge: 411
|
Riesen Dank an alle ♥ 👍 ❗ Da habe ich was zum Testen, Lernen, Üben 😊
|
pantomime
(Themenstarter)
Anmeldungsdatum: 20. März 2013
Beiträge: 411
|
Die Suche von user_unknown find -type f -exec grep -q schwarz {} ";" -exec grep -q weiss {} ";" -print und diese zwei Suchen - auch von user_unknown - mit entsprechenden Anpassungen find -name "*.md" -exec grep -iq Stefan {} ";" -exec grep -iq Git {} ";" -print
grep -l weiss $(grep -rl --include "*.txt" schwarz)
funktionieren bei mir auf Anhieb. Die variante ohne include von user_unknown
grep -l weiss $(grep -rl schwarz "*.txt")
bringt die Meldung *.txt: Datei oder Verzeichnis nicht gefunden oder auch *.*: Datei oder Verzeichnis nicht gefunden Diese Variante grep -l weiss $(grep -rl schwarz ) ohne Einschränkung der Dateiendung funktioniert. In meinem Anwendungsszenario ist die Einschränkung auf Dateiendung nicht notwendig - es müssen alle Dateien durchgesucht werden. FRAGE: Wo finde ich das Ergebnis von Lösungen, in den "-print0" verwendet wird? Die Suchen laufen - scheinbar ohne Fehler - durch, aber ich sehe das Ergebnis nicht. Edit 24.09.2022 ANFANG Die Lösung von seahawk1986
find -type f -name '*.*' -print0 | xargs -0 perl -n0e 'print "${ARGV}\n" if /(schwarz.*weiss)|(weiss.*schwarz)/is && close ARGV' funktioniert, wenn ich im Filter '*.txt' durch '*.*' - oder '*.desktop' - das ist die Endung der zu durchsuchenden Dateien in meinem Anwendungsszenario - ersetze. Die Lösung von @rklm
find -type f -exec awk '/schwarz/{s=1} /weiss/{w=1} END {exit(s && w ? 0 : 1)}' {} \; -print
funktioniert bei mir. Habe in meinem Test "wo/auch/immer" weggelassen, da der Befehl im zu durchsuchenden Verzeichnis ausgeführt wird. Edit 24.09.2022 ENDE Getestet mit
GNU bash, Version 5.1.16(1)-release (x86_64-pc-linux-gnu) @rklm Was verwende ich als Wert für "file"-Parameter? Wenn ich
awk '/schwarz/{s=1} /weiss/{w=1} END {exit(s && w ? 0 : 1)}' file ausführe, kommt bei mir die Meldung awk: cannot open file (No such file or directory) Auch wenn ich an Stelle von "file" einen Dateinamen verwende, kommt die oben zitierte Fehlermeldung.
$ dpkg-query -W mawk
mawk 1.3.4.20200120-3
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12986
|
pantomime schrieb:
@rklm Was verwende ich als Wert für "file"-Parameter? Wenn ich
awk '/schwarz/{s=1} /weiss/{w=1} END {exit(s && w ? 0 : 1)}' file ausführe, kommt bei mir die Meldung awk: cannot open file (No such file or directory)
Das ist jetzt nicht Dein Ernst, oder?
Auch wenn ich an Stelle von "file" einen Dateinamen verwende, kommt die oben zitierte Fehlermeldung.
Beweise?
|