PowerShell regex to pick out elapsed time - regex

I am new to powershell scripting and am not so good with Regex...
I want to create a regular expression that will pick out time from the following text file...
gfskf dakdshadk daksdkdahkd daksdhasdkh () zadf sflh f.d / sd lhlfhlj f 12hrs:10mins:05sec fsfsf fsfjhsfjh
I want to get the hours so 12 and mins as 10 and seconds as 5.
$hour= [regex]::match($line,$hour_regex).Groups[1].Value
$mins= [regex]::match($line,$mins_regex).Groups[1].Value
$sec= [regex]::match($line,$sec_regex).Groups[1].Value
So essentially I need three regular expressions to extract the relevant data from the file.
Thanks in advance!

Use one regex:
$r = '(\d+)hrs:(\d+)mins:(\d+)sec'
$i = 'hlfhlj f 12hrs:10mins:05sec fsfsf f'
$result = [regex]::match($i,$r)
$hour = $result.Groups[1].Value
$mins = $result.Groups[2].Value
$sec = $result.Groups[3].Value

Just for fun:
$a = "gfskf dakdshadk daksdkdahkd daksdhasdkh () zadf sflh f.d / sd lhlfhlj f 12hrs:10mins:05sec fsfsf fsfjhsfjh"
$hour,$min, $sec = $a -split '(\d\d)' | ? { $_ -match '\d\d' }

Since nobody mentioned it yet, you can also use the -match operator. The submatches can be accessed via the $matches hashtable:
Get-Content "C:\path\to\your.txt" | ? {
$_ -match '(\d+)hrs:(\d+)mins:(\d+)sec'
} | % {
$h = $matches[1]
$m = $matches[2]
$s = $matches[3]
"{0}:{1}:{2}" -f ($h, $m, $s)
}

Use regex capture groups. "(\d+)hrs:(\d+)mins:(\d+)sec" will capture the numbers into groups that can be accessed. Save the results in a MatchCollection to check the results. Like so,
$line = "sflh f.d / sd lhlfhlj f 12hrs:10mins:05sec fsfsf"
PS C:\> $mc = [regex]::match($line, "(\d+)hrs:(\d+)mins:(\d+)sec")
PS C:\> $mc.Groups[1].value
12
PS C:\> $mc.Groups[2].value
10
PS C:\> $mc.Groups[3].value
05

You can try :
$b= [regex]".* (\d*)hrs:(\d*)mins:(\d*)sec.*"
$hours=$b.Match($a).groups[1].value
$minutes=$b.Match($a).groups[2].value
$seconds=$b.Match($a).groups[3].value

