Der Regex Megathread

Begonnen von RichardCZ, 06 Mai 2020, 12:18:14

Vorheriges Thema - Nächstes Thema

RichardCZ

Es gibt neben Perl noch einen Formalismus, der sehr wichtig ist und der mit Perl eigentlich nichts zu tun (*) hat: Reguläre Ausdrücke.

Die beherrschen die Meisten hier auch nicht...  ;) (woah die Menge tobt)
...oder sind einfach nur zu faul bequem sie richtig zu machen. (die Menge tobt noch mehr obwohl ich ja so diplomatisch war)

Dieser Thread ist somit überfällig!

Praktisch jeder reguläre Ausdruck in FHEM ist falsch. Das mag sich jetzt deprimierend anhören, ist aber so.
Zur Aufmunterung schicke ich also gleich hinterher: Es ist verdammt schwer einen regulären Ausdruck tiptop richtig hinzubekommen.

Mein Ziel mit diesem Thread wäre es also den Regex-FHEM-Standard von "komplett daneben" zu "akzeptabel" zu hieven.

(*) Die Aussage stammt von Larry Wall persönlich




Kopf hoch, wenn einer ne Chance hat reguläre Ausdrücke zu knechten, dann sind das Perler mit ihrer Fähigkeit wirre Zeichenfolgen zu parsen. ;-)
Diese Aussage ist noch nicht einmal so übertrieben, denn wer sich z.B. ein wenig in der Python oder Java Community rumtreibt, wird nicht
selten die dort vorherrschende Meinung zu regulären Ausdrücken hören: "braucht man in 99% der Fälle nicht." und dann werden oft Turnübungen
veranstaltet (äquivalent zu substr, index, etc.) um ja Regexen zu vermeiden.

Fishermans Friend halt. ¯\_(ツ)_/¯

Hier ist mal so ein regulärer Ausdruck wahlfrei aus FHEM gefischt. Er soll ein Datum Jahr-Monat-Tag matchen, wobei die Jahr Angabe optional ist.

m/^((\d{4})-)?([01]\d)-([0-3]\d)$/;

Fehler 1: \d statt [0-9]

Klar, es ist bequemer \d zu schreiben als [0-9], aber \d matcht unter UTF-8 (und damit haben wir es oft zu tun, oder bekommen es damit zu tun so sicher wie das Amen in der Kirche) halt noch eine Menge anderen Sotter:

ZERO:  0٠۰߀०০੦૦୦௦౦೦൦๐໐0
ONE:   1١۱߁१১੧૧୧௧౧೧൧๑໑1
TWO:   2٢۲߂२২੨૨୨௨౨೨൨๒໒2
THREE: 3٣۳߃३৩੩૩୩௩౩೩൩๓໓3
FOUR:  4٤۴߄४৪੪૪୪௪౪೪൪๔໔4
FIVE:  5٥۵߅५৫੫૫୫௫౫೫൫๕໕5
SIX:   6٦۶߆६৬੬૬୬௬౬೬൬๖໖6
SEVEN: 7٧۷߇७৭੭૭୭௭౭೭൭๗໗7
EIGHT: 8٨۸߈८৮੮૮୮௮౮೮൮๘໘8
NINE:  9٩۹߉९৯੯૯୯௯౯೯൯๙໙9


siehe https://stackoverflow.com/questions/890686/should-i-use-d-or-0-9-to-match-digits-in-a-perl-regex

Fehler 2: Implizit [0-9] -> zu locker

siehe https://stackoverflow.com/questions/2137929/how-can-i-use-a-regular-expression-to-validate-month-input

(Interessant übrigens, wie auch in den Kommentaren durchscheint, dass Regexen für Validierung ein overkill sind.
Ich bin anderer Ansicht. Einen guten Teil der Validierung bekommt man bei guten Regexen "en passant" mit dem Matching.
Im Vorbeigehen also.)

Regex für Monat: (0[1-9]|1[012])
Regex für Tag: (0[1-9]|[12][0-9]|3[01])

Natürlich ist es wichtig den Regexen Begrenzungen mit auf den Weg zu geben (^ und $ - eigenlich \A und \z) und ja,
man könnte darüber nachdenken häufig benutzte reguläre Ausdrücke in Konstanten abzulegen zur Wiederverwendung

Readonly my $DAY_RX => qr{(0[1-9]|[12][0-9]|3[01])};

zum Beispiel. Und irgendwann dann stolpert man über https://metacpan.org/pod/Regexp::Common

Fehler 3: Die Jahreszahl

Das matcht 0001, was keine korrekte Jahreszahl ist. Nun könnte man sich z.B. auf 1000 to 2999 einigen

^[12][0-9]{3}$

oder 1900-2099

^(?:19|20)[0-9]{2}$ (Ja, wo man nur Alternierungen braucht sollte die Gruppe "non-capturing" sein)

oder man abstrahiert vom Y2K-Problem und

^[1-9][0-9]{3,}$

Alles Definitions-/Konventionsfrage, aber zumindest hat man dann nicht "mal was schnell dahingesch*", sondern sich was dabei gedacht.

Versäumnis 1: Lesbarkeit

m{...}xms

Leerzeichen, Kommentare... Hatten wir schon

Versäumnis 2: Named Captures

Seit Perl 5.10

m{\A
  ((?<year> (?:19|20)[0-9]{2})-)?    # here we explain to poor unenlightened people what we meant
  (?<month> (0[1-9]|1[012]))         # this leaves us space for some philosophical tractate
  -                                  # now we have enough space (and cognitive capacity) for other delimiters
  (?<day> (0[1-9]|[12][0-9]|3[01]))  # and here a little rant maybe?
  \z}xms


statt also $1, $2 rumzuzählen (und was passiert eigentlich wenn ne capture optional ist?) und die Zahlen zu vertauschen wenn was an der Regex geändert wird, kann man nun einfach
$+{year}, $+{month}, $+{day} rausfischen. (Wobei $+{year} u.U. undef ist.)

Vergleicht man das mit dem ursprünglichen Code, wird ganz klar warum bei vielen Programmierern die Einstellung "gut genug" vorherrscht.
Aber ist diese Einstellung "gut genug"?

PBP widmet regulären Ausdrücken das gesamte Kapitel 12 (235-271),
https://perldoc.perl.org/perlre.html ist einer der dicksten Doku-Oschis, den Perl zu bieten hat

Das Pferd ist nicht leicht zu zügeln, aber wenn man das (zumindest teilweise) schafft, dann pflügt euch das den Acker um
in einer Art und Weise, die ihr nie für möglich gehalten hättet.

positive & negative lookbehinds & lookaheads - sage ich da nur. Was könnte man alles an hanebüchenen Code knicken, wenn man nur die Tools hätte bzw. beherrschen würde.
Witty House Infrastructure Processor (WHIP) is a modern and
comprehensive full-stack smart home framework for the 21st century.

RichardCZ

Ein anderer Favorit von mir ist

my @a = split( "[ \t][ \t]*", $def );

habe ich schon erzählt davon - oder? Es gibt nicht viel an dieser Zeile was nicht falsch wäre und dennoch bringt die letzte FHEM-Volkszählung knapp 800 solcher (und ähnlicher) Zeilen hervor. Also bei

my @args = split m{\s+}xms, $def;

hätte ich jetzt Nichts gesagt und würde das einfach mal als Ersatz empfehlen (natürlich muss man dann $a[0] -> $args[0] im Code machen)

Aber wichtig wäre zu verstehen warum die ganz o.g. Zeile der Antichrist ist.




Ich habe jetzt keine historischen Untersuchungen angestellt, aber es scheint, als wäre "[ \t][ \t]*" eine 'Verbesserung' von  " "  (wobei mir langsam die Anführungszeichen ausgehen):

my @a = split( " ", $param );

