如何在Windows文本文件中查找和替换文本,并使用搜索和替换易于阅读且易于添加/修改/删除的字符串。该脚本将解析6800行文件,找到70个字符串实例,对其重新编号,并在不到400ms的时间内覆盖原始字符串。
搜索字符串“ AROUND LINE {1-9999}”和“ LINE2 {1-9999}”,并将{1-9999}替换为代码所在的{line number}。弦线周围有前后空格。最后两个测试是使用整个源批处理副本完成的,并将其粘贴到sample.bat中。
sample.bat包含两行:
ECHO AROUND LINE 5936
TITLE %TIME% DISPLAY TCP-IP SETTINGS LINE2 5937
当前代码包括寻找AROUND LINE和@ mklement0解决方案。
copy-item $env:temp\sample.bat -d $env:temp\sample.bat.$((get-date).tostring("HHmmss"))
$file = "$env:temp\sample.bat"
$lc = 0
$updatedLines = switch -Regex ([IO.File]::ReadAllLines($file)) {
'^(.*? (?:AROUND LINE|LINE2) )\d+(.*)$' { $Matches[1] + ++$lc + $Matches[2] }
default { ++$lc; $_ }
}
[IO.File]::WriteAllLines($file, $updatedLines, [Text.Encoding]::ASCII)
预期结果:
ECHO AROUND LINE 1
TITLE %TIME% DISPLAY TCP-IP SETTINGS LINE2 2
实际结果:
ECHO AROUND LINE 1
TITLE %TIME% DISPLAY TCP-IP SETTINGS LINE2 2
使用switch,.NET框架和粘贴到sample.bat中的整个批处理文件进行测量:
Measure-command {
copy-item $env:temp\sample.bat -d $env:temp\sample.bat.$((get-date).tostring("HHmmss"))
$file = "$env:temp\sample.bat"
$lc = 0
$updatedLines = switch -Regex ([IO.File]::ReadAllLines($file)) {
'^(.*? (?:AROUND LINE|LINE2) )\d+(.*)$' { $Matches[1] + ++$lc + $Matches[2] }
default { ++$lc; $_ }
}
[IO.File]::WriteAllLines($file, $updatedLines, [Text.Encoding]::ASCII)}
结果:十次运行中75ms-386ms。
使用Get-Content + -replace + Set-Content并将整个批处理文件粘贴到sample.bat中进行测量:
Measure-command {
copy-item $env:temp\sample.bat -d $env:temp\sample.bat.$((get-date).tostring("HHmmss"))
(gc $env:temp\sample.bat) | foreach -Begin {$lc = 1} -Process {
$_ -replace 'AROUND LINE \d+', "AROUND LINE $lc" -replace 'LINE2 \d+', "LINE2 $lc"
++$lc
} | sc -Encoding Ascii $env:temp\sample.bat}
结果:十次运行中363ms-451ms。
搜索字符串是一个易于理解的正则表达式。
您可以通过添加另一个-replace来搜索其他字符串。
-replace 'AROUND LINE \d+', "AROUND LINE $lc" -replace 'LINE2 \d+', "LINE2 $lc" -replace 'LINE3 \d+', "LINE3 $lc"
此问题从最年轻到最老的演变: 1. 54757890 2. 54737787 3. 54712715 4. 54682186
更新:我使用了@ mklement0正则表达式解决方案。
答案 0 :(得分:2)
switch -Regex -File $file {
'^(.*? (?:AROUND LINE|LINE2) )\d+(.*)$' { $Matches[1] + ++$lc + $Matches[2] }
default { ++$lc; $_ }
}
鉴于正则表达式^(.*? (?:AROUND LINE|LINE2) )\d+(.*)$
仅包含 2 个捕获组-要替换的数字之前({{1} )以及行之后的部分,您必须在输出的automatic $Matches
variable中引用索引为\d+
和1
的这些组(而不是{{1} }和2
。
2
是一个不捕获组,因此从设计上讲,它不会反映在3
中。不是使用(?:...)
来读取文件,而是使用$Matches
的{{1}}选项,该选项直接从文件[IO.File]::ReadAllLines($file)
中读取行。 / p>
-File
中的switch
确保在 non-matching 行中,行计数器也要递增({{1 })。
您可以使用以下obscure optimization来稍微提高性能:
$file
使用预编译的++$lc
实例而不是PowerShell转换为的字符串文字,具有高迭代次数(大量行)幕后的正则表达式可以进一步加快处理速度-请参见下面的基准测试。
此外,如果区分大小写 匹配就足够了,则可以通过在{上添加default { ++$lc; $_ }
选项来降低 的性能。 {1}}语句。
从总体上讲,使解决方案快速的原因是使用$_
处理行,并且通常使用.NET类型文件I / O (而不是cmdlet)(在本例中为# Enclose the switch statement in & { ... } to speed it up slightly.
$updatedLines = & { switch -Regex -File ... }
,如问题所示)-另请参见this related answer。
[regex]
循环方法,该方法在更高的迭代次数下会更快-然而,它更为冗长。 以下代码将此答案的-CaseSensitive
方法与marsze的switch
方法的性能进行了比较。
请注意,为了使两个解决方案完全等效,进行了以下调整:
switch -File
优化也已添加到IO.File]::WriteAllLines()
命令中。foreach
方法中添加了switch
和foreach
选项,以匹配分别用600行,3,000和30,000行文件而不是6行示例文件来测试性能,以显示迭代计数对性能的影响。
平均100次跑步。
我运行Windows PowerShell v5.1的Windows 10计算机上的 采样结果-绝对时间并不重要,但希望相对 & { ... }
栏中显示的效果通常具有代表性:
switch
请注意,使用 string文字在较低的迭代次数下IgnoreCase
最快,但是在大约1,500行的情况下,使用预编译的CultureInvariant
实例的foreach
解决方案开始了变得更快;将预编译的Factor
实例与VERBOSE: Averaging 100 runs with a 600-line file of size 0.03 MB...
Factor Secs (100-run avg.) Command
------ ------------------- -------
1.00 0.023 # switch -Regex -File with regex string literal...
1.16 0.027 # foreach with precompiled regex and [regex].Match...
1.23 0.028 # switch -Regex -File with precompiled regex...
VERBOSE: Averaging 100 runs with a 3000-line file of size 0.15 MB...
Factor Secs (100-run avg.) Command
------ ------------------- -------
1.00 0.063 # foreach with precompiled regex and [regex].Match...
1.11 0.070 # switch -Regex -File with precompiled regex...
1.15 0.073 # switch -Regex -File with regex string literal...
VERBOSE: Averaging 100 runs with a 30000-line file of size 1.47 MB...
Factor Secs (100-run avg.) Command
------ ------------------- -------
1.00 0.252 # foreach with precompiled regex and [regex].Match...
1.24 0.313 # switch -Regex -File with precompiled regex...
1.53 0.386 # switch -Regex -File with regex string literal...
一起使用,只会得到较小的回报,只有迭代次数更高。
基准代码,使用Time-Command
function:
switch -regex
答案 1 :(得分:2)
替代解决方案:
$regex = [Regex]::new('^(.*? (?:AROUND LINE|LINE2) )\d+(.*)$', 'Compiled, IgnoreCase, CultureInvariant')
$lc = 0
$updatedLines = & {foreach ($line in [IO.File]::ReadLines($file)) {
$lc++
$m = $regex.Match($line)
if ($m.Success) {
$g = $m.Groups
$g[1].Value + $lc + $g[2].Value
} else { $line }
}}
[IO.File]::WriteAllLines($file, $updatedLines, [Text.Encoding]::ASCII)