In Perl-Schleife String zerlegen

Vom einfachen Programm zum fertigen Debian-Paket, Fragen rund um Programmiersprachen, Scripting und Lizenzierung.
Antworten
rannseier
Beiträge: 79
Registriert: 24.09.2007 12:37:30

In Perl-Schleife String zerlegen

Beitrag von rannseier » 27.11.2024 14:57:04

Hallo zusammen,

Ich habe einen langen String, der wie folgt aufgebaut ist:

E007 = Dateninhalt "E" + Länge in Hex 007
V013 = Dateninhalt "V" + Länge in Hex 013
T009 = Dateninhalt "D" + Länge in Hex 009

Beispiele:
E007 020100
V013 8 20 0 0 0 DEF_OUT
T009 0 762060

Die einzelnen Inhalte kann ich so zerlegen (nicht schön, aber funktioniert, immerhin mein erstes Perl Script):

Code: Alles auswählen

        if ($go_string =~ /^T/) {
            $go_sub_string_lenh=substr($go_string,2,2);
            $go_sub_string_len=hex($go_sub_string_lenh);
            $go_basic1_string=substr($go_string,5,$go_sub_string_len);
            $go_string=substr($go_string,5+$go_sub_string_len);
        }
Leider ist die Reihenfolge nicht festgelegt und manche Felder können zweimal auftreten.

Schritt 1: Wie baue ich eine Schleife, die so lange läuft, bis kein Datenfeld mehr übrig ist (sprich: bis $go_string leer ist)?

juribel
Beiträge: 334
Registriert: 20.06.2023 10:17:01

Re: In Perl-Schleife String zerlegen

Beitrag von juribel » 27.11.2024 16:55:13

Ist der Eingabestring wirklich nur ein einziger String ohne \n Zeilenumbrüche? Sind die einzelnen Felder durch \t Tab-Zeichen voneinander getrennt? Kommt der String aus einer Datei?

Ausserdem solltest du schon auch beschreiben, was genau für ein Ergebnis du erwartest.

juribel
Beiträge: 334
Registriert: 20.06.2023 10:17:01

Re: In Perl-Schleife String zerlegen

Beitrag von juribel » 27.11.2024 16:59:18

Was meinst du mit /^T/ ?. Erwartest du am Anfang des Strings auf jeden Fall einen Grossbuchstaben "T" ?

rannseier
Beiträge: 79
Registriert: 24.09.2007 12:37:30

Re: In Perl-Schleife String zerlegen

Beitrag von rannseier » 27.11.2024 17:07:19

Format des Strings:

(Rest-)Zeile komplett mit Substrings, alles mit Leerzeichen getrennt.

Beispiel:

Code: Alles auswählen

E007 020100 V013 8 20 0 0 0 DEF_OUT T009 0 762060 T009 0 762360
Die Reihenfolge kann varieren, nicht jedes Feld muss vorkommen, manche können zwei mal vorkommen. Die Reihenfolge der Felder, die mehrfach vorkommen, ist egal.

Ziel:

Code: Alles auswählen

$str_e=020100
$str_v=8 20 0 0 0 DEF_OUT 
$str_t1=0 762060 
$str_t2=0 762360
Idee = 1. Substring zerlegen und z.B. in $str_e kopieren, Original-String um $str_e kürzen. Solange wiederholen, bis String leer ist.

Benutzeravatar
heisenberg
Beiträge: 4123
Registriert: 04.06.2015 01:17:27
Lizenz eigener Beiträge: MIT Lizenz

Re: In Perl-Schleife String zerlegen

Beitrag von heisenberg » 27.11.2024 18:34:35

Ich würde das ungefähr so umsetzen:

Das Programm braucht das installierte Paket: Debianlibstring-trim-perl

Code: Alles auswählen

#!/usr/bin/perl

use warnings;
use strict;
use Data::Dumper;
use String::Trim;

# Das nächste Feld aus einer übergebenen Zeichenkette holen

sub get_next_field {

        my $string=$_[0];
        $string or return;

        my ($id,$length,$rest) = $string =~ /^([A-Z])([0-9]+)(.*)/;
        my $data =  substr($rest,1,hex($length)); # offset [1] weil offset [0] ein Leerzeichen ist.
           $rest = substr($rest,$length);
        return $id, $length, $rest, $data;

}

# Eine übergebene Zeichenkette (Zeile) feldweise einlesen

sub parse_string {

        my $string = $_[0];
        my ($id, $length, $rest, $data, $result);

	# solange über die Zeichenkette iterieren, so lange noch
	# neue Felder erkannt werden
	
        while (1) {
                ($id, $length, $rest, $data) = get_next_field($string);
                $id or last;
                push @$result, { id=>$id , data=>trim($data) };
                $string=$rest;
        }
        return $result;

}