Irgendwann wollte man dann wohl noch ein Tab matchen, irgendwann dann noch "ein oder mehrere". Tadaa! Und dann wurde das halt kopiert.




Dabei ist vermutlich alles was man möchte "einfach" durch "Leerzeichen" getrennte Argumente/Parameter aufzutrennen.
Wenn wir unseren Regex-Hut aufsetzen, reden wir nicht von Leerzeichen, sondern von "Whitespace". Und von der Sorte gibt es viel.

Siehe https://perldoc.perl.org/perlrecharclass.html - oder auch nicht.

Space, Tabulator, Linefeed, Carriage Return, EN Quad, EM Quad, Paragraph Separator, und noch ungefähr 20 weitere.
Die meisten davon können als sog. Delimiter verwendet werden um zwischen Argumenten zu unterscheiden. Also verwenden wir doch das
was alle diese Delimiter matcht: \s

Und wenn "ein oder mehrere" gefragt sind, dann ist das immer +, nicht * (0 oder mehrere).

also es gilt immer: xx* = x+ = x*x

und man muss ja nicht unnötig komplexere Regexen schreiben als notwendig. Die werden auch so schon heftig genug manchmal. Folglich:

[ \t][ \t]* = [ \t]+

Naja und wenn man mehr matchen will (ja, die Regex wird an der Stelle lockerer, aber in diesem speziellen Fall eben "generischer"), dann eben

[ \t]+ ⊂ \s+ (soll heißen "ist/matcht Teilmenge von")

Also:

my @a = split( " ", $def );
->
my @a = split( "[ \t][ \t]*", $def );
->
my @a = split( "[ \t]+", $def );
->
my @args = split m{\s+}xms, $def;

* es ist kürzer (& lesbarer), mächtiger (& richtiger)
* es ist schneller, wenngleich nur ein "paar Nanosekunden"
* perlcritic lässt euch bis -2 in Ruhe (für diejenigen, die jetzt meinen xms sei für die kurze Regex overkill)
Witty House Infrastructure Processor (WHIP) is a modern and
comprehensive full-stack smart home framework for the 21st century.

amenomade

Naja. Die Nutzung von Regex ist immer abhängig vom Kontext. Wenn man weiss, woher die Daten kommen, braucht man ggf. nicht UTF8 in einem Datum zu berücksichtigen.

Genauso soll die Komplexität der Regex (und dazugehörige Lesbarkeit des Codes - Du sagst es selbst im 2. Post: "man muss ja nicht unnötig komplexere Regexen schreiben als notwendig") abhängig vom Kontext bleiben.

Die Regex für das Datum kann man z.B. noch komplexer machen: deine Regex matcht auch den 30 Februar. Zu locker? Und was mit dem 29 Februar abhängig von Schaltjahr? Und so weiter.  Aber ist das wirklich nötig ??? ??
Pi 3B, Alexa, CUL868+Selbstbau 1/2λ-Dipol-Antenne, USB Optolink / Vitotronic, Debmatic und HM / HmIP Komponenten, Rademacher Duofern Jalousien, Fritz!Dect Thermostaten, Proteus

RichardCZ

#3
Zitat von: amenomade am 06 Mai 2020, 16:50:29
Die Regex für das Datum kann man z.B. noch komplexer machen: deine Regex matcht auch den 30 Februar. Zu locker? Und was mit dem 29 Februar abhängig von Schaltjahr? Und so weiter.  Aber ist das wirklich nötig ??? ??

Gutes Beispiel. Genau das ist in einer Regex nicht mehr nötig und ich bin sicher nicht auf dem
dogmatischen Dampfer "alles in die Regex", 

Reguläre Ausdrücke sind (normalerweise, die in Perl schon) nicht Turing-vollständig
und können daher prinzipiell nicht weit in die Semantik (Schaltjahre etc.) reingreifen.
Aber sie können als Filter dienen wie weit man nach so einer Regex eben Programmcode zur weiteren
Validierung bemühen muss.

Es macht schon einen Unterschied ob ich nur Februar <= 29 checke, oder mich u.a. auch mit dem
39.19.0001 herumschlagen muss.

ZitatWenn man weiss, woher die Daten kommen, braucht man ggf. nicht UTF8 in einem Datum zu berücksichtigen.

Ich erlaube mir den leicht stichelnden Einwurf, dass man bei Betrachtung der Quelltexte eher den Eindruck gewinnt,
der Betreffende weiß sicher nicht woher die Daten kommen, bzw. macht sich keine Gedanken. Zumindest hielte ich
das Argument "das hat alles so seine Richtigkeit - ist gewollt" für ... gewagt.

Erfahrung bedeutet "den richtigen Mittelweg finden". Leute die von einem Extrem (.*) ins Andere "kann man z.B. noch komplexer machen:"
schwingen haben nach meiner Erfahrung Probleme auf dem Mittelweg zu bleiben.  Jugendlicher Leichtsinn halt. ;)

Reguläre Ausdrücke sind ein extrem gut ausgestatterer Werkzeugkasten. Es ist schade mit anzusehen wie daraus immer
wieder nur der Hammer herausgeholt wird - auch in Fällen wo ein Korkenzieher besser gewesen wäre. Prost!

ZitatKontext ... Du sagst es selbst im 2. Post: "man muss ja nicht unnötig komplexere Regexen schreiben als notwendig")

Wenn man sich den Kontext anschaut wo ich das sage, dann ist das auf die Situation

"Habe ich zwei äquivalente reguläre Ausdrücke, nehme ich den einfacheren davon."  ([ \t][ \t]* = [ \t]+)

Witty House Infrastructure Processor (WHIP) is a modern and
comprehensive full-stack smart home framework for the 21st century.

zap

#4
Jeffrey Friedl: "Mastering regular expressions". Lesen, verstehen und anwenden.
😎
2xCCU3, Fenster, Rollläden, Themostate, Stromzähler, Steckdosen ...)
Entwicklung: FHEM auf AMD NUC (Ubuntu)
Produktiv inzwischen auf Home Assistant gewechselt.
Maintainer: FULLY, Meteohub, HMCCU, AndroidDB

RichardCZ

Zitat von: zap am 06 Mai 2020, 18:53:20
"Mastering regular expressions". Lesen, verstehen und anwenden.

Richtig! (Jeffrey Friedl

https://doc.lagout.org/programmation/Regular%20Expressions/

Es ist ja nicht so, dass es einen Mangel an Literatur/Dokumentation gäbe. Vielleicht ist das Problem eher, dass es zuviel davon gibt.
Ich dachte mir, ich picke wie üblich FHEM-Spezifika heraus und diskutiere die. Soll aber nicht in einen Monolog ausarten - wenn
jemand konkrete Fragen zu einer Regex hat, ist hier durchaus der Platz dafür.

https://www.heise.de/hintergrund/Regex-Katastrophen-Wenn-regulaere-Ausdruecke-Software-lahmlegen-4702727.html

;-)

Cloudflare SuperGAU.
Witty House Infrastructure Processor (WHIP) is a modern and
comprehensive full-stack smart home framework for the 21st century.

RichardCZ

Bereits vor 20 Jahren hat Ovid (Illustres Mitglied der Perl Community) auf perlmonks.org einen Artikel mit diesem Titel geschrieben: https://www.perlmonks.org/?node_id=24640

Im Grunde ging es darum man solle .* vermeiden. Wie üblich in Diskussionsforen  ;) - folgte darauf mal mehr, mal weniger Weißes Rauschen, aber Grundaussage bleibt bis heute valide bestehen:

.* ist schlecht. Meistens. Fast Immer.

Was bedeutet .* ? "Matche ein beliebiges Zeichen 0 oder mehrmals."  Bevor man eigentlich im Detail begreiflich machen kann wie schlecht dieses Konstrukt ist, muss man etwas über Gier und Backtracking erzählen.