if like me you are not at ease with regex you can use split function :
PS>$t="gfskf dakdshadk daksdkdahkd daksdhasdkh () zadf sflh f.d / sd lhlfhlj f 12hrs:10mins:05sec fsfsf fsfjhsfjh"
PS>$slices=$t.split(#(" ",":"))
$hours=($slices[12]).substring(0,2)
$mins=($slices[13]).substring(0,2)
$secs=($slices[14]).substring(0,2)

PS> $s -replace '^.+ (\d+hrs:\d+mins:\d+sec).*$','$1' -split '\D+' | where {$_}
12
10
05

Related

Powershell: Replace only fist occurence of a line/string in entire file

I have following beggining of a Powershell script in which I would like to replace the values of variables for different enviroment.
$SomeVar1 = "C:\path\to\file\a"
$SomeVar1 = "C:\path\to\file\a" # Copy for test - Should not be rewriten
$SomeVar2 = "C:\path\to\file\b"
# Note $SomeVar1 = "C:\path\to\file\a" - Should not be rewriten
When I run the rewrite script, the result should look like this:
$SomeVar1 = "F:\different\path\to\file\a"
$SomeVar1 = "C:\path\to\file\a" # Copy for test - Should not be rewrite
$SomeVar2 = "F:\different\path\to\file\b"
# Note $SomeVar1 = "C:\path\to\file\a" - Should not be rewriten
Current script that does(n't) rewrite:
$arr = #(
[PSCustomObject]#{Regex = '$SomeVar1 = "'; Replace = '$SomeVar1 = "F:\different\path\to\file\a"'}
[PSCustomObject]#{Regex = '$SomeVar2 = "'; Replace = '$SomeVar1 = "F:\different\path\to\file\b"'}
)
for ($i = 0; $i -lt $arr.Length; $i++) {
$ArrRegex = [Regex]::Escape($arr[$i].Regex)
$ArrReplace = $arr[$i].Replace
# Get full line for replacement
$Line = Get-Content $Workfile | Select-String $ArrRegex | Select-Object -First 1 -ExpandProperty Line
# Rewrite part
$Line = [Regex]::Escape($Line)
$Content = Get-Content $Workfile
$Content -replace "^$Line",$ArrReplace | Set-Content $Workfile
}
This replaces all the occurences in file on the start of the line (and I need only the 1st one) and doest not replace the one in Note which is okay.
Then I found this Powershell: Replace last occurence of a line in a file which does the exact oposite of what I need, only rewrites the last occurence of the string and it does it in the Note aswell and I would somehow like to change it to do the opposite - 1st occurence, line begining (Wont target the Note)
Code in my case looks like this:
# Rewrite part
$Line = [Regex]::Escape($Line)
$Content = Get-Content $Workfile -Raw
$Line = "(?s)(.*)$Line"
$ArrReplace = "`$1$ArrReplace"
$Content -replace $Line,$ArrReplace | Set-Content $Workfile
Do you have any recommendations on how to archive my goal, or is there a more sothisticated way to replace variables for powershell scripts like this?
Thanks in advance.
So I finally figured it out, I had to add Select-String "^$ArrRegex" during $Line creation to exclude any string that were on on line beggining and then use this Regex to do the job: ^(?s)(.*?\n)$Line
In my case it does the following: Only selects 1st occurnece on the beggining of the line and replaces it. It ignores everything else and when re-run, does not rewrite others. The copies of vars will not really exist in final version and will be set once like $Var1 = "Value" and never changed during script, but I wanted to be sure that I won't replace something by mistake.
The final replacing part does look like this:
for ($i = 0; $i -lt $arr.Length; $i++) {
$ArrRegex = [Regex]::Escape($arr[$i].Regex)
$ArrReplace = $arr[$i].Replace
$Line = Get-Content $Workfile | Select-String "^$ArrRegex" | Select-Object -First 1 -ExpandProperty Line
$Line = [Regex]::Escape($Line)
$Line = "^(?s)(.*?\n)$Line"
$ArrReplace = "`$1$ArrReplace"
$Content -replace $Line, $ArrReplace | Set-Content $Workfile
}
You could possibly use flag variables like below to only do the first replacement for each of your regex patterns.
$Altered = Get-Content -Path $Workfile |
Foreach-Object {
if(-not $a) { #If replacement hasn't been done, replace
$_ = $_ -replace 'YOUR_REGEX1','YOUR_REPLACEMENT1'
if($_ -match 'YOUR_REPLACEMENT1') { $a = 'replacement done' } #Set Flag
}
if(-not $b) { #If replacement hasn't been done, replace
$_ = $_ -replace 'YOUR_REGEX2','YOUR_REPLACEMENT2'
if($_ -match 'YOUR_REPLACEMENT2') { $b = 'replacement done' } #Set Flag
}
$_ # Pipe back to $Altered
}
$Altered | Set-Content -Path $WorkFile
Just reverse the RegEx, if that is what you are after:
Clear-Host
#'
abc
abc
abc
'# -replace '^(.*?)\babc\b', '$1HelloWorld'
# Results
<#
HelloWorld
abc
abc
#>

I want to split a string from : to \n in Powershell script

I am using a config file that contains some information as shown below.
User1:xyz#gmail.com
User1_Role:Admin
NAME:sdfdsfu4343-234324-ffsdf-34324d-dsfhdjhfd943
ID:xyz#abc-demo-test-abc-mssql
Password:rewrfsdv34354*fds*vdfg435434
I want to split each value from*: to newline* in my Powershell script.
I am using -split '[: \n]' it matches perfectly until there is no '' in the value. If there is an '*' it will fetch till that. For example, for Password, it matches only rewrfsdv34354. Here is my code:
$i = 0
foreach ($keyOrValue in $Contents -split '[: *\n]') {
if ($i++ % 2 -eq 0) {
$varName = $keyOrValue
}
else {
Set-Variable $varName $keyOrValue
}
}
I need to match all the chars after : to \n. Please share your ideas.
It's probably best to perform two separate splits here, it makes things easier to work out if the code is going wrong for some reason, although the $i % 2 -eq 0 part is a neat way to pick up key/value.
I would go for this:
# Split the Contents variable by newline first
foreach ($line in $Contents -split '[\n]') {
# Now split each line by colon
$keyOrValue = $line -split ':'
# Then set the variables based on the parts of the colon-split
Set-Variable $keyOrValue[0] $keyOrValue[1]
}
You could also convert to a hashmap and go from there, e.g.:
$h = #{}
gc config.txt | % { $key, $value = $_ -split ' *: *'; $h[$key] = $value }
Or with ConvertFrom-StringData:
$h = (gc -raw dims.txt) -replace ':','=' | ConvertFrom-StringData
Now you have convenient access to keys and values, e.g.:
$h
Output:
Name Value
---- -----
Password rewrfsdv34354*fds*vdfg435434
User1 xyz#gmail.com
ID xyz#abc-demo-test-abc-mssql
NAME sdfdsfu4343-234324-ffsdf-34324d-dsfhdjhfd943
User1_Role Admin
Or only keys:
$h.keys
Output:
Password
User1
ID
NAME
User1_Role
Or only values:
$h.values
Output:
rewrfsdv34354*fds*vdfg435434
xyz#gmail.com
xyz#abc-demo-test-abc-mssql
sdfdsfu4343-234324-ffsdf-34324d-dsfhdjhfd943
Admin
Or specific values:
$h['user1'] + ", " + $h['user1_role']
Output:
xyz#gmail.com, Admin
etc.

Powershell use ForEach to match and replace string with regex and replace with incremental value

I have to replace multiple strings with the same pattern, and several strings are on the same line. The replacement value should be incremental. I need to match and replace only the pattern as in the example, not requesId, nor messageId.
Input:
<requestId>qwerty-qwer12-qwer56</requestId>Ace of Base Order: Q2we45-Uj87f6-gh65De<something else...
<requestId>zxcvbn-zxcv4d-zxcv56</requestId>
<requestId>1234qw-12qw9x-123456</requestId> Stevie Wonder <messageId>1234qw-12qw9x-123456</msg
reportId>plmkjh8765FGH4rt6As</msg:reportId> something <keyID>qwer1234asdf5678zxcv0987bnml65gh</msgdc
The desired output should be:
<requestId>Request-1</requestId>Ace of Base Order: Request-2<something else...
<requestId>Request-3</requestId>
<requestId>Request-4</requestId> Stevie Wonder <messageId>Request-4</msg
reportId>ReportId-1</msg:reportId> something <keyId>KeyId-1</msg
The regex finds all matching values but I cannot make the loop and replace these values. The code I am trying to make work is:
#'
<requestId>qwerty-qwer12-qwer56</requestId>Ace of Base Order: Q2we45-Uj87f6-gh65De<something else...
<requestId>zxcvbn-zxcv12-zxcv56</requestId>
<requestId>1234qw-12qw12-123456</requestId> Stevie Wonder <messageId>1234qw-12qw12-123456</msg
reportId>plmkjh8765FGH4rt6As</msg:reportId> something <keyID>qwer1234asdf5678zxcv0987bnml65gh</msgdc
'# | Set-Content $log -Encoding UTF8
$requestId = #{
Count = 1
Matches = #()
}
$tmp = Get-Content $log | foreach { $n = [regex]::matches((Get-Content $log),'\w{6}-\w{6}-\w{6}').value
if ($n)
{
$_ -replace "$n", "Request-$($requestId.count)"
$requestId.count++
} $_ }
$tmp | Set-Content $log
You want Regex.Replace():
$requestId = 1
$tmp = Get-Content $log |ForEach-Object {
[regex]::Replace($_, '\w{6}-\w{6}-\w{6}', { 'Request-{0}' -f ($script:requestId++) })
}
$tmp |Set-Content $log
The script block will run once per match to calculate the substitue value, allowing us to resolve and increment the $requestId variable, resulting in the consecutive numbering you need.
You can do this for multiple patterns in succession if necessary, although you may want to use an array or hashtable for the individual counters:
$counters = { requestId = 1; keyId = 1 }
$tmp = Get-Content $log |ForEach-Object {
$_ = [regex]::Replace($_, '\w{6}-\w{6}-\w{6}', { 'Request-{0}' -f ($counters['requestId']++) })
[regex]::Replace($_, '\b\w{32}\b', { 'Key-{0}' -f ($counters['keyId']++) })
}
$tmp |Set-Content $log
If you want to capture and the mapping between the original and the new value, do that inside the substitution block:
$translations = #{}
# ...
[regex]::Replace($_, '\w{6}-\w{6}-\w{6}', {
# capture value we matched
$original = $args[0].Value
# generate new value
$substitute = 'Request-{0}' -f ($counters['requestId']++)
# remember it
$translations[$substitute] = $original
return $substitute
})
In PowerShell 6.1 and newer versions, you can also do this directly with the -replace operator:
$requestId = 0
$tmp = Get-Content $log |ForEach-Object {
$_ -replace '\w{6}-\w{6}-\w{6}', { 'Request-{0}' -f ($requestId++) }
}
$tmp |Set-Content $log

