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

awk - doppelte Leerzeichen erkennen - wie?

Hallo,
ich komme hier mit einem bestimmten awk Problem nicht weiter.
In mehreren tausend Textdateien sollen bestimmte formale Eigenschaften gesucht werden. Unter anderem auch doppelte Leerzeichen im laufenden Text. Das geht zwar mit regexp / / ganz einfach, aber die Ausgabe soll so sein, dass (neben dem Dateinamen) auch das Wort vor und das Wort hinter dem doppelten Leerzeichen mit ausgegeben wird, damit man die entsprechende Textstelle schneller finden kann. Mit doppelten Wörtern war das kein Problem, aber bei den doppelten Leerzeichen stehe ich hier irgendwie auf den Schlauch oder sehe den Wald vor lauter Bäumen nicht.
Hat jemand eine Idee?
Nebenbei: Weiß jemand die maximale Anzahl von Dateien, die man an awk übergeben kann? (4000 waren zuviel - 2000 waren ok.)
Gruß, oa
 
Code:
export GREP_COLOR="36;7";
grep -Pn '\w+  \w+' *.txt;
Zuviele Dateien? Mit Blättern?
Code:
find blabla -iname '*.txt' -print0 | xargs -0 grep -Pn '\w+  \w+' | less -RS
ça, c'est bien.
Wenn man will, kann man auch zusätzlich grep noch -o mitgeben.
 
OP
O

oa

Hallo jengelh,

das ist ja recht trickreich, aber die Ausgabe des ganzen Records wollte ich gerade unterdrücken, da er in manchen Dateien eine DIN A4 Seite umfassen kann. So weit war ich mit awk Bordmitteln schon gekommen.
Was ich suche, ist eine Ausgabe etwa der folgenden Art:
Code:
"<Dateiname> ... Doppeltes Leerzeichen in Absatz  <Nr.>  zwischen <Wort1>  und  <Wort2>."
Mein letzter Versuch sah so aus (hat auch nicht geklappt - bin da als awk Anfänger noch am basteln)
Code:
for (i = 1; i <= NF; i++)

	if (FS =="  ")
	    print  "--------------------------------------------------------------------------------";\
	    printf "%.30s", substr(FILENAME, 9);
	    print  "Doppeltes Leerzeichen in Absatz " FNR " nach \""; $i "..\"" ;\

Es wird dann merkwürdigerweise jede " \n" im Text als " " (zwei Leerzeichen) interpretiert und demzufolge auch kein $i ausgegeben.

Gruß, oa
 
oa schrieb:
Hallo jengelh,

das ist ja recht trickreich, aber die Ausgabe des ganzen Records wollte ich gerade unterdrücken, da er in manchen Dateien eine DIN A4 Seite umfassen kann. So weit war ich mit awk Bordmitteln schon gekommen.
Was ich suche, ist eine Ausgabe etwa der folgenden Art:
Code:
"<Dateiname> ... Doppeltes Leerzeichen in Absatz  <Nr.>  zwischen <Wort1>  und  <Wort2>."
Ah na dann nimm doch folgende Regex..
Code:
\w{1,30}  \w{1,30}
dann ist die Ausgabe ca. eine Zeile lang.
 
Hallo oa,

vielleicht ist "awk" nicht mächtig genug, keine Ahnung ... Perl wäre es jedenfalls:
Bitte kopiere alle Dateien in ein Verzeichnis und folgendes Skript dazu:
Code:
siehe unten
Dann laß es dort einmal ablaufen. Wenn alle Zeilen korrigiert sind (das könnte man ggf. auch per Skript machen), laß es nochmal ablaufen, da es Zeilen geben kann, in denen mehrmals " " auftaucht.

Viele Grüße

Edit: Noch einen kleinen Bug entfernt.
 
OP
O

oa