Quantoren wie '+' und '*' sind gierig (greedy). Sie werden immer versuchen so viele Zeichen zu matchen wie es geht. Also angenommen man hat:

* einen string 'ABCDEGABEDGABCDEFGH'
* die Regex '.*AB'

dann matcht in diesem Fall .* den string 'ABCDEGABEDG' und nicht etwa den leeren String oder 'ABCDEG'.
Das kann durchaus das sein was man möchte, aber das schlägt leider sehr schnell in einen Performance-Albtraum um, wenn man z.B. sowas macht:

if ( $event =~ /(.*: +.*: +.*)+/ ) # ja, FHEM code

Denn jetzt wird jeder der gierigen .* versuchen einen möglichst langen String zu matchen.
Der Erste wird bis zum letzten Doppelpunkt mit folgendem Space matchen und happy sein.
Allerdings stellt dann der zweite fest (vereinfacht gesagt), dass der reguläre Ausdruck jetzt nicht mehr erfüllbar ist.
Die Regex-Engine vollführt nun Backtracking: Der eigentlich schon verdaute String wird wieder Zeichen für Zeichen
ausgespuckt bis der zweite .* genug hat um matchen zu können...
... und dann kommt das gleiche Spiel mit dem dritten, und vierten und ... (man sieht den + in der äußeren Klammer?)

Es wird schon funktionieren, aber die Laufzeit ist unter aller Sau.

Also wenn schon .*, dann non-greedy => .*? oder doch .+? oder doch [^:]+ (im obigen Beispiel)




Oder betrachten wir mal die Sache von der anderen Seite:

TcpServerUtils.pm:        if ( $cp =~ m,^(.*)/(.*?), && !-d $1 && !mkdir($1) ) {

Der Match funktioniert (vermutlich) nur deswegen, weil der zweite .* eben non-greedy ist. Ich frage mich
ob man das Fragezeichen von Anfang an so "richtig" drinhatte oder erst nach ein wenig Leidensweg.  ;)
Aber meine Hand ins Feuer legen, dass es funktioniert - würde ich nicht.




Und dann gibt es natürlich noch die Fälle wo .* einfach nur toter Code ist:

$cmd =~ m/^on.*/i

Was kann das, was

$cmd =~ m/^on/i

nicht könnte? Also außer Prozessorzyklen auffressen...




Also zusammengefasst und unterstrichen: Jedesmal wenn es euch gelüstet .* zu schreiben, sollten sich euch fortan die Nackenhaare aufstellen und statt .* sollte was besseres zum Zuge kommen.
Witty House Infrastructure Processor (WHIP) is a modern and
comprehensive full-stack smart home framework for the 21st century.

amenomade

#7
"Gutes" Beispiel heute im Forum gesehen, das mehrere Aussagen von dir illustriert:
Aus einem Text
12:07:43|0|noraw|1","003":"2|sur001|403470039|128|22|HUMB|86.7|2020-05-06
12:07:43|0|noraw|1","004":"3|sur001|403470039|128|22|SOILT|14.60|2020-05-06
12:07:43|0|noraw|1","005":"4|sur001|403470039|128|22|UV|32.27|2020-05-06
möchtet der TE die dezimale Zahl, die nach HUMB| steht extrahieren, also hier 86.7

Ihm wurde zuerst folgende Regex vorgeschlagen (ich vermute, der Autor wollte dich herausfordern ):
/^.*HUMB\|([0-9]+(\.[0-9])?)\|[0-9]{4}?.*/
187 steps. Backtracking ist mit "^.*" vom Anfang an angesagt. Je länger der Text nach dem gesuchten Wert, desto mehr steps.

Und warum ^ wenn sowieso danach .* kommt...? "Suche vom Anfang an, aber bitte mit so vielen Zeichen wie möglich danach , die mich nicht interessieren"

Und warum .* ganz am Ende? Wobei wir hier nur von einem einzigen zusätzlichen step reden.

Schon ohne ^.* am Anfang fällt man auf 16 steps.

Eigentlich reicht:
/HUMB\|([0-9.]+)/
9 steps, egal was danach steht. (OK, man kontrolliert damit nicht, dass nach der Zahl ein Pipe und 4 Ziffern kommen, und man kontrolliert auch nicht, dass in dem Wert der dezimale Punkt nicht mehrmals auftaucht. Zu locker? Abhängig vom Bedarf?)

Pi 3B, Alexa, CUL868+Selbstbau 1/2λ-Dipol-Antenne, USB Optolink / Vitotronic, Debmatic und HM / HmIP Komponenten, Rademacher Duofern Jalousien, Fritz!Dect Thermostaten, Proteus

Wzut

#8
Zitat von: RichardCZ am 06 Mai 2020, 19:32:19
wenn jemand konkrete Fragen zu einer Regex hat, ist hier durchaus der Platz dafür.
na sicher doch , als jemand der schon immer mit Regex auf Kriegsfuß steht und davon lebt die passende bei anderen abschreiben zu können :

a. meine Liebling Saubermach   =~ s/ //g; müsste ich zukünftig besser als =~ s/\s//g; schreiben ?

b. in 14_CUL_MAX steckt eine Regex die ich gerne loswerden möchte, aber natürlich keinen Plan habe wie
Es geht darum eine Nachricht in ihre Bestandteile zu zerlegen, bekannte Vorbedingung : Sie muß mit dem Buchstaben Z beginnen und mindestens 21 Zeichen lang sein. Prüfsumme gibt es keine, ausser das die richtige Gesammtlänge in den erste zwei Byte nach dem Z steckt. Die anderen Variablen haben jeweils eine fixe Länge bis auf die letzte $payload, daher wohl auch das "böse" .* am Ende :)

if ($rmsg !~ m/Z(..)(..)(..)(..)(......)(......)(..)(.*)/x) {
Log3 $shash,3, "$name, unknown message : $rmsg";
return $shash->{NAME};
    }

    my ($len,$msgcnt,$msgFlag,$msgTypeRaw,$src,$dst,$groupid,$payload) = ($1,$2,$3,$4,$5,$6,$7,$8);

    $len = hex($len);
    if (2*$len+3 != length($rmsg)) {
#+3 = +1 for 'Z' and +2 for len field in hex
Log3 $shash, 1, $name.', message len mismatch '.length($rmsg).' vs '.(2*$len+3);
return $shash->{NAME};
    }
   
Maintainer der Module: MAX, MPD, UbiquitiMP, UbiquitiOut, SIP, BEOK, readingsWatcher

Beta-User

Na ja, da du die Info aus dem letzten Element ja genau haben willst, gibt es vermutlich wenig Optimierungsmöglichkeiten, evtl. abgesehen davon, dass das (teilweise?) [0-9A-F]-Werte sind und man die Wiederholungsanzahl übersichtlicher schreiben kann?

Aber man kann das gleich benennen, wie das hier auch irgendwo schon steht. Für MySensors sieht das neuerdings so aus (Werte sind durch ";" getrennt):
sub parseMsg {
    my $txt = shift;

    use bytes;

    return if ($txt !~ m{\A
               (?<nodeid>  [0-9]+);
               (?<childid> [0-9]+);
               (?<command> [0-4]);
               (?<ack>     [01]);
               (?<type>    [0-9]{1,2});
               (?<payload> .*)
               \z}xms);

    return {
        radioId => $+{nodeid}, # docs speak of "nodeId"
        childId => $+{childid},
        cmd     => $+{command},
        ack     => $+{ack},
        subType => $+{type},
        payload => $+{payload}
    };
}
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

Sidey

Wieso willst Du die regex komplett loswerden?

Wenn es mit Z beginnen muss würde ich auf ^Z am Anfang prüfen.
Da ich mir immer schwer tue, die . zu zählen hilft mir folgende Schreibweise:

