术语说明:由于模式可用于指代通配符表达式和正则表达式,因此术语glob用作通配符表达式的明确简写。
基于字符串拆分的简单但有限的解决方案
具体来说,以下解决方案仅限于包含一个*
作为唯一通配符元字符的通配符模式。
# Sample input objects that emulate file-info objects
# as output by Get-ChildItem
$files =
@{ Name = 'ABC_1232.txt' },
@{ Name = 'abC_4321.TxT' },
@{ Name = 'qwerty_1232.cSv' },
@{ Name = 'QwErTY_4321.CsV' },
@{ Name = 'Unrelated.CsV' }
# The wildcard patterns to match against.
$globs = 'QWERTY_*.csv', 'abc_*.TXT'
# Loop over all files.
# IRL, use Get-ChildItem in lieu of $files.
$files | ForEach-Object {
# Loop over all wildcard patterns
foreach ($glob in $globs) {
if ($_.Name -like $glob) { # matching filename
# Split the glob into the prefix (the part before '*') and
# the extension (suffix), (the part after '*').
$prefix, $extension = $glob -split '\*'
# Extract the specific middle part of the filename; the part that
# matched '*'
$middle = $_.Name.Substring($prefix.Length, $_.Name.Length - $prefix.Length - $extension.Length)
# This is where your Rename-Item call would go.
# $_ | Rename-Item -WhatIf -NewName ($prefix + $middle + $extension)
# Note that if the filename already happens to be case-exact,
# Rename-Item is a quiet no-op.
# For this demo, we simply output the new name.
$prefix + $middle + $extension
}
}
}
使用正则表达式的通用但更复杂的解决方案
此解决方案要复杂得多,但应该适用于所有通配符表达式(只要`
不需要支持 -escaping)。
# Sample input objects that emulate file-info objects
# as output by Get-ChildItem
$files =
@{ Name = 'ABC_1232.txt' },
@{ Name = 'abC_4321.TxT' },
@{ Name = 'qwerty_1232.cSv' },
@{ Name = 'QwErTY_4321.CsV' },
@{ Name = 'Unrelated.CsV' }
# The globs (wildcard patterns) to match against.
$globs = 'QWERTY_*.csv', 'abc_*.TXT'
# Translate the globs into regexes, with the non-literal parts enclosed in
# capture groups; note the addition of anchors ^ and $, given that globs
# match the entire input string.
# E.g., 'QWERTY_*.csv' -> '^QWERTY_(.*)\.csv$'
$regexes = foreach($glob in $globs) {
'^' +
([regex]::Escape($glob) -replace '\\\*', '(.*)' -replace # *
'\\\?', '(.)' -replace # ?
'\\(\[.+?\])', '($1)') + # [...]
'$'
}
# Construct string templates from the globs that can be used with the -f
# operator to fill in the variable parts from each filename match.
# Each variable part is replaced with a {<n>} placeholder, starting with 0.
# E.g., 'QWERTY_*.csv' -> 'QWERTY_{0}.csv'
$templates = foreach($glob in $globs) {
$iRef = [ref] 0
[regex]::Replace(
($glob -replace '[{}]', '$&$&'), # escape literal '{' and '}' as '{{' and '}}' first
'\*|\?|\[.+?\]', # wildcard metachars. / constructs
{ param($match) '{' + ($iRef.Value++) + '}' } # replace with {<n>} placeholders
)
}
# Loop over all files.
# IRL, use Get-ChildItem in lieu of $files.
$files | ForEach-Object {
# Loop over all wildcard patterns
$i = -1
foreach ($regex in $regexes) {
++$i
# See if the filename matches
if (($matchInfo = [regex]::Match($_.Name, $regex, 'IgnoreCase')).Success) {
# Instantiate the template string with the capture-group values.
# E.g., 'QWERTY_{0}.csv' -f '4321'
$newName = $templates[$i] -f ($matchInfo.Groups.Value | Select-Object -Skip 1)
# This is where your Rename-Item call would go.
# $_ | Rename-Item -WhatIf -NewName $newName
# Note that if the filename already happens to be case-exact,
# Rename-Item is a quiet no-op.
# For this demo, we simply output the new name.
$newName
}
}
}