Converting to Powershell - regex

I have this perl code I am trying to convert to powershell.
foreach my $f ( #file_list ) {
if( $f =~ /^([\d]+)_${base_ver}_([\d]{4})\.zip$/i ){
$pfile = $f;
( $pnum, $pdate ) = ( $pfile =~ /^([\d]+)_${base_ver}_([\d]{4})\.zip$/i ); # need to convert
( $pmon, $pyear ) = ( $pdate =~ /^(\d\d)(\d\d)$/ ); # need to convert
if( ($patch_nbr == 0) || ($patch_nbr == $pnum) ) {
$fcnt++;
}
}
}
I've converted most of it here..
$file_list = Get-ChildItem -force $base_dir
$file_list | foreach-object {
if($_ -match "/^([\d]+)_${base_ver}_([\d]{4})\.zip$/i"){
$pfile = $_
if($patch_nbr -eq 0 -or $pacth_nbr -eq $pnum){
$fcnt++
}
}
}
Not quite sure how to convert the two variables that equal the regex or if there is a better way to convert the perl code to powershell than what I already have.

The [mode]/pattern/[replace/][options] syntax from perl doesn't apply to regex in PowerShell.
Thus, your pattern
/^([\d]+)_${base_ver}_([\d]{4})\.zip$/i
becomes
^([\d]+)_${base_ver}_([\d]{4})\.zip$
(i is unnecessary, -match resolves to -imatch (case-insensitive match) by default)
To capture the number prefix and date, you can use a named capture group ((?<name>pattern)):
^(?<num>[\d]+)_${base_ver}_(?<date>[\d]{4})\.zip$
You can then grab the match from $Matches["name"]:
if($f -match "^(?<num>[\d]+)_${base_ver}_(?<date>[\d]{4})\.zip$"){
$pfile = $f
$pnum = $Matches["num"]
$pdate = $Matches["date"]
$pmon = -join $pdate[0..1]
$pyear = -join $pdate[2..3]
}
You could also change the regex pattern to capture the month and year individually:
if($f -match "^(?<num>[\d]+)_${base_ver}_(?<month>[\d]{2})(?<year>[\d]{2})\.zip$"){
$pfile = $f
$pnum = $Matches["num"]
$pmon = $Matches["month"]
$pyear = $Matches["year"]
}

I would put a Where-Object filter first. That allows you to use the $matches collection in the subsequent ForEach-Object without a second -match in a nested if statement. If you also change the date pattern from (\d{4}) to (\d{2})(\d{2}) you can assign $pnum, $pmon, and $pyear in a single statement. You could also simplify the condition for incrementing $fcnt. Instead of checking if $patch_nbr equals one of two values you could check if it's contained in an array of the two values.
Get-ChildItem -Force $base_dir |
Where-Object { $_ -match '^(\d+)_${base_ver}_(\d{2})(\d{2})\.zip$' } |
ForEach-Object {
$pnum, $pmon, $pyear = $matches[1..3]
if (0, $pnum -contains $patch_nbr) { $fcnt++ }
}
}
Of course, if all you want to do is count the number of files matching a given patch number, you could just do something like this:
$f = Get-ChildItem -Force $base_dir | Where-Object {
$_ -match '^(\d+)_${base_ver}_\d{4}\.zip$' -and
0, $matches[1] -contains $patch_nbr
}
$fcnt = #($f).Count

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
#>

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

Regular expression in power shell to convert character between double quotes to upper case