sub main {
        my ($string,$fh,$result);

        open($fh,"<","data.txt");

        # Datei zeilenweise verarbeiten
        while($string=<$fh>) {
                chomp($string);
                print "***** NEW LINE *****\n";
                print("DATA: ".$string."\n");
                $result=parse_string($string);
                print Dumper($result);
        }
        # Demo: Zugriff auf die Felder
        foreach my $e (@$result) {
                print $e->{"id"} ." = ". $e->{"data"}."\n";
        }
        close($fh);
}

main();
Sieht dann in der Ausgabe so aus:

Code: Alles auswählen

$ perl parse.pl
***** NEW LINE *****
DATA: E007 020100 V013 8 20 0 0 0 DEF_OUT T009 0 762060 T009 0 762360
$VAR1 = [
          {
            'id' => 'E',
            'data' => '020100'
          },
          {
            'id' => 'V',
            'data' => '8 20 0 0 0 DEF_OUT'
          },
          {
            'data' => '0 762060',
            'id' => 'T'
          },
          {
            'data' => '0 762360',
            'id' => 'T'
          }
        ];
E = 020100
V = 8 20 0 0 0 DEF_OUT
T = 0 762060
T = 0 762360
Du hast dann also ein Array aus Hashes, was jeweils den zugehörigen Datenwert enthält.
Zuletzt geändert von heisenberg am 27.11.2024 22:26:45, insgesamt 4-mal geändert.

juribel
Beiträge: 334
Registriert: 20.06.2023 10:17:01

Re: In Perl-Schleife String zerlegen

Beitrag von juribel » 27.11.2024 20:25:14

Ich hätte auch was:

Code: Alles auswählen

  use warnings;
  use strict;

  my $string = 'E007 020100 V013 8 20 0 0 0 DEF_OUT T009 0 762060 T009 0 762360';
  print "-> $string\n";

# Vor jedem Feld-Marker ein "|" einfügen

  $string =~ s/\b([evt][0-9a-f]{3})\b/\|$1/ig;

# Neuen String anhand der "|" splitten und in ein Array schreiben

  my @array = split /\|/, $string;

# Das Array anhand des ersten Buchstabens in drei Ziel-Arrays verteilen

  my @arrayE;
  my @arrayV;
  my @arrayT;

  foreach my $teilstring ( @array ) {
    push @arrayE, $teilstring if ( $teilstring =~ /^E/ );
    push @arrayV, $teilstring if ( $teilstring =~ /^V/ );
    push @arrayT, $teilstring if ( $teilstring =~ /^T/ );
  }

# Die drei Ziel-Arrays ausgeben

  print "-> Array E\n";
  foreach my $field ( @arrayE ) {
    print "$field\n";
  }

  print "-> Array V\n";
  foreach my $field ( @arrayV ) {
    print "$field\n";
  }

  print "-> Array T\n";
  foreach my $field ( @arrayT ) {
    print "$field\n";
  }
Die Print-Ausgabe erzeugt:

Code: Alles auswählen

-> E007 020100 V013 8 20 0 0 0 DEF_OUT T009 0 762060 T009 0 762360
-> Array E
E007 020100 
-> Array V
V013 8 20 0 0 0 DEF_OUT 
-> Array T
T009 0 762060 
T009 0 762360
Anstelle der print-Anweisungen am Ende des Programms kann man mit den drei Arrays natürlich alles Mögliche anstellen.

Besorge dir das Buch "Einführung in Perl" von O'Reilly. Es vermittelt die Grundlagen sehr gut, und entgegen der Behauptung im Buchtitel hab ich es selbst nach > 30 Jahren Perl-Programmierung immer noch nicht ausgereizt. Ich bevorzuge immer noch die Babysprache, damit ich auch nächstes Jahr noch verstehe, was ich da eigentlich gebaut habe, und Dokumentation (je mehr, desto besser), damit ich auch weiss warum so und nicht anders.

Benutze die Anweisung "use strict" am Anfang deines Programms, und vielleicht auch "use warnings".

Hilfreich ist auch, das Programm sehr oft zum Test aufzurufen, auch schon nach einer bis wenigen Zeilen Änderung.

Benutzeravatar
heisenberg
Beiträge: 4123
Registriert: 04.06.2015 01:17:27
Lizenz eigener Beiträge: MIT Lizenz

Re: In Perl-Schleife String zerlegen

Beitrag von heisenberg » 27.11.2024 21:12:47

juribel hat geschrieben:

Code: Alles auswählen

...
# Vor jedem Feld-Marker ein "|" einfügen

$string =~ s/\b([evt][0-9a-f]{3})\b/\|$1/ig;
...
Damit gehst Du davon aus, das dieses Muster nicht in den Daten vorkommt. Wäre die Frage, ob man das tatsächlich ausschließen kann.