(.{3}) dann müssen es drei beliebige Zeichen sein.
Anstelle von . kannst Du dir dann überlegen ob wirklich jedes Zeichen hier vorkommt:
Man kann auch min und max so angebrn, vielleicht hat der letzte Abschnitt ja eine Mindest und Maximallänge.

m/^Z(.{2})(.{2})(.{2})(.{2})(.{6})(.{6})(.{2})(.{1,})/x)

Ansonsten kannst Du das ja auch noch mit dem Vorschlag von Beta-User kombinieren :)

Gruß Sidey
Signalduino, Homematic, Raspberry Pi, Mysensors, MQTT, Alexa, Docker, AlexaFhem

Maintainer von: SIGNALduino, fhem-docker, alexa-fhem-docker, fhempy-docker

KernSani

Hänge mich mal zum Mitlesen mit rein. In TRX habe ich auch so undurchschaubare Konstrukte...


Kurz, weil mobil....
RasPi: RFXTRX, HM, zigbee2mqtt, mySensors, JeeLink, miLight, squeezbox, Alexa, Siri, ...

Icinger

Dann würde mich auch mal die Meinung zu meinen OBIS-Konstrukten interessieren:

my $OBIS_value = qr{(<|>)?([-+]?\d+\.?\d*)\*?(.*)\).*}x;
my $hexreg=qr{([[:xdigit:]][[:xdigit:]])}x;

# Some known OBIS-Codes

