Below is the data I have
-Ignored:31,Modified,src,data,all,*file,MINOSFIC/UTMNUP10
-Ignored:33,Modified,src,&,tgt,data,all,*file,MINOSFIC/UVEGAP10
-Ignored:92,Synchro,is,running,*file,MINOSFIC/VM010P50
-Ignored:01,Object,hold,(synchro),*file,MINOSFIC/VM010U51
here I am parsing the data and keeping in csv
for 1st and 2nd line it is working but when it is coming to 3rd and 4th line, it is pushing the column value one forward as there is no data before *file
Please let me know how to handle this. how to insert a comma before *file if there is not entry (like for first 2 line it is all)
$allIGfiles = Get-ChildItem -Path 'C:\LG2' -Recurse -Filter "*LG_VFN*"
foreach($file in $allIGfiles)
{
$filename = $file.FullName
$data = Get-Content $filename | Select -SkipLast 1
$Lines = #()
foreach ($line in $data){
if($line -match "Ignored")
{
$Lines+=$line
}
}
$NewLines = #($Lines | % { ($_ -replace "\s{2,}",",") -replace "(\d) ", '$1,'} )
$NewLines | Export-Csv 'c:\file.csv' -append -NoTypeInformation
Original data
-Ignored:31 Modified src data all *file MINOSFIC/UTMNUP10
-Ignored:33 Modified src & tgt data all *file MINOSFIC/UVEGAP10
-Ignored:92 Synchro is running *file MINOSFIC/VM010P50
-Ignored:01 Object hold (synchro) *file MINOSFIC/VM010U51
Update:
I am now getting the data like below but when i am trying to put it in csv it is only writing numbers to the file
-Ignored:31,Modified src data,all *file,MINOSFIC/UTMNUP10
-Ignored:33,Modified src & tgt data,all *file,MINOSFIC/UVEGAP10
-Ignored:92,Synchro is running,*file,MINOSFIC/VM010P50
-Ignored:01,Object hold (synchro),*file,MINOSFIC/VM010U51
-Ignored:01,Object hold (synchro),*file,MINOSFIC/VM010U52
-Ignored:01,Object hold (synchro),*file,MINOSFIC/VM010U53
-Ignored:01,Object hold (synchro),*file,MINOSFIC/VM010U54
Data with other code
Object OK (partial) 97% *file MINOSFIC/VM011P10
-Ignored:18 Object hold *file MINOSFIC/VM011P50
Object OK (partial) 78% *file MINOSFIC/VM800P30
*Error: 09 Diff. Creation date *file MINOSSVG/S100000702
*Error: 09 Diff. Creation date *file MINOSSVG/S100000805
-Ignored:18 Object hold *file MINOSSVG/S100001154
*Error: 09 Diff. Creation date *file MINOSSVG/S100001227
You could do this by using a regex that at first ignores the all value, but when constructing the comma separated new string, this will be inserted when found:
Read the file as string array
$data = Get-Content -Path $filename
I'm faking that by using a Here-String below:
$data = #"
-Ignored:31 Modified src data all *file MINOSFIC/UTMNUP10
-Ignored:33 Modified src & tgt data all *file MINOSFIC/UVEGAP10
-Ignored:92 Synchro is running *file MINOSFIC/VM010P50
-Ignored:01 Object hold (synchro) *file MINOSFIC/VM010U51
"# -split '\r?\n'
$result = foreach ($line in $data) {
if ($line -match '^(-Ignored:\d+)\s+(.+)\s+(\*file)\s+(.*)') {
'{0},{1},{2},{3},{4}' -f $matches[1],
($matches[2] -replace 'all$').Trim(),
($matches[2] -split '\s{2,}')[-1],
$matches[3],
$matches[4]
}
}
# output to console screen
$result
# write to file
$result | Set-Content -Path 'X:\TheNewFile.txt'
Output:
-Ignored:31,Modified src data,all,*file,MINOSFIC/UTMNUP10
-Ignored:33,Modified src & tgt data,all,*file,MINOSFIC/UVEGAP10
-Ignored:92,Synchro is running,,*file,MINOSFIC/VM010P50
-Ignored:01,Object hold (synchro),,*file,MINOSFIC/VM010U51
To also do this with *Error.. lines as in your updated example, change the line
if ($line -match '^(-Ignored:\d+)\s+(.+)\s+(\*file)\s+(.*)') {
into
if ($line -match '^((?:-Ignored|\*Error):\s*\d+)\s+(.+)\s+(\*file)\s+(.*)') {
Regex details:
^ Assert position at the beginning of a line (at beginning of the string or after a line break character)
( Match the regular expression below and capture its match into backreference number 1
(?: Match the regular expression below
Match either the regular expression below (attempting the next alternative only if this one fails)
-Ignored Match the characters “-Ignored” literally
| Or match regular expression number 2 below (the entire group fails if this one fails to match)
\* Match the character “*” literally
Error Match the characters “Error” literally
)
: Match the character “:” literally
\s Match a single character that is a “whitespace character” (spaces, tabs, line breaks, etc.)
* Between zero and unlimited times, as many times as possible, giving back as needed (greedy)
\d Match a single digit 0..9
+ Between one and unlimited times, as many times as possible, giving back as needed (greedy)
)
\s Match a single character that is a “whitespace character” (spaces, tabs, line breaks, etc.)
+ Between one and unlimited times, as many times as possible, giving back as needed (greedy)
( Match the regular expression below and capture its match into backreference number 2
. Match any single character that is not a line break character
+ Between one and unlimited times, as many times as possible, giving back as needed (greedy)
)
\s Match a single character that is a “whitespace character” (spaces, tabs, line breaks, etc.)
+ Between one and unlimited times, as many times as possible, giving back as needed (greedy)
( Match the regular expression below and capture its match into backreference number 3
\* Match the character “*” literally
file Match the characters “file” literally
)
\s Match a single character that is a “whitespace character” (spaces, tabs, line breaks, etc.)
+ Between one and unlimited times, as many times as possible, giving back as needed (greedy)
( Match the regular expression below and capture its match into backreference number 4
. Match any single character that is not a line break character
* Between zero and unlimited times, as many times as possible, giving back as needed (greedy)
)
One approach is to first replace two consecutive spaces with a comma. Then replace digit followed with a space with the same digit via capture group and a comma. Like so,
$data=#(
'-Ignored:31 Modified src data all *file MINOSFIC/UTMNUP10',
'-Ignored:33 Modified src & tgt data all *file MINOSFIC/UVEGAP10',
'-Ignored:92 Synchro is running *file MINOSFIC/VM010P50',
'-Ignored:01 Object hold (synchro) *file MINOSFIC/VM010U51')
$data | % { ($_ -replace "\s{2,}",",") -replace "(\d) ", '$1,'}
-Ignored:31,Modified src data,all *file,MINOSFIC/UTMNUP10
-Ignored:33,Modified src & tgt data,all *file,MINOSFIC/UVEGAP10
-Ignored:92,Synchro is running,*file,MINOSFIC/VM010P50
-Ignored:01,Object hold (synchro),*file,MINOSFIC/VM010U51
This would get all *file in same column as *file. Should that not be enough, use ConvertFrom-String or do another replacement to introduce the missing column. As of how, you probably need to calculate how many commas there are and deduct from that if the column is needed.
Related
Beginner here, I am working on a error log file and library, the current step I am on is to pull specific information from a txt file.
The code I have currently is...
$StatusErr = "Type 1","Type 2"
for ($i=0; $i -lt $StatusErr.length; $i++) {
get-content C:\blah\Logs\StatusErrors.TXT |
select-string $StatusErr[$i] |
add-content C:\blah\Logs\StatusErrorsresult.txt
}
while it is working, I need it to display as
Type-1-Description
2-Description
Type-1-Description
2-Description
Type-1-Description
2-Description
etc.
it is currently displaying as
Type 1 = Type-1-Description
Type 1 = Type-1-Description
Type 1 = Type-1-Description
Type 2 = 2-Description
Type 2 = 2-Description
Type 2 = 2-Description
I am unsure how to change the arrangement and remove unneeded spaces and the = sign
You need to search for both patterns in a single Select-String call in order to get matching lines in order.
While the -Pattern parameter does accept an array of patterns, in this case a single regex will do.
You need to use a regex pattern in order to capture and output only part of the lines that match.
$StatusErrRegex = '(?<=Type [12]\s*=\s*)[^ ]+'
get-content C:\blah\Logs\StatusErrors.TXT |
select-string $StatusErrRegex |
foreach-object { $_.Matches.Value } |
set-content C:\blah\Logs\StatusErrorsresult.txt
Note that I've replaced add-content with set-content, as I'm assuming you don't want to append to a preexisting file. set-content writes all objects it receives via the pipeline to the output file.
Select-String outputs Microsoft.PowerShell.Commands.MatchInfo instances whose .Matches property provides access to the part of the line that was matched.
For an explanation of the regex and the ability to experiment with it, see this regex101.com page.
Additional notes:
Select-String, like PowerShell in general, is case-insensitive by default; add the -CaseSensitive switch, if needed.
(?<=...) is a (positive) lookbehind assertion, whose matching text doesn't became part of what the regex captures.
\s* matches zero or more whitespace characters; \s+ would match one or more.
[^ ]+ matches one or more (+) characters that are not ^ spaces ( ), and thereby captures the run of non-space characters to the right of the = sign.
To match any of multiple words at the start of the pattern, use a regex alternation (|), e.g. '(?<=(type|data) [12]\s*=\s*)[^ ]+'
Here, I am new to PowerShell scripting. I am trying to do Host file entries should match every entry in a dynamic list of IP and DNS name entries from C:\Windows\System32\drivers\etc. Any mismatch will fail the audit.
entries are
10.23.24.45 foo.com
10.24.45.34 domain.com
Here is my code.
"$Pattern = '^(?<IP>\d{1,3}(\.\d{1,3}){3})\s+(?<Host>.+)$'
$File = "$env:SystemDrive\Windows\System32\Drivers\etc\hosts"
$Entries = #()
(Get-Content -Path $File) | ForEach-Object {
If ($_ -match $Pattern) {
$Entries += "$($Matches.IP) $($Matches.Host)"
Write-Host " the values are $Entries"
$FailureMessage = "IP and host entries are existing"
}
else {
$FailureMessage = "IP and host entries are doesn't existing"
}
}"
But this is not working for me. Can you help here
A hosts file can also contain comment lines or comments after the IP and Hostname parts, preceeded by a # character. Your regex at the moment does not account for that.
I would create a lookup hashtable with all required entries and use that to find whether the hosts file contains any of these or not and as result create an array of PSObjects for nice formatting and easy filtering.
Something like:
$file = "$env:SystemDrive\Windows\System32\Drivers\etc\hosts"
$pattern = '^\s*(?<IP>[0-9a-f.:]+)\s+(?<HostName>[^\s#]+)(?<Comment>.*)$'
# create an array of Hashtables with required entries
$required = #{Ip = '10.23.24.45'; HostName = 'foo.com'},
#{Ip = '10.24.45.34'; HostName = 'domain.com'}
# read the current content of the hosts file, filter only lines that match the pattern
$result = Get-Content -Path $file | Where-Object { $_ -match $pattern } | ForEach-Object {
$ip = $matches.Ip
$hostname = $matches.HostName
# test if the entry is one of the required ones
$exists = [bool]($required | Where-Object { $_.Ip -eq $ip -and $_.HostName -eq $hostname })
# output an object
[PsCustomObject]#{
IP = $ip
HostName = $hostname
Exists = $exists
}
}
# show results on screen
$result | Format-Table -AutoSize
Next you can send an email using Send-MailMessage if any of the required entries is missing
# select the entries where property Exists is False
$missing = $result | Where-Object { -not $_.Exists }
if ($missing) {
# here is where you send your mail message
}
Regex details:
^ Assert position at the beginning of a line (at beginning of the string or after a line break character)
\s Match a single character that is a “whitespace character” (spaces, tabs, line breaks, etc.)
* Between zero and unlimited times, as many times as possible, giving back as needed (greedy)
(?<IP> Match the regular expression below and capture its match into backreference with name “IP”
[0-9a-f.:] Match a single character present in the list below
A character in the range between “0” and “9”
A character in the range between “a” and “f”
One of the characters “.:”
+ Between one and unlimited times, as many times as possible, giving back as needed (greedy)
)
\s Match a single character that is a “whitespace character” (spaces, tabs, line breaks, etc.)
+ Between one and unlimited times, as many times as possible, giving back as needed (greedy)
(?<HostName> Match the regular expression below and capture its match into backreference with name “HostName”
[^\s#] Match a single character NOT present in the list below
A whitespace character (spaces, tabs, line breaks, etc.)
The character “#”
+ Between one and unlimited times, as many times as possible, giving back as needed (greedy)
)
(?<Comment> Match the regular expression below and capture its match into backreference with name “Comment”
. Match any single character that is not a line break character
* Between zero and unlimited times, as many times as possible, giving back as needed (greedy)
)
$ Assert position at the end of a line (at the end of the string or before a line break character)
I have an text file that is structured in the format below, I need to extract the Hostname, Stratum=x and Offset=y values into a structured format e.g. CSV. I intend to use the output to write windows event logs if the values meet certain thresholds. My thinking is that creating objects (e.g. the hostnames) and adding the stratum and offset values as members would allow me to achieve this, but my PowerShell skills fail me here..
___________________________________________________________________________
02/04/2020 08:11:00 : Started [TEST] Command Scrape
Text I don't care about
___________________________________________________________________________
Hostname_1 (192.168.1.254):
assID=0 status=0544 leap_none, sync_local_proto, 4 events, event_peer/strat_chg,
version="ntpd 4.2.2p1#1.1570-o Tue May 19 13:57:55 UTC 2009 (1)",
processor="x86_64", system="Linux/2.6.18-164.el5", leap=00, stratum=4,
precision=-10, rootdelay=0.000, rootdispersion=11.974, peer=59475,
refid=LOCAL(0),
reftime=d495c32c.0e71eaf2 Mon, Jan 7 2013 13:57:00.056, poll=10,
clock=d495c32c.cebd43bd Mon, Jan 7 2013 13:57:00.807, state=4,
offset=0.123, frequency=0.000, jitter=0.977, noise=0.977,
stability=0.000, tai=0
___________________________________________________________________________
Hostname_2 (10.10.1.1):
assID=0 status=0544 leap_none, sync_local_proto, 4 events, event_peer/strat_chg,
version="ntpd 4.2.2p1#1.1570-o Tue May 19 13:57:55 UTC 2009 (1)",
processor="x86_64", system="Linux/2.6.18-164.el5", leap=00, stratum=4,
precision=-10, rootdelay=0.000, rootdispersion=11.974, peer=59475,
refid=LOCAL(0),
reftime=d495c32c.0e71eaf2 Mon, Jan 7 2013 13:57:00.056, poll=10,
clock=d495c32c.cebd43bd Mon, Jan 7 2013 13:57:00.807, state=4,
offset=2.456, frequency=0.000, jitter=0.977, noise=0.977,
stability=0.000, tai=0
___________________________________________________________________________
Hostname_3 (10.10.1.2):
...
I found I can create the CSV if I manually reformat the data into keypairs (as below), using ConvertFrom-StringData and outputting to CSV;
(Get-Content 'file.txt' -Raw) -split '####' |
ForEach-Object {
$results = Convertform-StringData - StringData ($PSitem -replace '\n-\s+')
New-Object PSObject -Property $results | Select-Object Hostname, Stratum, Offset
} | Export-Csv 'file.csv' - NoTypeInformation
Hostname=Hostname_1
stratum=3
offset=-10.345
####
Hostname=Hostname_2
stratum=4
offset=-8.345
Becomes the following CSV:
"Hostname","Stratum","offset"
"Hostname_1","3","-10.345"
"Hostname_2","4","-8.345"
You could do this with the code below.
In your example, the text blocks are separated by a series of underscores. If in real life that is different, change the -split '_{2,}' accordingly.
$regex = '(?s)^\s*([\w_\d]+)\s+\(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\):.*stratum=(\d+).*offset=([\d.]+)'
$result = (Get-Content 'D:\file.txt' -Raw) -split '_{2,}' | Where-Object {$_ -match $regex} | ForEach-Object {
[PsCustomObject]#{
'Hostname' = $matches[1]
'Stratum' = $matches[2]
'Offset' = $matches[3]
}
}
# output to console
$result
#output to csv file
$result | Export-Csv -Path 'D:\file.csv' -NoTypeInformation
Output on screen:
Hostname Stratum Offset
-------- ------- ------
Hostname_1 4 0.123
Hostname_2 4 2.456
Output as CSV:
"Hostname","Stratum","Offset"
"Hostname_1","4","0.123"
"Hostname_2","4","2.456"
Regex details:
^ Assert position at the beginning of the string
\s Match a single character that is a “whitespace character” (spaces, tabs, line breaks, etc.)
* Between zero and unlimited times, as many times as possible, giving back as needed (greedy)
( Match the regular expression below and capture its match into backreference number 1
[\w_\d] Match a single character present in the list below
A word character (letters, digits, etc.)
The character “_”
A single digit 0..9
+ Between one and unlimited times, as many times as possible, giving back as needed (greedy)
)
\s Match a single character that is a “whitespace character” (spaces, tabs, line breaks, etc.)
+ Between one and unlimited times, as many times as possible, giving back as needed (greedy)
\( Match the character “(” literally
\d Match a single digit 0..9
{1,3} Between one and 3 times, as many times as possible, giving back as needed (greedy)
\. Match the character “.” literally
\d Match a single digit 0..9
{1,3} Between one and 3 times, as many times as possible, giving back as needed (greedy)
\. Match the character “.” literally
\d Match a single digit 0..9
{1,3} Between one and 3 times, as many times as possible, giving back as needed (greedy)
\. Match the character “.” literally
\d Match a single digit 0..9
{1,3} Between one and 3 times, as many times as possible, giving back as needed (greedy)
\) Match the character “)” literally
: Match the character “:” literally
. Match any single character
* Between zero and unlimited times, as many times as possible, giving back as needed (greedy)
stratum= Match the characters “stratum=” literally
( Match the regular expression below and capture its match into backreference number 2
\d Match a single digit 0..9
+ Between one and unlimited times, as many times as possible, giving back as needed (greedy)
)
. Match any single character
* Between zero and unlimited times, as many times as possible, giving back as needed (greedy)
offset= Match the characters “offset=” literally
( Match the regular expression below and capture its match into backreference number 3
[\d.] Match a single character present in the list below
A single digit 0..9
The character “.”
+ Between one and unlimited times, as many times as possible, giving back as needed (greedy)
)
Fantastic, thank you so much. I've made the following adjustments to accommodate non-word characters in the hostname and offset, and exclude the trailing comma on the offset values.
$regex = '(?s)^\s*([\w_\d]+)\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):.*stratum=(\d+).*offset=([\d.]+)'
is now:
(?s)^\s*([\w|\W|\d]+)\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):.*stratum=(\d+).*offset=([\W|\d.][^,]
I have a file named: test_file.txt. The second line has 4 pipe delimiters and all other lines except 2nd line has 3 pipe delimiters.
I just want to output line 2 since it has one more delimiter than other lines.
$colCnt = "C:\test.txt"
[int]$LastSplitCount = $Null
Get-Content $colCnt | ?{$_} | Select -Skip 1 | %{if($LastSplitCount -and !
($_.split("|").Count -eq $LastSplitCount))
{"Process stopped at line number $($_.psobject.Properties.value[5]) for column count mis-match.";break}
elseif(!$LastSplitCount){$LastSplitCount = $_.split("|").Count}}
If your text file looks anything like this:
blah|using|three|delimiters
blah|using|four |delimiter |characters
blah|using|three|delimiters
blah|using|four |delimiter |characters
blah|using two |delimiters
The the following code should output the lines with more (or less) than 3 | delimiters:
$line = 0
switch -Regex -File "C:\test.txt" {
'^(?:[^|]*\|){3}[^|]*$' { $line++ } # this line is OK, just increase the line counter
default { "Bad delimiter count in line {0}: '{1}'" -f ++$line, $_ }
}
Output:
Bad delimiter count in line 2: 'blah|using|four |delimiter |characters'
Bad delimiter count in line 4: 'blah|using|four |delimiter |characters'
Bad delimiter count in line 5: 'blah|using two |delimiters'
Regex details:
^ Assert position at the beginning of the string
(?: Match the regular expression below
[^|] Match any character that is NOT a “|”
* Between zero and unlimited times, as many times as possible, giving back as needed (greedy)
\| Match the character “|” literally
){3} Exactly 3 times
[^|] Match any character that is NOT a “|”
* Between zero and unlimited times, as many times as possible, giving back as needed (greedy)
$ Assert position at the end of the string (or before the line break at the end of the string, if any)
I have a big file consists of "before" and "after" cases for every item as follows:
case1 (BEF) ACT
(AFT) BLK
case2 (BEF) ACT
(AFT) ACT
case3 (BEF) ACT
(AFT) CLC
...
I need to select all of the strings which have (BEF) ACT on the "first" string and (AFT) BLK on the "second" and place the result to a file.
The idea is to create a clause like
IF (stringX.LineNumber consists of "(BEF) ACT" AND stringX+1.LineNumber consists of (AFT) BLK)
{OutFile $stringX+$stringX+1}
Sorry for the syntax, I've just starting to work with PS :)
$logfile = 'c:\temp\file.txt'
$matchphrase = '\(BEF\) ACT'
$linenum=Get-Content $logfile | Select-String $matchphrase | ForEach-Object {$_.LineNumber+1}
$linenum
#I've worked out how to get a line number after the line with first required phrase
Create a new file with a result as follows:
string with "(BEF) ACT" following with a string with "(AFT) BLK"
Select-String -SimpleMatch -CaseSensitive '(BEF) ACT' c:\temp\file.txt -Context 0,1 |
ForEach-Object {
$lineAfter = $_.Context.PostContext[0]
if ($lineAfter.Contains('(AFT) BLK')) {
$_.Line, $lineAfter # output
}
} # | Set-Content ...
-SimpleMatch performs string-literal substring matching, which means you can pass the search string as-is, without needing to escape it.
However, if you needed to further constrain the search, such as to ensure that it only occurs at the end of a line ($), you would indeed need a regular expression with the (implied) -Pattern parameter: '\(BEF\) ACT$'
Also note PowerShell is generally case-insensitive by default, which is why switch -CaseSensitive is used.
Note how Select-String can accept file paths directly - no need for a preceding Get-Content call.
-Context 0,1 captures 0 lines before and 1 line after each match, and includes them in the [Microsoft.PowerShell.Commands.MatchInfo] instances that Select-String outputs.
Inside the ForEach-Object script block, $_.Context.PostContext[0] retrieves the line after the match and .Contains() performs a literal substring search in it.
Note that .Contains() is a method of the .NET System.String type, and such methods - unlike PowerShell - are case-sensitive by default, but you can use an optional parameter to change that.
If the substring is found on the subsequent line, both the line at hand and the subsequent one are output.
The above looks for all matching pairs in the input file; if you only wanted to find the first pair, append | Select-Object -First 2 to the Select-String call.
Another way of doing this is to read the $logFile in as a single string and use a RegEx match to get the parts you want:
$logFile = 'c:\temp\file.txt'
$outFile = 'c:\temp\file2.txt'
# read the content of the logfile as a single string
$content = Get-Content -Path $logFile -Raw
$regex = [regex] '(case\d+\s+\(BEF\)\s+ACT\s+\(AFT\)\s+BLK)'
$match = $regex.Match($content)
($output = while ($match.Success) {
$match.Value
$match = $match.NextMatch()
}) | Set-Content -Path $outFile -Force
When used the result is:
case1 (BEF) ACT
(AFT) BLK
case7 (BEF) ACT
(AFT) BLK
Regex details:
( Match the regular expression below and capture its match into backreference number 1
case Match the characters “case” literally
\d Match a single digit 0..9
+ Between one and unlimited times, as many times as possible, giving back as needed (greedy)
\s Match a single character that is a “whitespace character” (spaces, tabs, line breaks, etc.)
+ Between one and unlimited times, as many times as possible, giving back as needed (greedy)
\( Match the character “(” literally
BEF Match the characters “BEF” literally
\) Match the character “)” literally
\s Match a single character that is a “whitespace character” (spaces, tabs, line breaks, etc.)
+ Between one and unlimited times, as many times as possible, giving back as needed (greedy)
ACT Match the characters “ACT” literally
\s Match a single character that is a “whitespace character” (spaces, tabs, line breaks, etc.)
+ Between one and unlimited times, as many times as possible, giving back as needed (greedy)
\( Match the character “(” literally
AFT Match the characters “AFT” literally
\) Match the character “)” literally
\s Match a single character that is a “whitespace character” (spaces, tabs, line breaks, etc.)
+ Between one and unlimited times, as many times as possible, giving back as needed (greedy)
BLK Match the characters “BLK” literally
)
My other answer completes your own Select-String-based solution attempt. Select-String is versatile, but slow, though it is appropriate for processing files too large to fit into memory as a whole, given that it processes files line by line.
However, PowerShell offers a much faster line-by-line processing alternative: switch -File - see the solution below.
Theo's helpful answer, which reads the entire file into memory first, will probably perform best overall, depending on file size, but it comes at the cost of increased complexity, due to relying heavily on direct use of .NET functionality.
$(
$firstLine = ''
switch -CaseSensitive -Regex -File t.txt {
'\(BEF\) ACT' { $firstLine = $_; continue }
'\(AFT\) BLK' {
# Pair found, output it.
# If you don't want to look for further pairs,
# append `; break` inside the block.
if ($firstLine) { $firstLine, $_ }
# Look for further pairs.
$firstLine = ''; continue
}
default { $firstLine = '' }
}
) # | Set-Content ...
Note: The enclosing $(...) is only needed if you want to send the output directly to the pipeline to a cmdlet such as Set-Content; it is not needed for capturing the output in a variable: $pair = switch ...
-Regex interprets the branch conditionals as regular expressions.
$_ inside a branch's action script block ({ ... } refers to the line at hand.
The overall approach is:
$firstLine stores the 1st line of interest once found, and when the 2nd line's pattern is found and $firstLine is set (is nonempty), the pair is output.
The default handler resets $firstLine, to ensure that only two consecutive lines that contain the strings of interest are considered.