Jump to content

Powershell Script - Suche in Textblöcken


Direkt zur Lösung Gelöst von daabm,

Empfohlene Beiträge

Danke für Eure Antworten.
Es gilt, eine komplette Nachricht, welche einen Suchstring enthält, als Block zu exportieren.

Die Struktur ist recht einfach. Eine Nachricht fängt immer mit dem MSH-Segment (am Zeilenanfang) an und endet mit dem nächsten MSH-Segment der nächsten Nachricht, innerhalb einer Archivdatei (es sind einige hundert Nachrichten pro Archivdatei).
 

Das Lösen per Queue oder Array scheint mir Erfolg versprechend zu sein. Bei mir scheitert es an der korrekten Syntax für die Umsetzung (veranschaulicht am Stand in meinem letzten Post). Ich finde auch nicht wirklich viel beim großen „G“, in Verbindung mit Powershell und der Queue.
Wie sich ein -split dazu hinreißen lässt, den Datenstrom bei einem gefundenen Suchstring zu unterbrechen und den entsprechenden Datensatz komplett (MSH > MSH) zu exportieren, finde ich spannend, da dieser Ansatz sehr „smart“ ausschaut.

 
Z.B. der Suchstring 7909264350“ wird im PV1-Segment des Beispiels unten (Zitat) gefunden, nun sollte die komplette hervorgehobene Nachricht (von MSH > MSH) exportiert werden.
Es ist durchaus möglich, dass der Suchstring mehrfach in der Archivdatei vorhanden ist (alle betreffenden Datensätze/Nachrichten komplett exportieren).
Bisher wird dies manuell umgesetzt, Suchstring gefunden, ein paar Zeilen hoch (MSG-Segment vor dem Suchstring), markiert inklusive zur letzten Zeile der Nachricht (Zeile vor dem nächsten MSG-Segment), kopieren und in eine neue Datei einfügen.

 

Zitat

 

Archivdateibeispiel (sehr stark gekürzt):

 

MSH|^~\&|MEL
PID|1||7903313686|
PV1|1||^^^^^^^^2825||||||||||||||||7909260637|
ORC|OK|WKC097U|WKC097U-
OBR|1|WKC097U|WKC097U-1
OBX|1|ST|BI-032^Erreg
OBR|2|WKC097U|WKC097U
OBX|1|ST|BI-685^Staph
OBR|3|WKC097U|WKC097U
OBX|1|ST|BI-PROT1^Bef
NTE|1|L|Kulturelles W

MSH|^~\&|ME
PID|1||7902183713|
PV1|1||^^^^^^^^2938||||||||||||||||7909264350|
ORC|OK|WKC058S|WKC
OBR|1|WKC058S|WKC0
OBX|1|ST|BI-032^Er
OBR|2|WKC058S|WKC0
OBX|1|ST|BI-603^Prot
OBR|3|WKC058S|WKC058S
OBX|1|ST|BI-PROT1^Bef
NTE|1|L|Kulturelles Wach
OBR|4|WKC058S|WKC058S-1|
OBX|1|ST|BI-PROT2^Befund
NTE|1|L|Antibiogramm sie
OBR|5|WKC058S|WKC058S-1|
OBX|1|ST|BI-ICD109^ICD10
NTE|1|L|A40-A41 Bakterie
NTE|2|L|T82.7 Sepsis bei

MSH|^~\&|ME
PID|1||7903126877|
PV1|1||^^^^^^^^2825||||||||||||||||7909265076|
ORC|OK|WKC118D|WKC
OBR|1|WKC118D|WKC1
OBX|1|ST|BI-032^Er
OBR|2|WKC118D|WKC1
OBX|1|ST|BI-673^St
OBR|3|WKC118D|WKC1
OBX|1|ST|BI-PROT1^
NTE|1|L|Zusätzlich
OBR|4|WKC118D|WKC1
OBX|1|NM|BI-ATB^An
OBR|5|WKC118D|WKC1
OBX|1|ST|BI-RES^Me
NTE|1|L|Antibiogra
NTE|2|L|Wirkstoff
NTE|3|L|Penicillin
NTE|4|L|Oxacillin
NTE|5|L|Ampicillin

 

 