Hallo jengelh,
danke für die schnelle Hilfe.
Mein code sieht jetzt in Anlehnung an Deinen Vorschlag so aus (zwei Varianten):
Code:
#!/usr/bin/awk -f
{ 	for (i = 1; i <= NF; i++)
#	/\w{1,30}"  "\w{1,30}/
    /\w{1,30}  \w{1,30}/
	    print  "--------------------------------------------------------------------------------";\
	    printf "%.30s", substr(FILENAME, 9);
	    print  "		doppeltes Leerzeichen in Absatz " FNR " nach \""; $i "..\"" ;\
	print "--------------------------------------------------------------------------------"
}
Die Ausgabe gleicht allerdings in beiden Fällen der meines obigen Codes.
Code:
<Dateiname1>         doppeltes Leerzeichen in Absatz 1 nach "
--------------------------------------------------------------------------------
<Dateiname1>         doppeltes Leerzeichen in Absatz 2 nach "
--------------------------------------------------------------------------------
<Dateiname1>         doppeltes Leerzeichen in Absatz 3 nach "
--------------------------------------------------------------------------------
<Dateiname1>         doppeltes Leerzeichen in Absatz 4 nach "
Die hier verwendete Beispieldatei besteht aus 4 Zeilen.
Das "\w" aus Deinem Vorschlag interpretiere ich mal Escape Sequenz (die ich leider im awk/gawk Tutorial von Michael Brennan (Autor von mawk) nicht als solche verzeichnet finde.) Auf der anderen Seite sieht es mehr nach einer Längen/Bereichsangabe aus. Wofür steht dann der Backslash? - Sorry für die Anfängerfragen.

Hallo abgdf,
Dein Perlscript habe ich noch nicht ausprobiert, weil ich mich in den Gedanken verbissen habe, es mit gawk zu machen. Da hat mich irgendwie der Ehrgeiz gepackt, vom Suchtfaktor mal abgesehen.
Als Anfänger guck ich neidisch darauf, wie Du so schnell mal eben ein Perl Script hinblätterst. Wenn ich keine andere Lösung finde, werde ich es mit Deiner Erlaubnis gnadenlos abkupfern. Bis dahin renne ich mir noch ein bischen den Kopf an der "awk Wand" ein.

Gruß, oa
 

framp

Moderator
Teammitglied
Wie wärs mit
/.* .*/ { # two spaces !
# split line into tokens sperated with double spaces
tokenNr=split($0,tokens," ") # two spaces !
result=""
# now process all tokens separated by double spaces
for (i=1;i<=tokenNr;i++) {
# now split all parts separated by space
candidateNr=split(tokens,candidate," ") # one space !
if (i == 1) {
result=result " " candidate[candidateNr]
}
else if (i == tokenNr) {
result=result " " candidate[1]
}
else {
result=result " " candidate[1] " | " candidate[candidateNr]
}
}
print "Line " NR ": " result
}
 
OP
O

oa

Hallo framp,
im Moment sieht die Ausgabe noch so aus:
Code:
 Beispiel:
Dabei | Dabei ging | ging er | er mit | mit der | der ... usw.
D.h., dass alle vorkommenden einfachen Leerzeichen angezeigt werden.
Dort, wo doppelte Leerzeichen sind, ist die Ausgabe identisch mit der Ausgabe der einfachen Leerzeichen. Wenn da ein Unterschied wäre, könnte man sich immerhin langsam herantasten.
Naja, werde versuchen das script zu verstehen und daran weiter basteln.

Danke für Eure rege Anteilnahme (das tut richtig gut)
Gruß, oa
 

framp

Moderator
Teammitglied
Hm ... mit dem folgenden Input
1- -2 3 4 5
1- -2 3 4- -5 6 7 8
1- -2 3 4- -5 6 7- -8 9
Das ist eine Zeile ohne Fehler
Das ist eine- -Zeile mit Fehler
Das ist eine- -Zeile mit zwei- -weiteren Fehlern
erhalte ich
framp@obelix:/disks/hde6/home/framp> cat doubleSpace.dat | awk -f doubleSpace
Line 1: 1- -2
Line 2: 1- -2 | 4- -5
Line 3: 1- -2 | 4- -5 | 7- -8
Line 5: eine- -Zeile
Line 6: eine- -Zeile | zwei- -weiteren

