Schrift
Wiki:Tipp zum Debugging: use Data::Dumper; local $Data::Dumper::Useqq = 1; print Dumper \@var;
[thread]8707[/thread]

Dateistruktur überprüfen



<< >> 3 Einträge, 1 Seite
Gast Gast
 2007-02-01 01:50
#73906 #73906
Ich komme mit folgendem Problem nicht weiter:

Ich möchte die Struktur einer Datei überprüfen. Gewöhnlich sind alle Zeilen nach einem chomp 78 Zeichen lang und beginnen mit einer 4-stelligen Ziffer. Wenn alle Zeilen so sind ist die Integrität in Ordnung. Manchmal ist aber die letzte Zeile eine leere Zeile, oder die letzte Zeile enthält bloss das "Substitude" Zeichen STRG+Z,welches mir in Notepad++ als "SUB" angezeigt wird.

Angefangen habe ich so:

Code: (dl )
1
2
3
4
5
6
7
8
9
open DATEI_S, $dateiname_s
or die "Kann $dateiname_s nicht oeffnen: $!";
while(<DATEI_S>){
chomp ($_);
if ($_=~/^[0-9]{4}/){
if ((length($_)!=78)) {
push (@s_fail, $dateiname_s);
}
}


Dies führte Dazu, dass zwar die Länge der Zeile überprüft wurde aber lediglich nur der Zeilen, die auch mit einer 4-stelligen Ziffer beginnen. So gingen mir kaputte Zeilen, die eben nicht mit einer 4-stelligen Ziffer beginnen, aber durchaus 78 Zeichen lang sein können durch die Lappen.

Ich habe versucht nach zu bessern, bekomme aber nicht alle Kriterien unter einen Hut:

1.) Zeilen müssen immer 78 Zeichen lang sein
2.) Zeilen müssen mit 4-stelliger Zahl beginnen
3.) Leerzeile am Ende der Datei muss ignoriert werden
4.) Das Sub Zeichen (ord() gibt als Zahl die 26 aus) am Ende der Datei muss ignoriert werden.

Dieser Lösungsversuch klappt nicht:

In der if Anweisung soll der Marker $check nur dann gesetzt werden, wenn die Zeile ungleich 78 lang ist, keine leere Zeile ist und das erste Zeichen nicht das Steuerzeichen 26 ist (STRG+Z).
Habe ich die if Anweisung nicht korrekt geschrieben?

Code: (dl )
1
2
3
4
5
6
7
8
 while(<DATEI_S>){
$check=0;
chomp ($_);
$first_char=ord(substr($_,0,1));
if ((length($_)!=78) && (!($_=~/^\s*$/)) && ($first_char!=26) ) {
$check=1;
}
}


Ich erwarte keinen fertigen Code, aber vielleicht kann mir Jemand mitteilen, wie ich das Steuerzeichen 26 ignorieren kann.Vielen Dank im Voraus.
bloonix
 2007-02-01 09:55
#73907 #73907
User since
2005-12-17
1615 Artikel
HausmeisterIn
[Homepage]
user image
Hallo oes,

zunächst einmal ein paar Verbesserungsvorschläge zu deinem Code:

Statt des globalen Filehandles DATEI_S empfehle ich eine private Variable.
Ausserdem erwartet open() mehr als nur zwei Argumente.

open my $fh, '<', $dateiname_s or die ...

Statt auf den Angleoperator in der Schleife zurückzugreifen, empfehle ich
dir eine andere private Variable zu nutzen. Der Angleoperator kann sehr
schnell überschrieben werden.

while (my $line = <$fh>) { ... }

In deinem ersten Codestück wird für jede fehlerhafte Zeile der Dateiname
nach @s_fail gepushed. Das möchtest du bestimmt nicht! Bei 100 fehler-
haften Zeilen hast du in @s_fail 100 Mal den Dateinamen stehen.

Wenn ich es richtig verstanden habe, dann ist dein wichtigstes Kriterium,
dass jede Zeile 78 Zeichen lang ist und mit einer vierstelligen Ziffer
beginnt. Alle anderen Zeilen sind fehlerhaft.

Code: (dl )
1
2
3
4
5
6
7
8
while (my $line = <$fh>) {
  if (length($line) == 78 && $line =~ /^[0-9]{4}/) {
     # diese Zeile ist einwandfrei
  } else
     push @s_fail, $line
        unless ord(substr($_,0,1) == 26;
  }
}

Falls du nur auf fehlerhafte Zeichen in mehreren Dateien prüfen möchtest,
dann wäre es wünschenswert, alle fehlerhaften Zeilen zur späteren
Kontrolle festzuhalten. Sehr informativ wären hierbei wohl der Dateiname,
die Zeilennummer und natürlich die Zeile selbst. Diese Informationen
möchte man bestimmt auch nicht in einem Array behalten, denn je nach
Anzahl der Dateien und Zeilen die geprüft werden und fehlerhaft sind,
könnte das wohl deinen Speicher stark beanspruchen.