Link zu diesem Kommentar

Liest du auch Antworten und versuchst ansatzweise etwas Energie da reinzustecken?

 

$LogFile = "C:\Install\Test\Log.txt"
$LogContent = Get-Content -Path $LogFile `
    -Raw
$Content = $LogContent -split "(?=MSH\|)" |
    Where-Object { -not [string]::IsNullOrEmpty($_) }

$i = 1
$Content -match "\|7902183713\|" |
    ForEach-Object {
        Write-Host "Ich bin gefundener Block Nr.: $i`n"
        $_
        $i++
    }

 

(Das lässt sich sicherlich noch optimieren, sofern Bedarf besteht und das Auswerten der Logs wirklich zeitkritisch sein sollte.)

Link zu diesem Kommentar
  • Beste Lösung

Dein Fehler war nur das fehlende Verständnis für "Contains". Das gilt nur für Arrays, und der Vergleichswert muss einem _kompletten_ Element des Arrays entsprechen.

 

$SampleData = "$env:TEMP\SampleData.txt"

$Reader = [IO.StreamReader]::new( $SampleData )
$Results = [Collections.Arraylist]::new()
$Queue = [Collections.Queue]::new()

$BlockMarker = 'MSH'
$MatchString = "PID"

$MatchFound = $false
While ( $SampleLine = $Reader.ReadLine() ) {
    $Queue.Enqueue( $SampleLine )
    If ( $SampleLine -match $MatchString ) {
        $MatchFound = $true
    }
    If ( $SampleLine -match $BlockMarker ) {
        If ( $MatchFound ) {
            [void] $Results.Add(( $Queue.ToArray() ))
        }
        $Queue.Clear()
        $MatchFound = $false
        $Queue.Enqueue( $SampleLine )
    }
}
$Reader.Dispose()

 

Das steckt solange Zeilen in die Queue, bis ein "MSH" gefunden wird. Wenn unterwegs ein "PID" vorbeikam, wird die Queue in $Result gespeichert. Dann zurück auf Los :-)

 

Das meinte ich mit meinem vorigen Post

Du mußt eine Logik finden und korrekt implementieren, die die Aufgabe auch löst.

 

Edit: Nachträglich von Get-Content auf StreamReader geändert, ist einfach Lichtjahre schneller...

Nachtrag: Natürlich geht das auch mit Regex Multiline. Aber da sind wir wieder bei der Wartbarkeit, Regex hat da seine eigenen Tücken. Hab irgendwo mal ein Regex aufgeschnappt, das ich nicht mehr finde - das konnte alle Arten von Kommentaren aus C++-Sourcecode entfernen. War aber komplett unverständlich... :-)

bearbeitet von daabm
Link zu diesem Kommentar
vor 11 Minuten schrieb NorbertFe:

logisch, wenns regex ist. ;)

 

"NorbertFe" -match "^N[o].+e"

 

Regex hat viele "Geschmäcker". Die einfachen sind echt "handy", weil Regex halt für Stringmassage gemacht ist. Aber wenn man dann Lazy/Greedy und vielleicht noch Backward nutzt, nested und named Matches verwendet, wird's schnell unübersichtlich.

Link zu diesem Kommentar
vor 8 Stunden schrieb daabm:

Dein Fehler war nur das fehlende Verständnis für "Contains". Das gilt nur für Arrays, und der Vergleichswert muss einem _kompletten_ Element des Arrays entsprechen.

 

$SampleData = "$env:TEMP\SampleData.txt"

$Reader = [IO.StreamReader]::new( $SampleData )
$Results = [Collections.Arraylist]::new()
$Queue = [Collections.Queue]::new()

$BlockMarker = 'MSH'
$MatchString = "PID"

$MatchFound = $false
While ( $SampleLine = $Reader.ReadLine() ) {
    $Queue.Enqueue( $SampleLine )
    If ( $SampleLine -match $MatchString ) {
        $MatchFound = $true
    }
    If ( $SampleLine -match $BlockMarker ) {
        If ( $MatchFound ) {
            [void] $Results.Add(( $Queue.ToArray() ))
        }
        $Queue.Clear()
        $MatchFound = $false
        $Queue.Enqueue( $SampleLine )
    }
}
$Reader.Dispose()

 