my %OBIS_codes = (
    "DevInfo"      => qr{\/                    # Full Answer: /AAAB\@nnnnnnnnnnnnnn
                         (...)(.)            # AAA=Short Manufacturer        B=max. Baudrate
                         \\                 # Delimeter
                         (.*)}x,            # Device-ID
    "Serial"       => qr{^0-0:96.1.255(?:.\d+)?\((.*?)\).*}x,                                # 0-0:96.1.255*255(11400158)
    "Serial"       => qr{^(?:1-0:)?0\.0\.[1-9]+(?:.\d+)?\((.*?)\).*}x,
    "Owner"        => qr{^1.0.0.0.0(?:.\d+)?\((.*?)\).*}x,                                   # 1-0:0.0.0*255(GETTONE)
    "Status"       => qr{^1.0.96.5.5(?:.\d+)?\((.*?)\).*}x,                                  # 1-0:96.5.5*255(@)
    "Powerdrops"   => qr{^0.0.96.7.\d(?:.\d+)?\((.*?)\).*}x,                                 # 0-0:96.7.21(00001)
    "Time_param"   => qr{^0.0.96.2.1(?:.\d+)?\((.*?)\).*}x,
    "Time_current" => qr{^0.0.1.0.0(?:.\d+)?\((.*?)\).*}x,                                   # 0-0:1.0.0(160515181000S)
    "Channel_sum"  => qr{^(?:1.0.)?(\d+).1.7(?:.0|.255)?(?:\(.*?\))?\($OBIS_value}x,         # 1-0:2.1.7*255(07568.01*kWh)
    "Channels"     => qr{^(?:\d.0.)?(\d+).7\.\d+(?:.0|.255)?(?:\(.*?\))?\($OBIS_value}x,     # 1-0:21.7.255*255(0000.2264*kW)
    "Channels2"    => qr{^(?:0.1.)?(\d+).2\.\d+(?:.0|.255)?(?:\(.*?\))?\($OBIS_value}x,      # 0-1:24.2.1(160515180000S)(00041.661*m3)
    "Counter"      => qr{^(?:1.\d.)?(\d).(8)\.(\d).(\d+)?(?:\(.*?\))?\($OBIS_value}x,        # 1-0:1.8.0*255(17483.88*kWh)
    "ManufID"      => qr{^129-129:199\.130\.3(?:.\d+)?\((.*?)\).*}x,                            # 129-129:199.130.3*255(ESY)
    "PublicKey"    => qr{^129-129:199\.130\.5(?:.\d+)?\((.*?)\).*}x,
);

my %SML_specialities = (
    "TIME" => [
        qr{0.0.96.2.1
           |0.0.1.0.0}x,
        sub {
            return strftime( "%d-%m-%Y %H:%M:%S",
                localtime( unpack( "i", pack( "I", hex(@_) ) ) ) );
        }
    ],
    "HEX2" => [
        qr{1-0:0\.0\.[0-9]    }x,
        sub {
            my $a = shift;
            if ( $a =~ /^[0-9a-fA-F]+$/x ) { $a =~ s/(..)/$1-/xg; $a =~ s/-$//x }
            return $a;
        }
    ],
    "HEX4" => [
        qr{1.0.96.5.5|0.0.96.240.\d+|129.129.199.130.5}x,
        sub {
            my $a = shift;
            if ( $a =~ /^[0-9a-fA-F]+$/x ) { $a =~ s/(....)/$1-/xg; $a =~ s/-$//x }
            return $a;
        }
    ],
    "INFO" => [
        qr{1-0:0\.0\.[0-9]|129.129.199.130.3}x, ""
    ],
);


lg, Stefan
Verwende deine Zeit nicht mit Erklärungen. Die Menschen hören (lesen) nur, was sie hören (lesen) wollen. (c) Paulo Coelho

Wzut

Zitat von: Sidey am 07 Mai 2020, 08:16:08
Wieso willst Du die regex komplett loswerden?
perlcritic -> $1,$2 usw.
Das Bsp von Beta-User sieht optisch verdammt gut aus (auch wenn ich es noch nicht verstehe).
Werde ich heute Abend mal ein mini Programm machen und das mit diversen Telegrammen füttern.
Maintainer der Module: MAX, MPD, UbiquitiMP, UbiquitiOut, SIP, BEOK, readingsWatcher

Beta-User

Nur kurz als Merkposten:

Eine der "beliebtesten" Stellen, an denen regexe@FHEM stehen ist notify. Das macht immer "\A" und "\z" dazu, was bedeutet, dass man häufig "auffüllen" muß, also der Einfachheit den "bösen" ".*" hinten anfügt.

Habe jetzt  verstanden, dass es schneller ist, ein "(.*)?" ranzupappen bzw. entsprechende Abwandlungen davon, falls man weiß, dass da ein Zeichen sein muß.

Aus
defmod n_FK_TK_notify notify ((Dachf|F)enster|.*tuer)_.*:contact:.(open|tilted|closed)..to.VCCU. { myWinContactNotify ($NAME, $EVENT, 30) }

wurde jetztdefmod n_FK_TK_notify notify ((Dachf|F)enster|(.+)?tuer)_(.+)?:contact:.(open|tilted|closed)..to.VCCU. { myWinContactNotify ($NAME, $EVENT, 30) }
.

Hat jemand für einen DAM wie mich eine Größenordnung, um wie viel schneller das ist? Oder übersehe ich mal wieder was wesentliches?
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

RichardCZ

Zitat von: amenomade am 07 Mai 2020, 02:28:40
/^.*HUMB\|([0-9]+(\.[0-9])?)\|[0-9]{4}?.*/
187 steps. Backtracking ist mit "^.*" vom Anfang an angesagt. Je länger der Text nach dem gesuchten Wert, desto mehr steps.

Das "187 steps" da oben macht mich ziemlich glücklich. Das zeig mir, dass offensichtlich Kenntnis über

use re 'debug'; # bzw.
use re 'Debug';


vorhanden ist. Siehe: https://perldoc.perl.org/re.html
Damit kann man dann nämlich "Performance-Albtraum" quantifizieren und - sobald man gelernt hat diese
Debugausgaben zu interpretieren - auch herausfinden kann warum die Regex manchmal nicht tut.

Zitat
Eigentlich reicht:
/HUMB\|([0-9.]+)/
9 steps, egal was danach steht. (OK, man kontrolliert damit nicht, dass nach der Zahl ein Pipe und 4 Ziffern kommen, und man kontrolliert auch nicht, dass in dem Wert der dezimale Punkt nicht mehrmals auftaucht. Zu locker? Abhängig vom Bedarf?)

Man sollte immer auf das matchen was man erwartet. Erwarte ich Zahlen vom Typ

0.123
1.23
123.111

dann [0-9]+\.[0-9]+ bzw. jeweils [0-9]{1,3}, weil an etlichen (blah)+ Stellen (und konkret kann ich mich an eine Diskussion mit Rudolf hier erinnern)
dem matching "nur Gott und der Hauptspeicher" ein Ende bereiten können.

Und wenn man sieht, wie quantitativ von Dir dargelegt, dass man sich eine Regex "mal eben" um den Faktor 20 schneller machen kann,
dann ist so eine straffere Spezifikation sicher drin im Budget.




Zitat von: Wzut am 07 Mai 2020, 07:16:09
a. meine Liebling Saubermach   =~ s/ //g; müsste ich zukünftig besser als =~ s/\s//g; schreiben ?

Also willst Du immer aus diesem Satz -> AlsowillstDuimmerausdiesemSatz

machen?

Dann wäre wohl tr/ //; die bessere Wahl.


Zitat
b. in 14_CUL_MAX steckt eine Regex die ich gerne loswerden möchte, aber natürlich keinen Plan habe wie
Es geht darum eine Nachricht in ihre Bestandteile zu zerlegen, bekannte Vorbedingung : Sie muß mit dem Buchstaben Z beginnen und mindestens 21 Zeichen lang sein. Prüfsumme gibt es keine, ausser das die richtige Gesammtlänge in den erste zwei Byte nach dem Z steckt. Die anderen Variablen haben jeweils eine fixe Länge bis auf die letzte $payload, daher wohl auch das "böse" .* am Ende :)

m/Z(..)(..)(..)(..)(......)(......)(..)(.*)/x
...
    my ($len,$msgcnt,$msgFlag,$msgTypeRaw,$src,$dst,$groupid,$payload) = ($1,$2,$3,$4,$5,$6,$7,$8);


* wenn Z wirklich am Anfang steht, dann ^Z als Anker
* wenn ich recht mitgezählt habe, sind da schon 22 Zeichen bevor .* kommt, also ist die Bedingung > 21 Zeichen immer erfüllt
* definitiv named captures -> siehe "code von Beta-user" ;-) ... ne? @Beta-User?

m{\A Z(?<len> [0-9]{2})
      (?<msgcnt> xxxxx)
...
  }xms


* kommen da echt beliebige Zeichen? Also auch "Z" zum Beispiel, oder sind das nur bestimmte Zeichenklassen?

(. durch \w zu ersetzen - Beispiel - ist ebenso sinnvoll wie .* loszuwerden.)

* Einerseits redest Du von Zeichen, dann wieder von Byte. Vorsicht! Das ist gefährlich die zwei Konzepte zu mixen.
* Falls Du da wirklich bytes parst, mach ein

use bytes;

vor der Regex. Ansonsten natürlich nicht.

Witty House Infrastructure Processor (WHIP) is a modern and
comprehensive full-stack smart home framework for the 21st century.

RichardCZ

Zitat von: Icinger am 07 Mai 2020, 08:22:35
Dann würde mich auch mal die Meinung zu meinen OBIS-Konstrukten interessieren:

Prinzipiell auf einem guten Level. An einigen Stellen glaube ich hast Du vergessen . zu escapen - oder?

Zitat

...
        qr{1.0.96.5.5|0.0.96.240.\d+|129.129.199.130.5}x,
...
            my $a = shift; # bitte kein $a,$b verwenden - unabhängig von unserer Rx diskussion
...
        qr{1-0:0\.0\.[0-9]|129.129.199.130.3}x, ""   # hier ist's mal escaped mal nicht?
    ],
);


Ansonsten gilt das Gesagte zu \d -> [0-9]
Witty House Infrastructure Processor (WHIP) is a modern and
comprehensive full-stack smart home framework for the 21st century.

RichardCZ

Zitat von: Beta-User am 07 Mai 2020, 08:37:23
Eine der "beliebtesten" Stellen, an denen regexe@FHEM stehen ist notify. Das macht immer "\A" und "\z" dazu, was bedeutet, dass man häufig "auffüllen" muß, also der Einfachheit den "bösen" ".*" hinten anfügt.

Dann sollte man mal über dieses Verhalten von notify nachdenken.

Zitat
Habe jetzt  verstanden, dass es schneller ist, ein "(.*)?" ranzupappen bzw. entsprechende Abwandlungen davon, falls man weiß, dass da ein Zeichen sein muß.

(.*)? ist noch schlimmer.  ;) Falls Du ein "non-greedy" match meintest, dann ist der (.*?) - ist was anderes. UNd falls man weiß dass da ein Zeichen sein muss, dann
(.+?)


Zitat
Aus
defmod n_FK_TK_notify notify ((Dachf|F)enster|.*tuer)_.*:contact:.(open|tilted|closed)..to.VCCU. { myWinContactNotify ($NAME, $EVENT, 30) }

wurde jetztdefmod n_FK_TK_notify notify ((Dachf|F)enster|(.+)?tuer)_(.+)?:contact:.(open|tilted|closed)..to.VCCU. { myWinContactNotify ($NAME, $EVENT, 30) }
.
Hat jemand für einen DAM wie mich eine Größenordnung, um wie viel schneller das ist? Oder übersehe ich mal wieder was wesentliches?

Siehe Kommentar von amenomade - das kann mal eben das 20-fache an Laufzeit ausmachen. Aber auch locker mehr. Backtracking hat exponentielle Laufzeit.
Je nach Stringlänge.

Du machst immer (.+)? statt (.+?) - vorsicht. Das Fragezeichen muss direkt an den Quantor.
Witty House Infrastructure Processor (WHIP) is a modern and
comprehensive full-stack smart home framework for the 21st century.

Beta-User

Vorab zur Klarstellung:

Der "hübsche" Code ist nicht von mir, den hat mir RichardCZ zugespielt. Wie man an meiner anderen Frage sieht, bin ich eher noch dabei, die Untiefen zu verstehen und meine Gedanken zu ordnen ;D .

Das (.+)? war etwas sehr auf die Schnelle zusammengeschustert, ich mache das nicht "immer", sondern lasse die Finger von Dingen, die ich gar nicht verstehe, bzw. teste das ggf. erst mal aus. Da das in einem halbwegs nachvollziehbaren Beispiel funktioniert hat, hab's vielleicht etwas voreilig hier gepostet.

Ob notify "an sich" das "richtig" löst, sei mal dahingestellt, dem Bauchgefühl finde ich es ok. Vermute, dass das eher "false positives" verhindert, die kein "normaler user" verstehen würde/rechtzeitig erkennen.

Dann werden das jetzt wohl "non-greedy" matches, das war es, nach dem ich eigentlich suchte ::) . Dass die (erhoffte) "Ersparnis" von der Stringlänge abhängt ist klar, aber hier wäre auch schon eine geschätzte Größenordnung hinreichend und interessant :) . (Die zu füllenden Leerstellen sind "lesbare Namensbestandteile", also tendenziell unter 10 Zeichen.)
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

rudolfkoenig

ZitatHat jemand für einen DAM wie mich eine Größenordnung, um wie viel schneller das ist?
Wenn das tiefergelegte Regexp dazu fuehrt, dass notifyRegexpChanged kein Device erkennt, dann wird das eher Groessenordnungen langsamer.

Beta-User