Das mit den verschiedenen Arrays ist allerdings schön einfach zugreifbar.

Hier auch nochmal eine Variante, die die Werte in einzelne Arrays für E, V und T speichert:

Code: Alles auswählen

#!/usr/bin/perl

use warnings;
use strict;
use Data::Dumper;
use String::Trim;

sub get_next_field {

        my $string=$_[0];
        $string or return;

        my ($id,$length,$rest) = $string =~ /([A-Z])([0-9]+)(.*)/;
        my $data =  substr($rest,1,hex($length));
           $rest = substr($rest,$length);
        return $id, $length, $data, $rest;

}

sub parse_string {

        my $string = $_[0];
        my ($id, $length, $rest, $data, $result);
        $result->{"E"} = [];
        $result->{"V"} = [];
        $result->{"T"} = [];

        while (1) {
                ($id, $length, $data, $rest) = get_next_field($string);
                $id or last;
                push @{$result->{$id}}, trim($data);
                $string = $rest;
        }
        return $result;

}

sub main {
        my ($string,$fh,$result);

        open($fh,"<","data.txt");
        while( $string = <$fh> ) {
                chomp($string);
                print "***** NEW LINE *****\n";
                print("DATA: ".$string."\n");
                $result = parse_string($string);
                print Dumper($result);
        }
        # Ausgabe der ermittelten Felder
        print "E => ".join(",",@{$result->{"E"}})."\n";
        print "V => ".join(",",@{$result->{"V"}})."\n";
        print "T => ".join(",",@{$result->{"T"}})."\n";
        close($fh);

}

main();
Ausgabe:

Code: Alles auswählen

$ perl parse.pl
***** NEW LINE *****
DATA: E007 020100 V013 8 20 0 0 0 DEF_OUT T009 0 762060 T009 0 762360
$VAR1 = {
          'E' => [
                   '020100'
                 ],
          'T' => [
                   '0 762060',
                   '0 762360'
                 ],
          'V' => [
                   '8 20 0 0 0 DEF_OUT'
                 ]
        };
E => 020100
V => 8 20 0 0 0 DEF_OUT
T => 0 762060,0 762360
Anmerkung

Ich nutze lieber Referenzen auf Datenstrukturen (Arrays, Hashes oder Kombinationen davon), statt direkt damit zu arbeiten, weil Referenzen Einzelwerte sind, die sich leichter als Gesamtdatenstruktur übergeben lassen.
Zuletzt geändert von heisenberg am 27.11.2024 22:22:30, insgesamt 1-mal geändert.

juribel
Beiträge: 334
Registriert: 20.06.2023 10:17:01

Re: In Perl-Schleife String zerlegen

Beitrag von juribel » 27.11.2024 22:02:26

Ja, ich bin davon ausgegangen, dass die Feldmarker immer mit e, v oder t anfangen, dass dahinter immer drei hexadezimale Ziffern stehen, und dass der String nur aus Zahlen, Buchstaben und Leerzeichen besteht. Bei allem ignoriere ich Gross-/Kleinschreibung. Aber wenn die Daten noch Anderes erfordern, läüsst sich das ja auch leicht realisieren.

Mit Referenzen und verschachtelten Datenstrukturen hab ich zwar auch schon gearbeitet, aber als Liebhaber von Babysprache und einfachen Strukturen muss ich bei so etwas auch nach so vielen Jahren Perl immer noch erstmal tief Luft holen 8O

rannseier
Beiträge: 79
Registriert: 24.09.2007 12:37:30

Re: In Perl-Schleife String zerlegen

Beitrag von rannseier » 27.11.2024 22:26:49

Die Feldmarker sind nicht abschliessend, da können als noch weitere im String sein oder mal zukünftig dazukommen.

Benutzeravatar
heisenberg
Beiträge: 4123
Registriert: 04.06.2015 01:17:27
Lizenz eigener Beiträge: MIT Lizenz

Re: In Perl-Schleife String zerlegen

Beitrag von heisenberg » 27.11.2024 22:50:34

juribel hat geschrieben: ↑ zum Beitrag ↑
27.11.2024 22:02:26
Ja, ich bin davon ausgegangen, dass die Feldmarker immer mit e, v oder t anfangen, dass dahinter immer drei hexadezimale Ziffern stehen, und dass der String nur aus Zahlen, Buchstaben und Leerzeichen besteht. Bei allem ignoriere ich Gross-/Kleinschreibung. Aber wenn die Daten noch Anderes erfordern, läüsst sich das ja auch leicht realisieren.
Bei einem komplexen Datenformat - was das hier vermutlich ist - würde ich da lieber auf Nummer sicher gehen. Wenn da Längenangaben drin sind, dann würde ich die auch nutzen. Sonst hast Du da irgendwelche Datenfehler drin und merkst es im schlimmsten Fall noch nicht mal.