PowerShell Replace number in string

I'm not such a regex expert and frankly I'm trying to avoid it whenever I can.
I would like to create a new $String where the number within the string is updated with +1. This number can be one or two digits and will always be between 2 brackets.
From:
$String = "\\Server\c$\share_1\share2_\Taget2[1] - 2014-07-29.log"
To:
$String = "\\Server\c$\share_1\share2_\Taget2[2] - 2014-07-29.log"
Thank you for your help.
If you want to avoid the regex:
$String = "\\Server\c$\share_1\share2_\Taget2[1] - 2014-07-29.log"
$parts = $string.Split('[]')
$Newstring = '{0}[{1}]{2}' -f $parts[0],(1 + $parts[1]),$parts[2]
$Newstring
\\Server\c$\share_1\share2_\Taget2[2] - 2014-07-29.log
Another option is using the Replace() method of the Regex class with a scriptblock (code taken from this answer by Roman Kuzmin):
$callback = {
$v = [int]$args[0].Groups[1].Value
$args[0] -replace $v,++$v
}
$filename = "\\Server\c$\share_1\share2_\Taget2[1] - 2014-07-29.log"
$re = [Regex]"\[(\d+)\]"
$re.Replace($filename, $callback)
Existing files could be handled like this:
...
$re = [Regex]"\[(\d+)\]"
while (Test-Path -LiteralPath $filename) {
$filename = $re.Replace($filename, $callback)
}
Note that you must use Test-Path with the parameter -LiteralPath here, because your filename contains square brackets, which would otherwise be interpreted as wildcard characters.
With regex:
$s = "\\Server\c$\share_1\share2_\Taget2[1] - 2014-07-29.log"
$s -replace "(?<=\[)(\d+)","bla"
Result:
\\Server\c$\share_1\share2_\Taget2[bla] - 2014-07-29.log
So you can do something like this:
$s = "\\Server\c$\share_1\share2_\Taget2[1] - 2014-07-29.log"
$s -match "(?<=\[)(\d+)" | Out-Null ## find regex matches
$newNumber = [int]$matches[0] + 1
$s -replace "(?<=\[)(\d+)",$newNumber
Result:
\\Server\c$\share_1\share2_\Taget2[2] - 2014-07-29.log

