Reputation: 305
Question regarding renaming of files and content within files using Powershell following the solution here.
With the script below, all file names and occurrences within the files are renamed. The replacement is case-insensitive, i.e. no matter if there is an occurrence "uvw", "UVW", "Uvw", etc., the replacement is "XYZ". Is it possible to respect the case of the original and rename "true to original", i.e. "uvw" -> "xyz", "UVW" -> "XYZ", "Uvw" -> "Xyz" (also "abc_123" should be "def_123" and not "DEF_123" by default)?
$filePath = "C:\root_folder"
$include = '*.txt', '*.xml' # adapt as needed
Get-ChildItem -File $filePath -Recurse -Include $include |
Rename-Item -WhatIf -PassThru -NewName { $_.Name -replace 'UVW', 'XYZ' } |
ForEach-Object {
($_ | Get-Content -Raw) -replace 'ABC_123', 'DEF_123' |
Set-Content -NoNewLine -LiteralPath $_.FullName
}
Upvotes: 2
Views: 55
Reputation: 61103
So, this is incredibly inefficient but I don't see a way around it, you need to use a match evaluator and a hashtable to map the matched characters with their replacement character.
The code will look different depending on if you're on PowerShell 7+ where Replacement with a script block exists or if you're on Windows PowerShell 5.1 where you need to call the Regex.Replace
API targeting one of the MatchEvaluator
overloads).
$evaluator = {
# enumerate each character from the matched value
# `.Value` in this context from the examples would be `uvw`, `UVW` and `Uvw`
$result = foreach ($char in $_.Value.GetEnumerator()) {
# here we get the replacement character
# i.e.:
# - if `$char` is `u` then `$value` is `x`
# - if `$char` is `v` then `$value` is `y`
$value = $map[$char.ToString()]
# check if the enumerated character is uppercase
if ([char]::IsUpper($char)) {
# then, we need to output the uppercase replacement char too
$value.ToUpper()
# and go to the next char
continue
}
# else, just output the char as-is (lowercase)
$value
}
# lastly, after all matched characters are processed,
# create a new string from the char array
[string]::new($result)
}
$map = @{
u = 'x'
v = 'y'
w = 'z'
}
'foo uvw bar' -replace 'uvw', $evaluator # Outputs: foo xyz bar
'foo UVW bar' -replace 'uvw', $evaluator # Outputs: foo XYZ bar
'foo Uvw bar' -replace 'uvw', $evaluator # Outputs: foo Xyz bar
$evaluator = {
$result = foreach ($char in $args[0].Value.GetEnumerator()) {
$value = $map[$char.ToString()]
if ([char]::IsUpper($char)) {
$value.ToUpper()
continue
}
$value
}
[string]::new($result)
}
$map = @{
u = 'x'
v = 'y'
w = 'z'
}
$re = [regex]::new('uvw', [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)
$re.Replace('foo uvw bar', $evaluator) # Outputs: foo xyz bar
$re.Replace('foo UVW bar', $evaluator) # Outputs: foo XYZ bar
$re.Replace('foo Uvw bar', $evaluator) # Outputs: foo Xyz bar
Upvotes: 2
Reputation: 440227
An alternative approach to Santiago's helpful answer, which too relies on a match-evaluator script block that is called for every match:
The general idea is:
System.Text.StringBuilder
instance to build a case-matched version of the replacement text, character by character.Both solutions below output <XyZ>
, as intended.
PowerShell (Core) 7+ solution:
# Sample input string
$inputString = '<UvW>'
# Sample search pattern.
$searchPattern = 'uvw'
# Sample replacement text.
$replaceWith = 'xyz'
# A string builder to make constructing the case-matched replacement string
# more efficient.
$caseMatchedReplaceWith = [System.Text.StringBuilder]::new($replaceWith.Length)
$inputString -replace $searchPattern, {
$matchedText = $_.Value
$numCharsToMatch = [Math]::Min($replaceWith.Length, $matchedText.Length)
$null = $caseMatchedReplaceWith.Clear()
foreach ($i in 0..($numCharsToMatch-1)) {
$replacementChar = $replaceWith[$i]
if ([char]::IsUpper($matchedText[$i]) -and -not [char]::IsUpper($replacementChar)) {
$replacementChar = [char]::ToUpper($replacementChar)
}
$null = $caseMatchedReplaceWith.Append($replacementChar)
}
$caseMatchedReplaceWith.ToString() + $replaceWith.Substring($numCharsToMatch)
}
Windows PowerShell solution (where using a script block as the substitution operand of -replace
isn't supported):
# Sample input string
$inputString = '<UvW>'
# Sample search pattern.
$searchPattern = 'uvw'
# Sample replacement text.
$replaceWith = 'xyz'
# A string builder to make constructing the case-matched replacement string
# more efficient.
$caseMatchedReplaceWith = [System.Text.StringBuilder]::new($replaceWith.Length)
[regex]::Replace(
$inputString,
$searchPattern,
{
$matchedText = $args[0].Value
$numCharsToMatch = [Math]::Min($replaceWith.Length, $matchedText.Length)
$null = $caseMatchedReplaceWith.Clear()
foreach ($i in 0..($numCharsToMatch-1)) {
$replacementChar = $replaceWith[$i]
if ([char]::IsUpper($matchedText[$i]) -and -not [char]::IsUpper($replacementChar)) {
$replacementChar = [char]::ToUpper($replacementChar)
}
$null = $caseMatchedReplaceWith.Append($replacementChar)
}
$caseMatchedReplaceWith.ToString() + $replaceWith.Substring($numCharsToMatch)
},
'IgnoreCase'
)
Upvotes: 2