如何在保留输入大小写的同时用 case-insensitive 匹配替换 PowerShell 中的字符串
How to replace strings in PowerShell with case-insensitive matching while preserving the input case
我想替换文件中的多个字符串。如果字符串是大写字母,则用大写字母替换。如果是小写就用小写替换。
到目前为止,我的代码如下所示。它确实会替换,但不会按大小写替换。
(Get-Content $xyx -ErrorAction Stop) | Foreach-Object {
$_ -replace 'abc001', 'abc002' -replace 'cde001', 'cde002'
} | Set-Content $xyz
有时我要替换的字符串是大写的。所以我需要引入一个检查,如果它是大写替换为大写,否则小写。
这段代码怎么写?
编者注:在首次发布此问题时,要求在替换字符串中保留原始大小写的同时匹配 case-insensitively 的要求并不十分明显 - 标题后来被修改,之后部分答案已发布。
-replace
正则表达式运算符默认区分大小写 in。如果您想更改该行为,您需要使用正则表达式修饰符 (-replace '(?-i)abc001', 'abc002'
) 或更简单的 -creplace 'abc001', 'abc002'
来使操作区分大小写。
但是,由于您在这里似乎没有使用正则表达式,我建议您使用字符串运算符 .Replace()
,因为它默认区分大小写。
$_.Replace('abc001', 'abc002').Replace('cde001', 'cde002')
来自另一个问题 here,尝试这样的事情 -
$i = 0
(Get-Content $xyx -ErrorAction Stop) | Foreach-Object {
$line = $_[$i];
if ($line -cmatch "^[^a-z]*$")
{
$line -replace $Matches.Value, 'YourNewValue'.ToUpper()
}
elseif ($line -cmatch "^[^A-Z]*$")
{
$line -replace $Matches.Value, 'YourNewValue'.ToLower()
}
$i++
} | Set-Content $xyz
原答案使用正则匹配。原始答案中已经提到了正则表达式的解释。我所做的只是引入一个大写和小写检查并同样替换它。
就像original answer中提到的,诀窍是使用-cmatch
运算符。 -cmatch
始终 区分大小写。
来到正则表达式,
- 字符串开头的anchor(
^
)
- 一个 character class 小写拉丁字母 (
[a-z]
)。同样([A-z]
为大写)。
- A quantifier,告诉要重复字符 class 至少 0 次,从而根据需要匹配尽可能多的字符 (
*
)。您可以使用 +
来禁止空字符串。
- anchor为字符串结尾(
$
)。这两个锚点确保正则表达式必须匹配字符串中的 每个 字符。如果您只使用 [a-z]*
那么这将匹配任何至少包含 0
个小写字母 某处 的字符串。这将是每个字符串。
如果您的字符串也可能由字母以外的其他内容组成,并且您想确保其中的每个 字母 都是小写,而不是还要求字符串包含仅包含字母,则必须反转字符 class,类似于:
if ($xyz -cmatch "^[^A-Z]*$") { ... }
字符class开头的^
反转 class,匹配每个字符not包括在内。因此,只有当字符串在某处包含大写字母时,此正则表达式才会失败。尽管如此,仍然需要-cmatch
。
听起来您正在寻找的是 匹配 上 不区分大小写 的替换操作, 但 case-preserving on replacement.
您没有指定要匹配的字符串和用什么替换它们之间的确切关系,而是按照您的示例命令进行:
(Get-Content $xyx -ErrorAction Stop) -replace '(abc00)1', '2' -replace
'(cde00)1', '2' | Set-Content $xyz
注意:根据手头的特定替换,可以将它们组合成一个操作:
... -replace '(abc00|cde00)1', '2'
鉴于 -regex
默认情况下不区分大小写,它将匹配 abc
和 cde
的所有大小写变体,但将它们与 original 第一个(也是唯一一个)捕获组 ((...)
) 的情况,可以在替换操作数中引用为 </code>(或者只是 <code>
,如果没有歧义)。
另请注意我是如何省略 ForEach-Cmdlet
调用的,因为您可以将 -replace
直接应用于 (Get-Content ...)
returns;对于已经在内存中的数组,这种方法不仅更短,而且明显更快。
举个简单的例子:
PS> 'aBc001' -replace '(abc00)1', '2'
aBc002 # input case was preserved
我想替换文件中的多个字符串。如果字符串是大写字母,则用大写字母替换。如果是小写就用小写替换。
到目前为止,我的代码如下所示。它确实会替换,但不会按大小写替换。
(Get-Content $xyx -ErrorAction Stop) | Foreach-Object {
$_ -replace 'abc001', 'abc002' -replace 'cde001', 'cde002'
} | Set-Content $xyz
有时我要替换的字符串是大写的。所以我需要引入一个检查,如果它是大写替换为大写,否则小写。
这段代码怎么写?
编者注:在首次发布此问题时,要求在替换字符串中保留原始大小写的同时匹配 case-insensitively 的要求并不十分明显 - 标题后来被修改,之后部分答案已发布。
-replace
正则表达式运算符默认区分大小写 in。如果您想更改该行为,您需要使用正则表达式修饰符 (-replace '(?-i)abc001', 'abc002'
) 或更简单的 -creplace 'abc001', 'abc002'
来使操作区分大小写。
但是,由于您在这里似乎没有使用正则表达式,我建议您使用字符串运算符 .Replace()
,因为它默认区分大小写。
$_.Replace('abc001', 'abc002').Replace('cde001', 'cde002')
来自另一个问题 here,尝试这样的事情 -
$i = 0
(Get-Content $xyx -ErrorAction Stop) | Foreach-Object {
$line = $_[$i];
if ($line -cmatch "^[^a-z]*$")
{
$line -replace $Matches.Value, 'YourNewValue'.ToUpper()
}
elseif ($line -cmatch "^[^A-Z]*$")
{
$line -replace $Matches.Value, 'YourNewValue'.ToLower()
}
$i++
} | Set-Content $xyz
原答案使用正则匹配。原始答案中已经提到了正则表达式的解释。我所做的只是引入一个大写和小写检查并同样替换它。
就像original answer中提到的,诀窍是使用-cmatch
运算符。 -cmatch
始终 区分大小写。
来到正则表达式,
- 字符串开头的anchor(
^
) - 一个 character class 小写拉丁字母 (
[a-z]
)。同样([A-z]
为大写)。 - A quantifier,告诉要重复字符 class 至少 0 次,从而根据需要匹配尽可能多的字符 (
*
)。您可以使用+
来禁止空字符串。 - anchor为字符串结尾(
$
)。这两个锚点确保正则表达式必须匹配字符串中的 每个 字符。如果您只使用[a-z]*
那么这将匹配任何至少包含0
个小写字母 某处 的字符串。这将是每个字符串。
如果您的字符串也可能由字母以外的其他内容组成,并且您想确保其中的每个 字母 都是小写,而不是还要求字符串包含仅包含字母,则必须反转字符 class,类似于:
if ($xyz -cmatch "^[^A-Z]*$") { ... }
字符class开头的^
反转 class,匹配每个字符not包括在内。因此,只有当字符串在某处包含大写字母时,此正则表达式才会失败。尽管如此,仍然需要-cmatch
。
听起来您正在寻找的是 匹配 上 不区分大小写 的替换操作, 但 case-preserving on replacement.
您没有指定要匹配的字符串和用什么替换它们之间的确切关系,而是按照您的示例命令进行:
(Get-Content $xyx -ErrorAction Stop) -replace '(abc00)1', '2' -replace
'(cde00)1', '2' | Set-Content $xyz
注意:根据手头的特定替换,可以将它们组合成一个操作:
... -replace '(abc00|cde00)1', '2'
鉴于
-regex
默认情况下不区分大小写,它将匹配abc
和cde
的所有大小写变体,但将它们与 original 第一个(也是唯一一个)捕获组 ((...)
) 的情况,可以在替换操作数中引用为</code>(或者只是 <code>
,如果没有歧义)。另请注意我是如何省略
ForEach-Cmdlet
调用的,因为您可以将-replace
直接应用于(Get-Content ...)
returns;对于已经在内存中的数组,这种方法不仅更短,而且明显更快。
举个简单的例子:
PS> 'aBc001' -replace '(abc00)1', '2'
aBc002 # input case was preserved