Und das sieht doch Ok aus - oder? :roll: (Die '-' begrenzen immer links und rechts ein Double Space).

Achte auf die Kommentare bzgl single und double spaces im Code. Das wird in den quote tags zu EINEM space reduziert :shock: Du musst also den Code nachträglich ändern. Wenn Du willst schicke ich Dir die paar Zeilen auch per eMail. Kurze PN mit Deiner eMail Adresse an mich.
 
Hallo oa,

vielen Dank für die Blumen :p.

Müssen die " " letztlich eigentlich manuell entfernt werden, weil manche sinnvoll sind, viele aber nicht ?
Sonst könnte man mit einer entsprechenden Schleife und sowas wie
Code:
$line =~ s/  / /g
einfach ALLE automatisch umändern, so daß überall nur " " vorhanden wäre.

Ich bin im Zweifel, ob man um die 4.000 Textdateien wirklich komplett manuell angehen sollte.

Man könnte das Perl-Skript auch so schreiben, daß die zwei Wörter und/oder die Zeile jeweils angezeigt werden, und dann jeweils nur "j"/"y" oder "n" eingegeben werden muß, um das " " zu " " zu ändern oder nicht.

(Bisher liest mein Skript jede Textdatei komplett in eine Listenvariable ein. Bei 4.000 Durchläufen könnte es dabei vielleicht zu Speicherproblemen kommen (obwohl die Variable jedesmal neu gefüllt wird, so daß der Rest Perls "Garbage Collector" zum Opfer fallen müßte). Bei Speicherproblemen müßte man sonst zeilenweises Einlesen programmieren).

Aber, wenn Du willst, probier's gern auch mit "awk".

Viele Grüße
 

framp

Moderator
Teammitglied
abgdf schrieb:
...Aber, wenn Du willst, probier's gern auch mit "awk".
Mit awk kenne ich mich ganz gut aus. Immer wieder starte ich Versuche perl zu benutzen - es ist bzgl der Funktionalität wesentlich mächtiger als awk. Ich schaffe es auch kleine perl scripts zu erstellen. Aber die Syntax ist schon sehr gewöhnungsbeürftig. awk ist da übersichtlicher ... Deshalb bevorzuge ich auch awk und kann den TE verstehen - denn ihm scheint es wohl ähnlich zugehen :roll:
 
Fakt ist, du willst zwei Leerzeichen finden, und ein paar Hilfestellungen haben wo sich das ganze befindet. Dazu hast du einmal grep -n, dass dir die Zeilennummer anzeigt, und \w{1,30}, dass die jeweils bis zu dreißig Zeichen (*eines*) Wortes auf jeder Seite anzeigt. (Alternativ: /.{1,30} .{1,30}/) Dazu braucht man kein perl und auch kein awk.
 
OP
O

oa

Heute früh unter der Dusche hatte ich noch die Idee, dass wenn man den Record Separator auf RS=" " (2 Leerzeichen) umstellt, sich daraus etwas machen lassen müsse. Heute abend also gleich folgendes ausprobiert:
Code:
#!/usr/bin/awk -f
BEGIN {RS = "  "}
{
	print  "--------------------------------------------------------------------------------";\
	printf "%.30s", substr(FILENAME, 9);# Formatierung tut hier eigentlich nichts zur Sache
	print  "	Doppeltes Leerzeichen in Absatz " FNR " nach " "\""$(NF -1)" " $NF "\""
}
Tatsächlich bekomme ich die zwei Worte vor dem doppelten Leerzeichen angezeigt (Täterätää usw.) Dummerweise aber auch noch das, was nach einem "\n\r" kommt (die txt.Dateien wurden alle mit Windows erstellt) und die damit verbundenen unerwünschten Ausgaben, wofür ich den Grund nicht recht nachvollziehen kann. Hilfe ist hier weiterhin sehr willkommen.

Gruß, oa