Zitat von: rudolfkoenig am 07 Mai 2020, 10:18:37
Wenn das tiefergelegte Regexp dazu fuehrt, dass notifyRegexpChanged kein Device erkennt, dann wird das eher Groessenordnungen langsamer.
Na ja, das "tiefergelegte Regexp" hatte zwar Balast an Board, aber so schlau, das wenigstens vor dem Posten auf Funktionsfähigkeit zu prüfen war ich schon ::) ... (Und ich bin mir auch ziemlich sicher, dass es auch keinen zweiten Eventhandler gibt, der das "Fenster-auf"-Signal verzögert an meine Thermostaten sendet ;D .)
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

rudolfkoenig

notifyRegexpChanged() in fhem.pl versucht zu erkennen, fuer welche Geraete das Regexp zutrifft.
Wenn was erkannt wird, dann wird NotifyFn (in notify/FileLog/etc) nur fuer Events, die von diesen Geraeten stammen, aufgerufen, ansonsten fuer alle Events.

Ein tiefergelegtes Regexp hat die Ehre, sein Performance gegen alle FHEM-Events zu beweisen, wogegen ein "Dummes" deutlich entspannter sein kann.
Nix gegen Regexp-Optimierung, aber (wie oft), ist man nicht ganz allein auf der Welt.

Beta-User

...dann bekommt mein notify jetzt wieder seine Urspungsgestalt und ich lasse an der Stelle "andere werkeln"...

Danke für die Aufklärung (falls ich den Hinweis jetzt richtig interpretiert habe ::) ).
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

RichardCZ

Kurzes Kochrezept:

Regex sollte straff sein. Es stimmt schon: Es hängt vom Kontext ab - man muss wissen/ahnen was man da an Daten erwartet.
Folglich sollte man sich mit allzu generischen Regexen zurückhalten. Ich mache hier mal ne Hitparade der "schlimmsten" Zeichen:

1) .  <- matche jedes Zeichen - wirklich jedes - Whitespaces, Interpunktion, UTF8 Schlonz, Normale Buchstaben, Zahlen

2) * <- matche was davorsteht (Zeichen, Gruppe), null oder mehrmals, also null bis unendlich mal. Also nix bis Universum.

In Kombination: .* gehört das eigentlich auf Platz eins oder 0).  :)

3) + <- matche eins bis unendlich mal. Viel besser als * (weil der Unterschied zwischen 0 und 1 sehr groß ist), aber immer noch sehr ... locker.

Man überlege sich bitte, ob man wirklich immer mit "potentiell unendlich" arbeiten will. Oder weiß, dass z.B. "3 bis 7 Zeichen" also \w{3,7}

4) \d <- matche eine Ziffer. Wie schon ausgeführt, kann eine Ziffer durchaus was anderes sein als was wir Mitteleuropäer uns darunter vorstellen.

Wir stellen uns eben [0-9] darunter vor, also sollten wir auch das schreiben.

5) \w <- matche ein "Wort-Zeichen", grob vereinfacht "Buchstaben und Zahlen und so". Wesentlich besser als . aber häufig immer noch zu locker.

Wenn ich weiß ich habe es z.B. mit hexadezimal Zahlen zu tun (0-9A-Fa-f), dann siehe https://perldoc.perl.org/perlreref.html
Entweder ich schnitze mir die Klasse selbst aus dem Knie: [0-9A-Za-z] oder ich verwende [[:xdigit:]], oder \p{XPosixDigit}




Praktisch immer kann ich eine Regex

.*:

verbessern und beschleunigen, indem ich "negative Zeichenklassen" verwende. Anstatt also zu glauben gesagt zu haben:

"Matche mir alles vor einem Doppelpunkt" (was nicht stimmt, denn alles beinhaltet eben auch einen Doppelpunkt)

wollte ich eigentlich sagen: "Matche mir alles - außer einem Doppelpunkt - vor einem Doppelpunkt" und dann sollte ich das auch so schreiben:

[^:]*:

Aber es gilt weiterhin das oben Gesagte. Muss ich wirklich "Null bis Universum matchen"? Erwarte ich vor dem Doppelpunkt mindestens ein Zeichen, oder tatsächlich auch mal "Nichts"? Es macht sehr viel aus stattdessen

[^:]+:

zu schreiben. Oder noch besser ich weiß, dass da "2 bis 20" Zeichen kommen können, dann eben

[^:]{2,20}:

Und noch besser ist natürlich die Zeichen explizit zu benennen. Negative Zeichenklassen sind ja auch ein "Alles bis auf..."
Versucht einfach mal spaßeshalber Regexen in natürlichsprachliche Aussagen zu übersetzen. Jedes Mal, wenn ihr zur Beschreibung die Worte "Alles", "beliebig" heranziehen müsst, besteht Optimierungspotential.
Witty House Infrastructure Processor (WHIP) is a modern and
comprehensive full-stack smart home framework for the 21st century.

RichardCZ

Zitat von: rudolfkoenig am 07 Mai 2020, 10:18:37
Wenn das tiefergelegte Regexp dazu fuehrt, dass notifyRegexpChanged kein Device erkennt, dann wird das eher Groessenordnungen langsamer.

Und dann wurde es nicht tiefergelegt, sondern einfach kaputtgemacht.

Wobei ja meistens nur Youngsters ihre Rostkarren tieferlegen. Wir Erfahrenen mit genug Zeit und Ausdauer restaurieren lieber rostige Oldtimer.
Witty House Infrastructure Processor (WHIP) is a modern and
comprehensive full-stack smart home framework for the 21st century.

Wzut

Zitat von: RichardCZ am 07 Mai 2020, 10:41:37
Praktisch immer kann ich eine Regex
.*:

verbessern und beschleunigen
@Richard, IMHO wird noch ein Extra Thread nötig sein, denn bisher reden wir doch zu 99% über Regex die wir in unseren Modulen verwenden ?
Aber eine ganze andee Baustelle ist doch : was ist mit den ganzen Regexen die alle User irgendwo als DEF oder Attribut eintragen ?
Als Beispiel nur mal das geliebte event-on-change-reading .* oder halt der banale FileLog Device.*:.*
Maintainer der Module: MAX, MPD, UbiquitiMP, UbiquitiOut, SIP, BEOK, readingsWatcher

Beta-User

Zitat von: Wzut am 07 Mai 2020, 11:37:15
@Richard, IMHO wird noch ein Extra Thread nötig sein, denn bisher reden wir doch zu 99% über Regex die wir in unseren Modulen verwenden ?
Aber eine ganze andee Baustelle ist doch : was ist mit den ganzen Regexen die alle User irgendwo als DEF oder Attribut eintragen ?
Als Beispiel nur mal das geliebte event-on-change-reading .* oder halt der banale FileLog Device.*:.*
Wenn ich Rudi's Antwort richtig interpretiere, sind das eigentlich ein paar mehr Varianten:

1. Was direkt im Modul (oder "echtem Perl-Code", insbesondere in myUtils) steht => hier zu diskutieren;
2. Was der User an das jeweilige Modul geben sollte, _sofern der jeweilige Modulautor nicht bereits Vorkehrungen getroffen hat_: (mit Einschränkungen) hier zu diskutieren (im Sinne von: Was mache ich als Modulautor ggf. draus, wie mache ich die Doku für den Endanwender dazu);
3. "Andere" Module: Hatten wir in Bezug auf notify schon, denke, das gilt vermutlich für den "kleinen Bruder" FileLog analog: Liegt in der Verantwortung des Modulautors; für notify hat Rudi das so optimiert, dass der User einfach ".*" verwenden kann und soll (wie es in der Doku steht...).

4. (MMn.) Separates Thema: Wie klinkt sich mein Modul optimalerweise das Event-Dispatching von fhem.pl ein? Das ist eigentlich irgendwo bei https://wiki.fhem.de/wiki/DevelopmentModuleIntro#X_Notify zu verorten, oder? Falls da was verbesserungswürdiges steht, wäre das mMn. gesondert zu diskutieren.
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

