首页 > 解决方案 > 文件夹中每个文件中的 Powershell 更新字符串的问题

问题描述

我有一组存储在文件夹中的 SQL 文件。这些文件包含格式名称,dbo_xxxxxx其中 xxxxxx 是年和月,例如dbo_202001dbo_202002。我希望 powershell 脚本在每个 SQL 文件中用新的数字替换 xxxxx 数字。我正在使用下面的脚本来实现这一点。然而,问题在于它似乎在旧字符串上部分匹配(而不是在完整字符串上匹配)并将新字符串放在适当的位置,例如,而不是替换[dbo_202001][dbo_201902],它找到的任何地方db等等o。它替换为[dbo_201902]。有任何解决这个问题的方法吗?

$sourceDir = "C:\SQL_Scripts"
$SQLScripts = Get-ChildItem $sourceDir *.sql -rec
foreach ($file in $SQLScripts)
{
    (Get-Content $file.PSPath) |
    Foreach-Object { $_ -replace "[dbo_202001]", "[dbo_201902]" } |
    Set-Content $file.PSPath -NoNewline
}

标签: powershell

解决方案


vonPryzmarsze提供了关键指针:由于-replace运算符对正则表达式正则表达式)进行操作,因此您必须\-escape 特殊字符,例如[and]以便逐字处理它们(作为文字)
$_ -replace '\[dbo_202001\]', '[dbo_201902]'

虽然-replace通常最好使用运算符,但该[string]类型的.Replace()方法直接提供逐字(文字)字符串替换,因此也比-replace.
通常,这无关紧要,但在与您类似的情况下,涉及许多迭代,它可能(请注意,替换区分大小写):
$_.Replace('[dbo_202001]', '[dbo_201902]')

有关何时使用. _-replace.Replace()


您的代码的性能可以大大提高:

$sourceDir = 'C:\SQL_Scripts'
foreach ($file in Get-ChildItem -File $sourceDir -Filter *.sql -Recurse)
{
  # CAVEAT: This overwrites the files in-place.
  Set-Content -NoNewLine $file.PSPath -Value `
    (Get-Content -Raw $file.PSPath).Replace('[dbo_202001]', '[dbo_201902]')
}
  • 由于您无论如何都要将整个文件读入内存,因此使用Get-Content's-Raw开关将其作为单个多行字符串(而不是行数组)读取,您可以在其上执行单个 .Replace()操作要快得多。

  • Set-Content-NoNewLine开关是为了防止在写回文件时附加一个额外的换行符。

  • 注意使用-Value参数而不是管道来提供文件内容。因为这里只有一个字符串对象要写,所以差别不大,但总的来说,有很多对象要写,这些对象已经收集在内存中Set-Content ... -Value $array$array | Set-Content ....


-replace 操作员.Replace() 方法的使用指南:

请注意,这两个功能总是替换它们找到的所有匹配项,相反,如果没有找到,则返回原始字符串

通常,PowerShell 的-replace运算符更自然地适合 PowerShell 代码 - 无论是在语法上还是由于其不区分大小写 - 并且由于基于正则表达式而提供了更多功能。

.Replace()方法仅限于逐字替换,在 Windows PowerShell 中仅限于区分大小写的方法,但具有速度更快且不必担心在其参数中转义特殊字符的优点:

  • 仅使用[string]类型的.Replace()方法

    • 用于始终逐字替换的字符串
    • 具有以下区分大小写:
      • PowerShell [Core] v6+默认区分大小写,通过附加参数可选择不区分大小写;例如:

        'FOO'.Replace('o', '@', 'InvariantCultureIgnoreCase')
        
      • Windows PowerShell总是(!)区分大小写

    • 如果在功能上可行,当性能很重要时
  • 否则,请使用 PowerShell 的-replace运算符(在此处详细介绍):

    • 对于基于正则表达式的替换:

      • 支持复杂的、基于模式的匹配和替换字符串的动态构造

      • 转义元字符(具有特殊句法含义的字符)以便逐字处理:

        • 在模式(正则表达式)参数中:-\转义它们(例如,\.\[
        • 在替换参数中: only$是特殊的,将其转义为$$.
      • 转义整个操作数以逐字处理其值(有效地执行文字替换):

        • 在模式参数中:调用[regex]::Escape($pattern)

        • 在替换参数中:调用$replacement.Replace('$', '$$')

    • 具有以下区分大小写:

      • 默认情况下不区分大小写
      • 可选地通过其-prefixed 变体区分大小写,c-creplace
    • 注意:-replace是一个对 PowerShell 友好的封装[regex]::Replace()方法,它不会暴露后者的所有功能,尤其是它限制替换次数的能力;请参阅此答案以了解如何使用它。

请注意,-replace可以像 LHS 一样直接对字符串的数组(集合)进行操作,在这种情况下,对每个元素执行替换,这是按需字符串化的

由于 PowerShell 的基本成员枚举功能,.Replace()也可以对数组进行操作,但前提是所有元素都已经是字符串。此外,与如果 LHS 为 1-replace也总是返回数组不同,如果输入对象恰好是单元素数组,则成员枚举返回单个字符串。

顺便说一句:类似的考虑适用于使用 PowerShell 的-split运算符与[string]类型的.Split()方法 - 请参阅此答案

例子

-replace-有关语法详细信息,请参阅此答案:

# Case-insensitive replacement.
# Pattern operand (regex) happens to be a verbatim string.
PS> 'foo' -replace 'O', '@'
f@@

# Case-sensitive replacement, with -creplace
PS> 'fOo' -creplace 'O', '@'
f@o

# Regex-based replacement with verbatim replacement: 
# metacharacter '$' constrains the matching to the *end*
PS> 'foo' -replace 'o$', '@'
fo@

# Regex-based replacement with dynamic replacement: 
# '$&' refers to what the regex matched
PS> 'foo' -replace 'o$', '>>$&<<'
fo>>o<<

# PowerShell [Core] only:
# Dynamic replacement based on a script block.
PS> 'A1' -replace '\d', { [int] $_.Value + 1 }
A2

# Array operation, with elements stringified on demand:
PS> 1..3 -replace '^', '0'
01
02
03

# Escape a regex metachar. to be treated verbatim.
PS> 'You owe me $20' -replace '\$20', '20 dollars'
You owe me 20 dollars. 

# Ditto, via a variable and [regex]::Escape()
PS> $var = '$20'; 'You owe me $20' -replace [regex]::Escape($var), '20 dollars'
You owe me 20 dollars.

# Escape a '$' in the replacement operand so that it is always treated verbatim:
PS> 'You owe me 20 dollars' -replace '20 dollars', '$$20'
You owe me $20

# Ditto, via a variable and [regex]::Escape()
PS> $var = '$20'; 'You owe me 20 dollars' -replace '20 dollars', $var.Replace('$', '$$')
You owe me $20.

.Replace()

# Verbatim, case-sensitive replacement.
PS> 'foo'.Replace('o', '@')
f@@

# No effect, because matching is case-sensitive.
PS> 'foo'.Replace('O', '@')
foo

# PowerShell [Core] v6+ only: opt-in to case-INsensitivity:
PS> 'FOO'.Replace('o', '@', 'InvariantCultureIgnoreCase')
F@@

# Operation on an array, thanks to member enumeration:
# Returns a 2 -element array in this case.
PS> ('foo', 'goo').Replace('o', '@')
f@@
g@@

# !! Fails, because not all array elements are *strings*:
PS> ('foo', 42).Replace('o', '@')
... Method invocation failed because [System.Int32] does not contain a method named 'Replace'. ...

推荐阅读