I want to write a powershell script which will convert a string which is present between double quotes in a file, and convert it into upper case.
The files are placed in different folders.
I am able to extract the string between the double quotes and convert it to upper case, but not able to replace it in the correct position.
Ex : This is the input string.
"e" //&&'i&&
The output should be
"E" //&&'i&&
This is what i have tried. Also this even i not replacing the content of the file.
$items = Get-ChildItem * -recurse
# enumerate the items array
foreach ($item in $items)
{
# if the item is a directory, then process it.
if ($item.Attributes -ne "Directory")
{
(Get-Content $item.FullName ) |
Foreach-Object {
if (($_ -match '\"'))
{
$str = $_
$ext = [regex]::Matches($str, '".*?"').Value -replace '"'
$ext = $ext.ToUpper()
Write-Host $ext
$_ = $ext
}
else { }
} |
Set-Content $item.FullName
}
}
This can do it. Really I wasn't following your code so I stripped it and modified the regex.
$items = Get-ChildItem "C:\Users\UsernameHere\Desktop\Folder123\*.txt"
# enumerate the items array
foreach ($item in $items){
# if the item is a directory, then process it.
if ($item.Attributes -ne "Directory"){
$content = (gc $item.FullName )
$content = $content.replace('"\w.*"',$matches[0].ToUpper)
$content | sc $item
}
}
If you had powershell 6 or 7:
'"hi"' -replace '".*"', { $_.value.toupper() }
"HI"
'"e" //&&''i&&' -replace '".*"', { $_.value.toupper() }
"E" //&&'i&&
I am able to print the upper case characters with the below code, but the file is not getting updated. It still has the old characters, How to update the fie with new contents.
$items = Get-ChildItem *.txt -recurse
# enumerate the items array
foreach ($item in $items)
{
# if the item is a directory, then process it.
if ($item.Attributes -ne "Directory")
{
(Get-Content $item.FullName ) |
Foreach-Object {
$str = $_
$_ = [regex]::Replace($_, '"[^"]*"', { param($m) $m.Value.ToUpper() })
Write-Host $_
} |
Set-Content $item.FullName
}
}

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"

powershell regular expressions

quick one:
$logFile="D:\Code\functest\1725.log"
function getTime($pattern) {
Get-Content $logFile | %{ if ($_.Split('\t') -match $pattern) {$_} }
}
getTime("code")
gives me
simple 17-Feb-2011 10:45:27 Updating source code to revision: 49285
simple 17-Feb-2011 10:54:22 Updated source code to revision: 49285
but if I change the print value from
$_
to
$matches
I get nothing. I thought this array should have been created automatically? probably something silly, but this is my first day of using powershell :-)
EDIT: what I want to return is
Get-Date (column 2 of the matching line)
Your call to Split() is using C# conventions to escape the t to specify a tab character. In PowerShell, you use a single backtick e.g. $_.Split("`t"). Also -match is behaving a bit differently on an array like this so have it operate on each individual string like so:
Get-Content $logFile | Foreach {$_.Split("`t")} | Where { $_ -match $pattern }
There's also a sort of hidden trick here with Get-Content where you can get it to split for you:
Get-Content $logFile -del "`t" | Where { $_ -match $pattern }
Update: based on the updated question, try something like this:
gc $logFile | % {$cols = $_.Split("`t"); if ($cols[2] -match $pattern) {$cols[1]}}
Keeping in mind that arrays are 0-based in PowerShell. If the text is already in a DateTime format that PowerShell/.NET understand, you can just cast it to a DateTime like so [DateTime]$cols[1].
The $_.Split('\t') is breaking it.
First, it's breaking on every letter "t", not at tabs.
Second, it return a array that confounds -match.
With the following code:
Get-Content $logFile | %{ if ($_ -match $pattern) { $matches } }
getTime("code") would return:
Name Value
---- -----
0 code
0 code
This would allow to search with regular expressions, as in
$answerArray = getTime("(\t)(\d+)")
$digitsOfSecondResult = $answerArray[1][2]
Write-Output $digitsOfSecondResult
If you just want to print the lines that match the pattern, try:
Get-Content $logFile | %{ if ($_ -match $pattern) { $_} }
To get the date:
function getTime($pattern) {
Get-Content $logFile | %{ if ($_ -match $pattern) { Get-Date $matches[1] } }
}
getTime("`t(.+)`t.*code")
Or:
function getTime($pattern) {
Get-Content $logFile | %{ if ($_ -match "`t(.+)`t.*$pattern") { Get-Date $matches[1] } }
}
getTime("code")