ubuntuusers.de

Zeichenfolgen an unterschiedlichen Stellen in der Datei

Status: Ungelöst | Ubuntu-Version: Ubuntu 22.04 (Jammy Jellyfish)
Antworten |

pantomime

Anmeldungsdatum:
20. März 2013

Beiträge: 512

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 Team-Icon

Supporter

Anmeldungsdatum:
20. Juli 2020

Beiträge: 1271

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: 512

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 Team-Icon

Supporter

Anmeldungsdatum:
20. Juli 2020

Beiträge: 1271

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

Avatar von user_unknown

Anmeldungsdatum:
10. August 2005

Beiträge: 17621

Wohnort: Berlin

Hier schlägt die Stunde des find-Experten 😉

1
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

Avatar von user_unknown

Anmeldungsdatum:
10. August 2005

Beiträge: 17621

Wohnort: Berlin

Es geht aber auch mit verschachteltem Grep (apropos Shiro:"leichter verständlich"):

1
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:

1
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: 11261

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

Avatar von user_unknown

Anmeldungsdatum:
10. August 2005

Beiträge: 17621

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.

1
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

Avatar von user_unknown

Anmeldungsdatum:
10. August 2005

Beiträge: 17621

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 Team-Icon

Supporter

Anmeldungsdatum:
20. Juli 2020

Beiträge: 1271

user_unknown schrieb:

1
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

Avatar von user_unknown

Anmeldungsdatum:
10. August 2005

Beiträge: 17621

Wohnort: Berlin

shiro schrieb:

user_unknown schrieb:

1
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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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?).

1
2
3
4
5
6
7
8
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.

1
2
3
4
5
6
7
8
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 Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13207

Eine effiziente Variante, bei der die Datei nur ein Mal gelesen wird, kann so aussehen:

1
awk '/schwarz/{s=1} /weiss/{w=1} END {exit(s && w ? 0 : 1)}' file

In einer Suche kannst Du das so verwenden:

1
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: 512

Riesen Dank an alle ♥ 👍 ❗ Da habe ich was zum Testen, Lernen, Üben 😊

pantomime

(Themenstarter)

Anmeldungsdatum:
20. März 2013

Beiträge: 512

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 Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13207

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?

Antworten |