rudolfkoenig

Zitatwas ist mit den ganzen Regexen die alle User irgendwo als DEF oder Attribut eintragen ?
Wir beschliessen hiermit, dass FHEM nur von gebildeten Leuten, mit abgeschlossenem Master in Informatik und Doktorarbeit in Regexp-Optimierung verwendet werden darf.
Alle anderen werden wuest beschimpft und auf dem Pranger gestellt.

RichardCZ

#28
Zitat von: Wzut am 07 Mai 2020, 11:37:15
@Richard, IMHO wird noch ein Extra Thread nötig sein, denn bisher reden wir doch zu 99% über Regex die wir in unseren Modulen verwenden ?

Ja klar. Hier dürfen User ja auch gar nicht mitschreiben. Die Ambitionierten können ja mitlesen. Ansonsten sollen die User erstmal machen wozu sie fähig und willens sind.

Zitat
Aber eine ganze andee Baustelle ist doch : was ist mit den ganzen Regexen die alle User irgendwo als DEF oder Attribut eintragen ?
Als Beispiel nur mal das geliebte event-on-change-reading .* oder halt der banale FileLog Device.*:.*

Eben. Andere Baustelle. Also die ganzen User-Eval-Verquickungen (und Eigentlich-Grund-Warum-Man-bei-FHEM-fast-nix-anfassen-kann-Rückwärtskompatibilität-wissensschon)
... das sehe ich mir so in 6 Monaten an - frühestens. Bevor man ne Testsuite hat ist das "futile".

Mittelfristig stelle ich mir das so vor, dass da eine hübsche Unit-Tests Suite vorhanden ist, und dann baut man eine Integrations-Testsuite.
... naja - man - ich vermutlich, wer sonst?  ...
und diese Integrationssuite bekommt eben diese User-Snippets vor den Latz geknallt (braucht dann auch Heavy-Duty Mock::Object u.ä.)

Also da ist noch weit hin.
Witty House Infrastructure Processor (WHIP) is a modern and
comprehensive full-stack smart home framework for the 21st century.

Sidey

Ich hab jetzt überlegt ob ich eine meiner Werke präsentiere,
da ist es:

m/^MS;(?:P\d=-?\d+;){3,8}D=\d+;CP=\d;SP=\d;/

Eigentlich extrahieren ich alle Bestandteile dann später noch mal in einen Hash, damit ich auf die Daten zugreifen kann.

Mit named capture groups habe ich mich noch nicht so sehr versucht, wüsste aktuell auch nicht, wie man es mit mehrfach vorkommenden capture groups arbeitet :)

Gruß Sidey
Signalduino, Homematic, Raspberry Pi, Mysensors, MQTT, Alexa, Docker, AlexaFhem

Maintainer von: SIGNALduino, fhem-docker, alexa-fhem-docker, fhempy-docker

RichardCZ

Zitat von: Sidey am 07 Mai 2020, 20:55:32
m/^MS;(?:P\d=-?\d+;){3,8}D=\d+;CP=\d;SP=\d;/
Eigentlich extrahieren ich alle Bestandteile dann später noch mal in einen Hash, damit ich auf die Daten zugreifen kann.
Mit named capture groups habe ich mich noch nicht so sehr versucht, wüsste aktuell auch nicht, wie man es mit mehrfach vorkommenden capture groups arbeitet :)

Gibt's ein paar Beispieldaten (so 10-20 repräsentative Zeilen gegen die gematched wird)?
Wenn ich es recht verstanen habe, matchst Du mal gegen den string als ganzes und dann nochmal gegen die "\d"'s, die dann gefangen und ausgewertet werden?
Würde vermutlich Sinn machen das gleich auf einmal zu machen.

m/^MS;(?:P\d=-?\d+;){3,8}
         D=(?<d>  \d+);
        CP=(?<cp> \d);
        SP=(?<sp> \d);/xms;


? Leider fehlt mir mehr Kontext um da qualifiziertere Aussagen treffen zu können.
Witty House Infrastructure Processor (WHIP) is a modern and
comprehensive full-stack smart home framework for the 21st century.

Wzut

#31
Ich werf nochmal meinen Hut in den Ring zum Thema MAX Telegramm prüfen & dekodieren.
So schaut jetzt meine Lösung aus :

# Beispiel  : $rmsg =  "Z0CF304420584D8120834002CF8";
# Beispiel  : $rmsg =  "Z0F0004600703780000000039003D00B9";

my $l = hex(substr($rmsg,1,2));

    if (2*$l+3 != length($rmsg))  { #+3 = +1 for 'Z' and +2 for len field in hex
Log3($name, message $rmsg len mismatch '.length($rmsg).' vs '.(2*$l+3);
        return $name;
    }

my ($len,$msgcnt,$msgFlag,$msgTypeRaw,$src,$dst,$groupid,$payload) =
$rmsg =~ m/\A Z([0-9A-F]{2}) ([0-9A-F]{2}) ([0-9A-F]{2}) ([0-9A-F]{2})
       ([0-9A-F]{6}) ([0-9A-F]{6}) ([0-9A-F]{2}) ([0-9A-F]{0,10})\z/xms;

Maintainer der Module: MAX, MPD, UbiquitiMP, UbiquitiOut, SIP, BEOK, readingsWatcher

RichardCZ

Zitat von: Wzut am 08 Mai 2020, 19:22:42
my ($len,$msgcnt,$msgFlag,$msgTypeRaw,$src,$dst,$groupid,$payload) =
$rmsg =~ m/\A Z([0-9A-F]{2}) ([0-9A-F]{2}) ([0-9A-F]{2}) ([0-9A-F]{2})
       ([0-9A-F]{6}) ([0-9A-F]{6}) ([0-9A-F]{2}) ([0-9A-F]{0,10})\z/xms;


Würde ich sagen ist ok. Die klassisch Analfixierten würden vermutlich irgendwas in der Art
my ($len,         $msgcnt,      $msgFlag,     $msgTypeRaw,
    $src,         $dst,         $groupid,     $payload)
    =  $rmsg =~ m/\A Z
    ([0-9A-F]{2}) ([0-9A-F]{2}) ([0-9A-F]{2}) ([0-9A-F]{2})
    ([0-9A-F]{6}) ([0-9A-F]{6}) ([0-9A-F]{2}) ([0-9A-F]{0,10})
    \z/xms;


machen, aber da muss man nicht zuschauen bei.  ;)
Witty House Infrastructure Processor (WHIP) is a modern and
comprehensive full-stack smart home framework for the 21st century.

Sidey

Zitat von: RichardCZ am 08 Mai 2020, 12:43:15
Gibt's ein paar Beispieldaten (so 10-20 repräsentative Zeilen gegen die gematched wird)?
Ja, ich denke es macht nicht viel her, die eine Regex zu überarbeiten.
Da gibt's es mehrere die mehr oder weniger alle an diesem Datensatz rumwerkeln mit Variationen.


Zitat von: RichardCZ link=topic=110949.msg1051655#msg1051655
Wenn ich es recht verstanen habe, matchst Du mal gegen den string als ganzes und dann nochmal gegen die "\d"'s, die dann gefangen und ausgewertet werden?
Würde vermutlich Sinn machen das gleich auf einmal zu machen.

Es gibt ein Identifier am Anfang. Im Besipiel MS, MC, MU und MN sind aber auch valide Werte. :)
Das ";" ist immer ein Trennzeichen

Nach dem Identifier finden sich Key=Value Zuordnung wieder.
P<0-7>=<Wert>
D=<Wert>
CP=<Wert>
SP=<Wert>


Gülige Werte bedienen sich aus dem Bereich [0-7]  (derzeit).

