2

I have a fairly long string in PowerShell that I need to split. Each section begins with a date in format mm/dd/yyyy hh:mm:ss AM. Essentially what I am trying to do is get the most recent message in the string. I don't need to keep the date/time part as I already have that elsewhere.

This is what the string looks like:

10/20/2018 1:22:33 AM
Some message the first one in the string
It can be several lines long
With multiple line breaks
But this is still the first message in the string
10/21/2018 4:55:11 PM
This would be second message
Same type of stuff
But its a different message

I know how to split a string on specific characters, but I don't know how on a pattern like date/time.

asked Oct 22, 2018 at 23:50
2
  • You could use Select-String to determine the position of the date strings and then use the .SubString() - method to extract whatever you're after. Commented Oct 23, 2018 at 0:45
  • You can also just select the first 2 lines etc.. if it's a [string[]] if it's one long string, you could try splitting with regex. $string -split "\r\n" | select -first 2 play around and see what comes out. A lot of times when assigning a string, it can be helpful to specify the type beforehand. [string[]]$string = cmd to get string Commented Oct 23, 2018 at 1:32

4 Answers 4

3

Note:

  • The solution below assumes that the section are not necessarily chronologically ordered so that you must inspect all time stamps to determine the most recent one.
  • If, by contrast, you can assume that the last message is the most recent one, use LotPings' much simpler answer.

If you don't know ahead of time what section has the most recent time stamp, a line-by-line approach is probably best:

$dtMostRecent = [datetime] 0
# Split the long input string ($longString) into lines and iterate over them.
# If input comes from a file, replace 
# $longString -split '\r?\n'
# with
# Get-Content file.txt
# If the file is large, replace the whole command with
# Get-Content file.txt | ForEach-Object { ... } 
# and replace $line with $_ in the script block (loop body).
foreach ($line in $longString -split '\r?\n') {
 # See if the line at hand contains (only) a date. 
 if ($dt = try { [datetime] $line } catch {}) {
 # See if the date at hand is the most recent so far.
 $isMostRecent = $dt -ge $dtMostRecent
 if ($isMostRecent) {
 # Save this time stamp as the most recent one and initialize the
 # array to collect the following lines in (the message).
 $dtMostRecent = $dt 
 $msgMostRecentLines = @()
 }
 } elseif ($isMostRecent) {
 # Collect the lines of the message associated with the most recent date.
 $msgMostRecentLines += $line
 }
}
# Convert the message lines back into a single, multi-line string.
# $msgMostRecent now contains the multi-line message associated with
# the most recent time stamp.
$msgMostRecent = $msgMostRecentLines -join "`n"

Note how try { [datetime] $line } catch {} is used to try to convert a line to a [datetime] instance and fail silently, if it can't, in which case $dt is assigned $null, which in a Boolean context is interpreted as $False.

This technique works irrespective of the culture currently in effect, because PowerShell's casts always use the invariant culture when casting from strings, and the dates in the input are in one of the formats the invariant culture understands.

By contrast, the -as operator, whose use would be more convenient here - $dt =$line -as [datetime] - unexpectedly is culture-sensitive, as Esperento57 points out.
This surprising behavior is discussed in this GitHub issue.

answered Oct 23, 2018 at 3:45
Sign up to request clarification or add additional context in comments.

Comments

2

Provided the [datetime] sections are ascending,
it should be sufficient to split on them with a RegEx and get the last one

((Get-Content .\test.txt -Raw) -split "\d+/\d+/\d{4} \d+:\d+:\d+ [AP]M`r?`n")[-1]

Output based on your sample string stored in file test.txt

This would be second message
Same type of stuff
But its a different message
answered Oct 23, 2018 at 10:15

4 Comments

Nicely done; if you can assume the sections to be chronologically ordered (which sounds like a fair assumption), this is by far the simplest solution.
Nothing says that the file is written sequentially, the order must be according to a date and not the writing order of the file
@Esperento57 Well my answer begins with Provided ... and that assumption seems to be proven right by the OP accepting the answer. If something looks, smells and feels like a log file with a datetime stamp it most likely is one.
That why i dont have put a -1 ;)
1

you can split it by timestamp pattern like this:

$arr = $str -split "[0-9]{1,2}/[0-9]{1,2}/[0-9]{1,4} [0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2} [AaPp]M\n"
answered Oct 23, 2018 at 1:49

Comments

-2

To my knowledge you can't use any of the static String methods like Split() for this. I tried to find a regular expression that would handle the entire thing, but wasn't able to come up with anything that would quite break it up properly.

So, you'll need to go line by line, testing to see if it that line is a date, then concatenate the lines in between like the following:

$fileContent = Get-Content "inputFile.txt"
$messages = @()
$currentMessage = [string]::Empty
foreach($line in $fileContent)
{
 if ([Regex]::IsMatch($line, "\d{1,2}/\d{1,2}/\d{4} \d{1,2}:\d{2}:\d{2} (A|P)M"))
 {
 # The current line is a date, the current message is complete
 # Add the current message to the output, and clear out the old message 
 # from your temporary storage variable $currentMessage
 if (-not [string]::IsNullOrEmpty($currentMessage))
 {
 $messages += $currentMessage
 $currentMessage = [string]::Empty
 }
 }
 else
 {
 # Add this line to the message you're building.
 # Include a new line character, as it was stripped out with Get-Content
 $currentMessage += "$line`n"
 }
}
# Add the last message to the output
$messages += $currentMessage
# Do something with the message
Write-Output $messages

As the key to all of this is recognizing that a given line is a date and therefore the start of a message, let's look a bit more at the regex. "\d" will match any decimal character 0-9, and the curly braces immediately following indicate the number of decimal characters that need to match. So, "\d{1,2}" means "look for one or two decimal characters" or in this case the month of the year. We then look for a "/", 1 or 2 more decimal characters - "\d{1,2}", another "/" and then exactly 4 decimal characters - "\d{4}". The time is more of the same, with ":" in between the decimal characters instead of "/". At the end, there will either be "AM" or "PM" so we look for either an "A" or a "P" followed by an "M", which as a regular expression is "(A|P)M".

Combine all of that, and you get "\d{1,2}/\d{1,2}/\d{4} \d{1,2}:\d{2}:\d{2} (A|P)M" to determine if you have a date on that line. I believe it would also be possible to use[DateTime]::Parse() to determine if the line is a date, but then you wouldn't get to have fun with Regex's and would need a try-catch. For more info on Regex's in Powershell (which are just the .NET regex) see .NET Regex Quick Reference

mklement0
450k68 gold badges722 silver badges975 bronze badges
answered Oct 23, 2018 at 4:00

2 Comments

PowerShell has the -split operator, which is regex-based by default (you could also use [regex]::Split(), but -split is easier). LotPings' answer shows -split in action, which makes for the most concise solution, if you can assume the sections to be chronologically ordered; note that the OP only wants the most recent section's message, whereas your answer collects them all. The line-by-line solution is only needed if chronological order cannot be assumed, and then you'd have to compare time stamps to find the most recent section (which my answer does, which is otherwise similar).
Nothing says that the file is written sequentially, the order must be according to a date and not the writing order of the file

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.