Code: (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
open my $out, '>', $file_out or die $!;

for my $dateiname_s (@files) {
  my $counter = 0;

  open my $in, '<', $dateiname_s or do {
     warn "Kann $dateiname_s nicht oeffnen: $!";
     next; # ab zur naechsten Datei
  }

  while (my $line = <$in>){
     ++$counter;
     chomp($line);
     next unless length($line) == 78 && $line =~ /^[0-9]{4}/;
     print $out "$dateiname_s $counter $line\n"
        unless ord(substr($line,0,1) == 26;
  }

  close $in;
}

close $out;


Gruss,
opi\n\n

<!--EDIT|opi|1170317858-->
What is a good module? That's hard to say.
What is good code? That's also hard to say.
One man's Thing of Beauty is another's man's Evil Hack.
Dubu
 2007-02-02 23:03
#73908 #73908
User since
2003-08-04
2145 Artikel
ModeratorIn + EditorIn

user image
@oes: Dein Fehler im zweiten Code ist, dass du das $check=0 innerhalb der Schleife stehen hast; damit wird auch bei einem Fehler dein $check beim Einlesen der nächsten Zeile immer wieder zurückgesetzt. Außerdem solltest du beim Auftreten eines Fehlers das Einlesen der Datei abbrechen, der Rest interessiert dann ja nicht mehr, oder?
Außerdem sollten use strict und use warnings selbstverständlich sein.
Code: (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use strict;
use warnings;

for my $datafilename (@ARGV) {
   open my $datafh, '<', $datafilename or die "cannot open $datafilename: $!\n";
   my $error=0;
   while(my $line = <$datafh>){
       chomp $line;    
       my $first_char = substr($line, 0, 1);
       if (length($line) != 78 && $line !~ /^\s*$/ && $first_char != chr(26)) {
           $error=1;   # setze Fehler-Flag
           last;        # ueberspringe Rest der Datei
       }
   }
   # Evtl. Namen der fehlerhaften Datei ausgeben
   if ($error) {
       print "File $datafilename ist fehlerhaft!\n";
   }
}

Was hier natürlich noch fehlt, ist der Test auf die führenden Ziffern. Außerdem könnte man noch testen, ob die Leerzeile bzw. das chr(26) wirklich nur am Dateiende vorkommt. Der folgende Code sollte dies machen:
Code: (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
use strict;
use warnings;

for my $datafilename (@ARGV) {
   open my $datafh, '<', $datafilename or die "cannot open $datafilename: $!\n";
   my $error=0;
   my $last_line=0;
   while(my $line = <$datafh>){
       chomp $line;

       if ($last_line) {
           # Wenn wir hierher kommen, hatte die letzte Zeile einen Fehler
           $error = 1;
           # Einlesen abbrechen
           last;
       }
       if (length $line != 78 || $line !~ /^\d{4}\D/) {
           # keine 78 Zeichen oder keine 4 Ziffern am Anfang

           if ($line =~ /^\s*$/ || substr($line, 0, 1) == chr(26)) {
               # leer oder chr(26) am Anfang, könnte also die letzte Zeile sein
               $last_line=1;
               # versuchen wir mal, die nächste Zeile zu lesen
               next;
           } else {
               # offensichtlich eine fehlerhafte Zeile
               $error = 1;
               # Einlesen abbrechen
               last;
           }
       }
       # Wenn wir hier hin kommen, ist die eingelesene Zeile korrekt
   }
   # Wenn Fehler, Namen der fehlerhaften Datei ausgeben
   if ($error) {
       print "File $datafilename ist fehlerhaft!\n";
   }
}



@opi: Nur damit den Mitlesern das auch klar ist: open() muss keine drei Argumente haben, es reichen zwei. Aber die Variante mit drei Argumenten ist generell sicherer, weil man den Mode nicht im Dateinamen übergeben kann.


Übrigens, das Zeichen mit dem ASCII-Code 26 hat (hier zumindest) nichts mit "Substitute" zu tun, sondern ist das alte End-of-File-Zeichen unter DOS. Wer heute noch mit  der "MS-DOS-Eingabeaufforderung" arbeitet, der kennt es vielleicht, dass man Strg-Z drücken muss, wenn man das Einlesen von STDIN beenden möchte (unter UNIX: Strg-D). Das Strg-Z sendet ein chr(26) an STDIN (Strg-A sendet das Zeichen mit ASCII-Code 1, Strg-B den ASCII-Code 2, etc.), also ein EOF.
<< >> 3 Einträge, 1 Seite



View all threads created 2007-02-01 01:50.