Zitat von: RichardCZ link=topic=110949.msg1051655#msg1051655
code]m/^MS;(?:P\d=-?\d+;){3,8}
         D=(?<d>  \d+);
        CP=(?<cp> \d);
        SP=(?<sp> \d);/xms;
[/code]
Für das Abgreifen von D, CP und SP ist die vorgehensweise klar.
Ich brauche noch die Werte aus Px=y wobei ich X als Key und Y als Value benötige.
Ohne named capture group könnte ich das mit $1, $2, ... abrufen.

Um jetzt mal den Umfang der Regexen zu umschreiben,
ich hab aktuell vier und alle sind ein bisschen abweichend umgesetzt. :(


m/^MS;(P\d=-?\d+;){3,8}D=\d+;CP=\d;SP=\d;
m/^MU;(P\d=-?\d+;){3,8}((CP|R)=\d+;){0,2}D=\d+;/
m/^M[cC];.*;/
m/^MN;.*;/


Je nachdem, welcher matcht wird eine dafür definierte funktion für die verarbeitung aufgerufen.

Das Auswerten der Inhalte realisiere ich aktuell via split(';') und suche in einer Schleife die passenden Werte nach dem Motto Key=Value mittlels split(/=/).

Testdaten habe ich massenweise:
https://github.com/RFD-FHEM/SIGNALduino_TOOL/blob/master/FHEM/lib/SD_Device_ProtocolList.json


MS;P1=-7949;P2=492;P3=-1978;P4=-3970;D=21232423232424242423232323232324242423232323232424;CP=2;SP=1;R=245;O;
MS;P1=492;P2=-4005;P3=-8937;P4=-2023;D=13121214121412121414121214121414141414141414141414141412141414141412121414;CP=1;SP=3;s6;e;m2;
MS;P0=-970;P1=254;P3=-1983;P4=-8045;D=14101310131010101310101010101010101010101313101010101010101313131010131013;CP=1;SP=4;
MU;P0=595;P1=344;P2=-862;P3=846;P4=244;P5=-602;P7=-251;D=1232323245454545454507074545070707450707454545070707070707074545454507074545074507454540;CP=4;R=4;
MC;LL=-409;LH=448;SL=-172;SH=262;D=238126DAA58;C=215;L=41;R=238;
MC;LL=-1002;LH=952;SL=-489;SH=475;D=000000A0EBDDC4FBFBF13EF5B6;C=486;L=104;s48;b1;
MU;P0=132;P1=500;P2=-233;P3=-598;P4=-980;P5=4526;D=012120303030303120303030453120303121212121203121212121203121212121212030303030312030312031203030303030312031203031212120303030303120303030453120303121212121203121212121203121212121212030303030312030312031203030303030312031203031212120303030;CP=0;O;
MN;D=8AA6362CC8AAAA000012F8F4;R=4;
MN;D=0405019E8700AAAAAAAA0F13AA16ACC0540AAA49C814473A2774D208AC0B0167;N=3;R=6;


Sind ein paar Varianzen enthalten :)
Signalduino, Homematic, Raspberry Pi, Mysensors, MQTT, Alexa, Docker, AlexaFhem

Maintainer von: SIGNALduino, fhem-docker, alexa-fhem-docker, fhempy-docker

RichardCZ

Zitat von: RichardCZ am 08 Mai 2020, 19:51:03
my ($len,         $msgcnt,      $msgFlag,     $msgTypeRaw,
    $src,         $dst,         $groupid,     $payload)
    =  $rmsg =~ m/\A Z
    ([0-9A-F]{2}) ([0-9A-F]{2}) ([0-9A-F]{2}) ([0-9A-F]{2})
    ([0-9A-F]{6}) ([0-9A-F]{6}) ([0-9A-F]{2}) ([0-9A-F]{0,10})
    \z/xms;


Es erreichte mich eine PM, die zeigt, dass man sich auch manchmal in die andere Richtung verlaufen kann.
Genauso wie so manch Java/Python/xxx Dev reguläre Ausdrücke meidet wie der Teufel das Weihwasser,
genauso kann es manchmal sein, dass man den Regex-Hammer zu früh herausholt - nur weil er schön
griffbereit liegt.

Eigentlich ist das nämlich ein Fall für unpack. Aufmerksam gemacht hat mich darauf @elle,
code in etwa:

if ( $rmsg !~ m{\A Z [0-9A-F]{1,30} }xms ) { # repeptition safe margin, but not "infinite"
# Log "Wrong Format", or somesuch
}

my ($len, $msgcnt, $msgFlag, $msgTypeRaw, $src, $dst, $groupid, $payload) = unpack("(A2)4(A6)2A2A10", $rmsg);


Witty House Infrastructure Processor (WHIP) is a modern and
comprehensive full-stack smart home framework for the 21st century.

Wzut

sieht auch gut aus :) mal schauen was die Praxis dazu sagt. 
Maintainer der Module: MAX, MPD, UbiquitiMP, UbiquitiOut, SIP, BEOK, readingsWatcher

Wzut

Zitat von: RichardCZ am 09 Mai 2020, 11:54:53
my ($len, $msgcnt, $msgFlag, $msgTypeRaw, $src, $dst, $groupid, $payload) = unpack("(A2)4(A6)2A2A10", $rmsg);
schaut schön aus , dekodiert aber bereits das Start Z mit und dann ist alles um ein Zeichen verschoben, dagegen
my ($len, $msgcnt, $msgFlag, $msgTypeRaw, $src, $dst, $groupid, $payload) = unpack("(A2)4(A6)2A2A10", substr($rmsg,1));
macht was es soll :)



Maintainer der Module: MAX, MPD, UbiquitiMP, UbiquitiOut, SIP, BEOK, readingsWatcher

RichardCZ

Zitat von: Wzut am 09 Mai 2020, 17:02:08
schaut schön aus , dekodiert aber bereits das Start Z mit und dann ist alles um ein Zeichen verschoben, dagegen
my ($len, $msgcnt, $msgFlag, $msgTypeRaw, $src, $dst, $groupid, $payload) = unpack("(A2)4(A6)2A2A10", substr($rmsg,1));
macht was es soll :)

Zum Beispiel.

Wobei mich ja so der Verdacht beschleicht, dass man u.U. direkt Hex werte entpacken möchte? (H bzw. h) ?

Aber wie gesagt, dieses Beispiel gehört in diesen Thread eigentlich nur herein um zu zeigen wann man Regexen nicht verwenden muss/soll.
Witty House Infrastructure Processor (WHIP) is a modern and
comprehensive full-stack smart home framework for the 21st century.

Sidey



Zitat von: Wzut am 09 Mai 2020, 17:02:08
my ($len, $msgcnt, $msgFlag, $msgTypeRaw, $src, $dst, $groupid, $payload) = unpack("(A2)4(A6)2A2A10", substr($rmsg,1));
macht was es soll :)

Sieht nett aus mit dem substr, ist vor allem einfach.
Nur so muss es nicht mehr mit Z anfangen, was aber ggf. schon an anderer Stelle geprüft wurde :)


Gesendet von meinem Moto Z (2) mit Tapatalk

Signalduino, Homematic, Raspberry Pi, Mysensors, MQTT, Alexa, Docker, AlexaFhem

Maintainer von: SIGNALduino, fhem-docker, alexa-fhem-docker, fhempy-docker

Wzut

Zitat von: Sidey am 09 Mai 2020, 18:00:11
Nur so muss es nicht mehr mit Z anfangen, was aber ggf. schon an anderer Stelle geprüft wurde :)
doch die Regel davor hat das Z ja noch drin und IMHO liefert 00_CUL je nur an 14_CUL_MAX  Parse aus wenn dessen eigener $hash->{Match} = '^Z' passt :) 
Maintainer der Module: MAX, MPD, UbiquitiMP, UbiquitiOut, SIP, BEOK, readingsWatcher