PS:
An abgdf:
Was ich total stark finde, ist die Formulierung von abgdf:
Ich bin im Zweifel, ob man um die 4.000 Textdateien wirklich komplett manuell angehen sollte.
Eine wunderbar höfliche Umschreibung für "Der hat wohl ein Rad ab?" oder so ähnlich. Womit er natürlich völlig recht hätte! (Es gibt jedoch Gründe dafür, die hier aber oT sind)

PPS:
An jengelh
Zunächst mal muss ich mich berichtigen. Das "\w" ist im besagten Tutorial etwas weiter hinten sehr wohl zu finden unter der Bezeichnung Regex - Operator. Hätte nur die Augen besser aufmachen müssen. Während ich hier schreibe lese ich gerade Deinen letzten Beitrag. Danke nochmal dafür. Ich denke, ich werde es zusammen mit meinem anderen awk Kram in ein shell script mit einbauen (es gibt noch ein halbes Dutzend andere Dinge, die durch awk erledigt werden müssen).

PPPS: Vielen Dank nochmal an alle für die hilfreiche Beteiligung.

oa
 
OP
O

oa

Hallo abgdf

Ich habe Dein Perlscript laufen lassen und ich muss sagen, ich bin "positiv erschüttert". Diese Lösung ist überzeugend. Leider habe ich von Perl keinerlei Ahnung. Ich habe es gerade noch hinbekommen, die Ausgabe teilweise an meine Erfordernisse anzupassen.

Drei Fragen habe ich daher noch.

1. Dein Script liegt ja im gleichen Verzeichnis wie die Textdateien. Es wird nun ebenfalls auf Leerzeichen untersucht und hat eine recht umfangreiche Ausgabe zur Folge. An welcher Stelle kann ich einen Pfad zum Script eingeben?

2. Wie lautet der Code, wenn z.B.die ersten 7 Zeichen des Dateinamens nicht ausgegeben werden sollen und von da ab nur die nächsten 25 (relevanten) Zeichen? Durch gleichlange Dateinamen möchte ich eine etwas gefälligere Ausgabe erreichen, da bei mir Dateiname und Fehlerangabe in eine Zeile müssen.

3. Wie wird das Programm aus einem Shellscript heraus aufgerufen?

Der Code für meine Ausgabe sieht jetzt so aus:
Code:
if($line =~ m/  /) {
                print "$file";
                print "		Im $linenumber. Absatz zwischen ---> ";
                print &getWords($line) . "\n";
                print "----------------------------------------------------------------------------------------\n"
            }
Gruß, oa
 
Hallo oa,

zu 1. (Hoffentlich) gefixt :).
2. Siehe neue Funktion "upTo25".
zu 3. Wie jedes andere Programm: Du kannst z.B. auch die Ausgabe mit ">" dahin leiten, wo Du sie haben möchtest usw..

Hier die neue Version (manche spezielle Textzeilen könnten noch Probleme bereiten):
Code:
#!/usr/bin/perl

use warnings;
use strict;

my @a = <*>;
my @b = ();
my $file = "";
my $line = "";
my $linenumber = 0;

foreach (@a) {

    if(-T $_ && $_ ne &stripDir($0)) {
        $file = $_;
        open(FH, "<$file");
        @b = <FH>;
        close(FH);
        $linenumber = 0;

        foreach(@b) {
            $line = $_;
            chomp($line);
            $linenumber++;

            if($line =~ m/  /) {
                print &upTo25($file);
                print "      Im $linenumber. Absatz zwischen ---> ";
                print &getWords($line) . "\n";
                print "----------------------------------------------------------------------------------------\n"
            }
        }
    }
}

sub getWords {
    my $line = shift;

    if($line eq '  ') {
        return "";
    }
    if(index($line, '  ') == 0) {
        return substr($line, 2);
    }
    if(index($line, '  ') == length($line) - 2) {
        return substr($line, 0, length($line) - 2);
    }

    my @a = split('  ', $line);
    my $first = $a[0];
    my $sec = $a[1];
    my @b = ();

    if($first =~ m/ /) {
        @b = split(' ', $first);
        $first = pop(@b);
    }

    if($sec =~ m/ /) {
        @b = split(' ', $sec);
        $sec = shift(@b);
    }

    return join('  ', ($first, $sec));
}