Regex replace using a substring of the regex result value

I've been reading a ton of material and thought I had found my solution but no luck. I need to find apostrophes contained in a name and then replace them with a double. I am loading a file to an array and then looping through that, looking for the apostrophes. The catch is that each row can have several apostrophes so that's why it's not a simple find and replace.
Here is a sample of the file:
create(xxxxxxx)using(xxxxxxx)name('O'Doe, John')
replace(xxxxxxx)instdata('ab 1234 ')
create(xxxxxxx)using(xxxxxxx)name('Doe, O'Jane')
replace(xxxxxxx)instdata('ab 5678 ')
There are other lines inbetween but they don't contain apostrophes.
Here is what I have so far:
$Pattern = "[A-Z]'[A-Z]"
$user = gc C:\Temp\mfnewuser.ins
for ($i = 0; $i -lt $user.count; $i++) {
if ($user[$i] -match $Pattern) {
$user[$i] = [regex]::replace($strText, $Pattern.substring(2,1), "''")
$user | out-file C:\Temp\mfnewuser.ins
}
}
I'm looking for a capital letter, followed by an apostrophe, followed by another capital. Because of the other commas, I can't just do a global replace. I know my pattern matching is working but I can't seem to manipulate it with the substring. The substring looks at $Pattern as a string instead of the result of a regex. If I can save the regex result to a variable, that would be great. I think then the replace would be easy.
Tried this as well but no luck either:
$Pattern = "[A-Z]'[A-Z]"
$NewPattern = "[A-Z]''[A-Z]"
$f = Get-Content C:\Temp\mfnewuser.ins
$f = $f -replace $Pattern, $NewPattern
$f | out-file C:\Temp\mfnewuser.ins
I may be approaching this all wrong and there is an easier way but I haven't seen anything yet.
EDIT:
Based on Bill_Stewarts example below, I've got this to work on the First Name but not yet the Last Name:
$Pattern = "[A-Z]'[A-Z]"
$user = gc C:\Temp\mfnewuser.ins
for ($i = 0; $i -lt $user.count; $i++) {
if ($user[$i] -match $Pattern) {
$user[$i] = $user[$i] -replace "(.*[A-Z])'([A-Z]+.*)", "`$1''`$2"
$user | out-file C:\Temp\mfnewuser.ins
}
}
Perhaps something like this?
get-content "test.txt" | foreach-object {
$_ -replace "([A-Z])'([A-Z])", "`$1''`$2"
}
Regular expressions can be grouped using ( ) and the -replace operator supports substring replacement ($1 and $2).
Replace your line, with the following.
$user[$i] = $user[$i] -replace "([A-Z])'([A-Z])", "`$1`''`$2"
Or try one of the following. This should suffice.
get-content "mfnewuser.ins" | foreach-object {
$_ -replace "([A-Z])'([A-Z])", "`$1`''`$2"
} | set-content "mfnewuser.ins"
...
get-content "mfnewuser.ins" | foreach-object {
$_ -replace "([a-zA-Z', ]+)'([a-zA-Z', ]+)", "`$1`''`$2"
} | set-content "mfnewuser.ins"