@rannseier: Hast Du auch mal ein paar umfangreichere Testdaten und wenn die nicht vertraulich sind, kannst Du die mal posten?
Zuletzt geändert von heisenberg am 27.11.2024 23:01:09, insgesamt 1-mal geändert.

juribel
Beiträge: 334
Registriert: 20.06.2023 10:17:01

Re: In Perl-Schleife String zerlegen

Beitrag von juribel » 27.11.2024 23:00:22

@rannseier: Meinst du noch andere Feldmarker-Buchstaben, also Feldtypen? Wär ja wohl nicht so das Problem, solange sie eindeutig sind bzw. nicht andere Inhalte von Teilstrings das Kriterium zum Erkennen der Feldmarker (einer der Feldtyp-Buchstaben gefolgt von 3 Zeichen [0-9A-F] gefolgt von einem Leerzeichen) erfüllen. In so einem Fall müsste dann vielleicht doch auf die Längenangabe zurückgegriffen werden.

@heisenberg: So besonders komplex finde ich das Datenformat bis jetzt noch nicht, da hab ich schon Schlimmeres gesehen, Freitext mit verborgenen unabsichtlich mit kopierten Formatierungen, verschiedenen Schreibweisen und Abkürzungen für dieselben Dinge, und daraus ein vollständig gehypertextes Lexikon machen... Aber wie gesagt, mit der Längenangabe könntest du Recht haben.

Benutzeravatar
Meillo
Moderator
Beiträge: 9241
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: In Perl-Schleife String zerlegen

Beitrag von Meillo » 28.11.2024 07:16:55

rannseier hat geschrieben: ↑ zum Beitrag ↑
27.11.2024 14:57:04
Die einzelnen Inhalte kann ich so zerlegen (nicht schön, aber funktioniert, immerhin mein erstes Perl Script):

Code: Alles auswählen

        if ($go_string =~ /^T/) {
            $go_sub_string_lenh=substr($go_string,2,2);
            $go_sub_string_len=hex($go_sub_string_lenh);
            $go_basic1_string=substr($go_string,5,$go_sub_string_len);
            $go_string=substr($go_string,5+$go_sub_string_len);
        }
Das sieht gar nicht so schlecht aus, finde ich.
rannseier hat geschrieben: ↑ zum Beitrag ↑
27.11.2024 14:57:04
Leider ist die Reihenfolge nicht festgelegt und manche Felder können zweimal auftreten.

Schritt 1: Wie baue ich eine Schleife, die so lange läuft, bis kein Datenfeld mehr übrig ist (sprich: bis $go_string leer ist)?
Was daran funktioniert denn noch nicht? Eigentlich ist doch alles vorhanden. Du brauchst bloss noch eine Schleife drumrum, die so lange laeuft, bis $go_string leer ist:

Code: Alles auswählen

while ($go_string != "") {
	...
}
Use ed once in a while!

juribel
Beiträge: 334
Registriert: 20.06.2023 10:17:01

Re: In Perl-Schleife String zerlegen

Beitrag von juribel » 28.11.2024 10:28:57

Kleiner Tipp am Rande zur Lesbarkeit, besonders beim ersten Perl-Projekt (bitte nicht als überheblich verstehen):

Leerzeichen benutzen! zum Beispiel so:

Code: Alles auswählen

  if ( $go_string =~ /^T/ ) {
            $go_sub_string_lenh = substr ( $go_string, 2, 2 );
            $go_sub_string_len = hex ( $go_sub_string_lenh );
            $go_basic1_string = substr ( $go_string, 5, $go_sub_string_len );
            $go_string = substr ( $go_string, 5 + $go_sub_string_len );
        }
Also: um Klammern herum, hinter den Kommata bei Listen, an Zuweisungszeichen (Gleichheitszeichen).

In spätestens zwei Wochen wirst du dir selber dafür danken, denn durch kompakten Spagetti blickt man am Ende selber nicht mehr durch. Es kommt weniger darauf an, das der Computer das lesen kann (der meckert sowieso wenn etwas nicht stimmt), sondern dass DU es noch lesen kannst. Leider findet man im Netz meistens Spagetti-Code, das ist kein gutes Vorbild.

Den Tipp von Meillo würde ich etwas modifizieren. statt

Code: Alles auswählen

while ($go_string != "") {
würde ich schreiben

Code: Alles auswählen

while ( $go_string =~ \w ) {
also den String solange bearbeiten, wie er noch Wortzeichen (Buchstaben, Zahlen) enthält. Es ist ja nicht unbedingt gegeben, dass der String nach den erfolgten Aktionen gar nichts mehr enthält, auch keine vielleicht übrig gebliebenen Leerzeichen.

Antworten