Das steckt solange Zeilen in die Queue, bis ein "MSH" gefunden wird. Wenn unterwegs ein "PID" vorbeikam, wird die Queue in $Result gespeichert. Dann zurück auf Los :-)

 

Das meinte ich mit meinem vorigen Post

Du mußt eine Logik finden und korrekt implementieren, die die Aufgabe auch löst.

 

Edit: Nachträglich von Get-Content auf StreamReader geändert, ist einfach Lichtjahre schneller...

Nachtrag: Natürlich geht das auch mit Regex Multiline. Aber da sind wir wieder bei der Wartbarkeit, Regex hat da seine eigenen Tücken. Hab irgendwo mal ein Regex aufgeschnappt, das ich nicht mehr finde - das konnte alle Arten von Kommentaren aus C++-Sourcecode entfernen. War aber komplett unverständlich... :-)

Danke dir / euch,

 

ich würde mir das Ganze einmal durch die Hirnwindungen jagen und versuchen zu finalisieren.

Danke für den Tipp mit dem "Contains" .... hat mich doch recht lange beschäftigt...

Nur gut, dass auch andere sich mit / bei den Regex nicht zu Hause fühlen. :-)

vor 7 Stunden schrieb NorbertFe:

logisch, wenns regex ist. ;)

 

.... ich schaue mal, wie weit ich komme. 

 

Link zu diesem Kommentar

Durch die massive Unterstützung von euch, schaut nun eine Lösung so aus:

$SampleData = "c:\temp\Schnittstelle\oru.log"

$Reader = [IO.StreamReader]::new( $SampleData )
$Results = [Collections.Arraylist]::new()
$Queue = [Collections.Queue]::new()

$BlockMarker = 'MSH'
$MatchString = "7909265176"

$MatchFound = $false
While ( $SampleLine = $Reader.ReadLine() ) {
    $Queue.Enqueue( $SampleLine )
    If ( $SampleLine -match $MatchString ) {
        $MatchFound = $true
        }
    If ( $SampleLine -match $BlockMarker ) {
        If ( $MatchFound ) {
            [void] $Results.Add(( $Queue.ToArray() ))
            $Zeilen = $Queue.Count
            for($i= 0; $i -lt $Zeilen -1 ; $i++) {
            $Queue.Dequeue() >> c:\temp\Schnittstelle\ORU_$(Get-Date -Format yyyy-MM-dd.hh.mm.ss).log
            }             
        }
        $Queue.Clear()
        $MatchFound = $false
        $Queue.Enqueue( $SampleLine )
    }
}
$Reader.Dispose()

Es waren nur noch kleine Anpassungen zu dem fast fertigen Script von Martin nötig.
In der Ausgabe ist immer die erste Zeile (MSH-Segment) der nächsten Nachricht mit ausgegeben worden.
Ein Löschen des letzten Eintrags der $Queue ist mir nicht möglich gewesen, weshalb ich eine Schleife eingebaut habe, die die Queue (Einträge / Zeilen -1) ausgibt.

Meinen herzlichen Dank an euch - Habt Ihr eine virtuelle Kaffeedose mit Schlitz für eine Spende?

Helmer

bearbeitet von Helmer
Link zu diesem Kommentar

Schreibe einen Kommentar

Du kannst jetzt antworten und Dich später registrieren. Falls Du bereits ein Mitglied bist, logge Dich jetzt ein.

Gast
Auf dieses Thema antworten...

×   Du hast formatierten Text eingefügt.   Formatierung jetzt entfernen

  Only 75 emoji are allowed.

×   Dein Link wurde automatisch eingebettet.   Einbetten rückgängig machen und als Link darstellen

×   Dein vorheriger Inhalt wurde wiederhergestellt.   Editor-Fenster leeren

×   Du kannst Bilder nicht direkt einfügen. Lade Bilder hoch oder lade sie von einer URL.

×
×
  • Neu erstellen...