sub stripDir {
    my @a = split('/', $_[0]);
    my $b = pop(@a);
    return $b;
}

sub upTo25 {
    if(length($_[0]) < 7) {
        return $_[0];
    } elsif(length($_[0]) < 32) {
        return substr($_[0], 7);
    }
    return substr($_[0], 7, 25);
}

Funktioniert das bei Dir ?

Viele Grüße

P.S.: Bei der Gelegenheit hab ich auch gleich an meiner Perl-Seite (am besten ohne JavaScript anschauen)

http://www.angelfire.com/linux/tux25/perl.html

gebastelt. Sie sollte eigentlich eher wie meine Python-Seite

http://www.angelfire.com/linux/tux25/python.html

werden, wird bislang aber doch mehr und mehr zu einem Perl-Programmier-Tutorial, obwohl es davon natürlich schon jede Menge gibt ...
 
OP
O

oa

Hallo abgdf,
War einige Tage "weggetreten", sorry für die Verzögerung.
Das funktioniert bei mir ganz ausgezeichnet, was die Ausgabe/Umleitung der Ergebnisse in eine Textdatei betrifft und soweit ich das überblicken kann.
Auf der Konsole bekomme ich allerdings folgende Meldungen:
Code:
Use of uninitialized value in join or string at ./lta-20-perl line 64.
Use of uninitialized value in pattern match (m//) at ./lta-20-perl line 59.
Use of uninitialized value in join or string at ./lta-20-perl line 64.
Und zwar die Meldung für line 64 über 20 mal.
Was das für die Funktionalität wirklich bedeutet, kann ich natürlich nicht beurteilen.
Gruß, oa

PS: Wenn ich mir die Zeitstempel meines vorletzten Beitrages und Deines letzten Beitrages ansehe, dann zeigt sich darin, denke ich, die einzigartige Qualität der OpenSource Gemeinde: Schnelligkeit und Effizienz wird eben nicht durch hierarchischen Druck erreicht, sondern durch freie Initiative.
(Ich hoffe, Du hältst den ganzen Weihrauch aus)
oa
 
Hallo oa,

ja, das habe ich erwartet. Das kommt bei bestimmten Zeilen, z.B. " dsasdad": Wenn ich die bei " " splitte (also gleich am Zeilenanfang) und dann versuche, das Wort links davon zu greifen, ist da nichts.
Diesen Fall habe ich abgefangen, die Zeilen, wo jetzt der Fehler kommt, müssen also noch etwas anders aussehen.
Das kann ich ohne Deine Textdateien aber leider schlecht testen.
Ich würde sagen, benutz erstmal das Skript ein paarmal nacheinander (wegen Zeilen mit mehrmals " " (s.o.)) so, und laß nach der Korrektur nochmal ein "grep" über die Textdateien laufen.

Über Lob freue ich mich immer :D.

Viele Grüße
 
Hallo oa,

doch noch ein 3. Versuch: Die neue Version erkennt auch (soll auch erkennen), wenn mehrmals " " in einer Zeile vorkommt :mrgreen:. Dann wird das mit den Wörtern links und rechts zu umständlich, da zweimal " " direkt nebeneinander sein kann (was beim 2. Skript wohl die Warnungen verursacht hat). Die Zeile wird dann also ganz ausgegeben.
Sonst (bei einmal " ") wie bisher. So sollte es keine Fehler mehr geben.
Puh, allmählich wird's dann doch kompliziert :roll::
Code:
#!/usr/bin/perl

use warnings;
use strict;

my @a = <*>;
my @b = ();
my $file = "";
my $line = "";
my $linenumber = 0;
my @empties = ();

foreach (@a) {

    if(-T $_ && $_ ne &stripDir($0)) {
        $file = $_;
        open(FH, "<$file");
        @b = <FH>;
        close(FH);
        $linenumber = 0;

        foreach(@b) {
            $line = $_;
            chomp($line);
            $linenumber++;

            @empties = &getEmpties($line);

            if($#empties > 0) {
                print &upTo25($file);
                print "      Mehrere im $linenumber. Absatz an Positionen ";
                print join(", ", @empties) . ":\n";
                print "Die Zeile lautet: $line\n";
                print "----------------------------------------------------------------------------------------\n"

            } elsif ($#empties == 0) {
                print &upTo25($file);
                print "      Im $linenumber. Absatz zwischen ---> ";
                print &getWords($line) . "\n";
                print "----------------------------------------------------------------------------------------\n"
            }
        }
    }
}

sub getWords {
    my $line = shift;

    if($line eq '  ') {
        return "";
    }
    if(index($line, '  ') == 0) {
        return substr($line, 2);
    }
    if(index($line, '  ') == length($line) - 2) {
        return substr($line, 0, length($line) - 2);
    }

    my @a = split('  ', $line);
    my $first = $a[0];
    my $sec = $a[1];
    my @b = ();

    if($first =~ m/ /) {
        @b = split(' ', $first);
        $first = pop(@b);
    }

    if($sec =~ m/ /) {
        @b = split(' ', $sec);
        $sec = shift(@b);
    }

    return join('  ', ($first, $sec));
}

sub stripDir {
    my @a = split('/', $_[0]);
    my $b = pop(@a);
    return $b;
}

sub upTo25 {
    if(length($_[0]) < 7) {
        return $_[0];
    } elsif(length($_[0]) < 32) {
        return substr($_[0], 7);
    }
    return substr($_[0], 7, 25);
}


sub getEmpties {

    my $a = shift;
    my @b = ();
    my $x = 0;

    while($x < length($a)) {
        $x = index($a, "  ", $x);
        if($x == -1) {
            last;
        }
        push(@b, $x);
        $x += 2;
    }

    return @b;
}
Viele Grüße
 
OP
O

oa

So, bin wieder im Lande.
Du hast Recht, es wird langsam schwierig. Für mich aber doch auch lehrreich. Die digitale Logik des Rechners und die analoge Logik des Alltags sind nicht immer leicht zur Deckung zu bringen (wenn überhaupt jemals - meiner Meinung nach werden die Rechner der Zukunft Analogrechner auf der Basis gentechnisch erzeugter Nervensysteme sein - aber das nur nebenbei).
Es kommt z.B. an manchen Stellen zu folgender Ausgabe:
Code:
2.tx      Mehrere im 1556. Absatz an Positionen 4, 6, 8:
Die Zeile lautet: .txt      Mehrere im 213. Absatz an Positionen 25, 27, 29, 57, 59, 61:
Die erzeugte txt-Datei ist nun 1.6 MB (vorher 145 KB), es tauchen nun Dateinamen wie "perl", txt~ , 2.tx auf und manchmal werden die Absätze über die Dateigrenzen hinweg durchgezählt.
Code:
2.tx      Mehrere im 6729. Absatz an Positionen 22, 24, 26, 82:
Die Zeile lautet: Die Zeile lautet: .txt      Im 885. Absatz zwischen ---> _98_ABC_Xxxx-Xxxxx_12-34 <das letzte ist der formatierte Dateiname-(anonymisiert)>
Ich denke, dies ist der Zeitpunkt, wo ich mit der vorhergehenden Version zufrieden sein kann.
Außerdem wird es für Dich langsam frustrierend, da Du, was die Struktur der Textdateien betrifft, ständig im Dunkeln tappen mußt. Am besten wäre es natürlich, wenn du einen Schwung Beispieldateien zur Verfügung hättest. Aber die lassen sich nicht sinnvoll anonymisieren (noch dazu in der benötigten Anzahl), so dass hier weiterer Aufwand kaum mehr zumutbar ist.

Dennoch verläuft dieser ganze Vorgang für mich höchst befriedigend. (Aus den weiter oben genannten Gründen, die ich jetzt nicht wiederhole, damit es nicht zu tragischen Erstickungsanfällen durch Weihrauchschwaden kommt).
Gruß, oa
 
Oben