如何用 PowerShell 替换文件中每次出现的字符串?
Posted
技术标签:
【中文标题】如何用 PowerShell 替换文件中每次出现的字符串?【英文标题】:How can I replace every occurrence of a String in a file with PowerShell? 【发布时间】:2013-06-13 05:16:58 【问题描述】:使用 PowerShell,我想用MyValue
替换给定文件中所有精确出现的[MYID]
。最简单的方法是什么?
【问题讨论】:
有关内存消耗方面比此问题的答案中提供的更有效的解决方案,请参阅Find and Replace in a Large File。 投反对票,因为问题中没有善意的努力。 【参考方案1】:使用(V3版本):
(Get-Content c:\temp\test.txt).replace('[MYID]', 'MyValue') | Set-Content c:\temp\test.txt
或者对于 V2:
(Get-Content c:\temp\test.txt) -replace '\[MYID\]', 'MyValue' | Set-Content c:\temp\test.txt
【讨论】:
谢谢 - 我收到错误“替换:方法调用失败,因为 [System.Object[]] 不包含名为“替换”的方法。”虽然? @rob 如果你想保存修改,将结果通过管道传送到 set-content 或 out-file 我收到错误“方法调用失败,因为 [System.Object[]] 不包含名为 'replace' 的方法。”因为我试图在只有 V2 的机器上运行 V3 版本。 警告:对大文件(几百兆字节左右)运行这些脚本会消耗相当多的内存。如果您在生产服务器上运行,请确保您有足够的空间:D 最好使用'-replace',这样你就可以使用正则表达式了。【参考方案2】:我更喜欢使用 .NET 的 File 类及其静态方法,如下例所示。
$content = [System.IO.File]::ReadAllText("c:\bla.txt").Replace("[MYID]","MyValue")
[System.IO.File]::WriteAllText("c:\bla.txt", $content)
这具有使用单个字符串而不是像Get-Content 那样使用字符串数组的优点。这些方法还负责文件的编码(UTF-8 BOM 等),而您大部分时间都不必关心。
与使用 Get-Content 和管道到 Set-Content 的算法相比,这些方法也不会弄乱行尾(可能使用的 Unix 行尾)。
所以对我来说:多年来可能会损坏的东西越来越少。
使用 .NET 类时鲜为人知的一点是,当您在 PowerShell 窗口中键入“[System.IO.File]::”时,您可以按 Tab 键进行单步通过那里的方法。
【讨论】:
也可以通过[System.IO.File] | gm
命令查看方法
为什么这个方法假设一个来自C:\Windows\System32\WindowsPowerShell\v1.0
的相对路径?
是这样吗?这可能与在 PowerShell 中启动 .NET AppDomain 的方式有关。可能是,使用 cd 时当前路径没有得到更新。但这只是一个有根据的猜测。我没有对此进行测试或查找。
这也比为不同版本的Powershell编写不同的代码要容易得多。
这个方法似乎也是最快的。再加上明显的好处,问题应该是,“你为什么要使用其他东西?”【参考方案3】:
(Get-Content file.txt) |
Foreach-Object $_ -replace '\[MYID\]','MyValue' |
Out-File file.txt
注意(Get-Content file.txt)
周围的括号是必需的:
如果没有括号,内容会一次读取一行,并沿管道向下流动,直到到达 out-file 或 set-content,它会尝试写入同一个文件,但它已经被 get-content 打开你得到一个错误。括号使内容读取的操作执行一次(打开、读取和关闭)。只有当所有行都被读取后,它们才会一次通过管道传输,当它们到达管道中的最后一个命令时,它们才能写入文件。与 $content=content; 相同$内容 |哪里...
【讨论】:
如果可以的话,我会把赞成票改为反对票。在 PowerShell 3 中,这会默默地删除文件中的所有内容!使用Set-Content
而不是Out-File
你会得到类似“进程无法访问文件'123.csv',因为它正被另一个进程使用”的警告。。
当 get-content 在括号中时不应该发生。它们导致操作打开、读取和关闭文件,因此您得到的错误不应该发生。你能用一个示例文本文件再次测试它吗?
括号中的Get-Content
可以工作。你能在你的回答中解释为什么括号是必要的吗?我仍然会用Set-Content
替换Out-File
,因为它更安全;如果您忘记括号,它可以保护您不会清除目标文件。
文件编码为 UTF-8 的问题。保存文件时,更改编码。不一样。 ***.com/questions/5596982/…。我认为 set-content 考虑编码文件(如 UTF-8)。但不是Out-File
此解决方案具有不必要的误导性,并在我使用时引起了问题。我正在更新安装过程立即使用的配置文件。配置文件仍由进程持有,安装失败。使用Set-Content
而不是Out-File
是更好、更安全的解决方案。抱歉不得不投反对票。【参考方案4】:
上面的仅对“一个文件”运行,但您也可以对文件夹中的多个文件运行它:
Get-ChildItem 'C:yourfile*.xml' -Recurse | ForEach
(Get-Content $_ | ForEach $_ -replace '[MYID]', 'MyValue' ) |
Set-Content $_
【讨论】:
请注意,我使用的是 .xml 但你可以用 .txt 替换 不错。或者使用内部foreach
你可以这样做Get-ChildItem 'C:\folder\file*.xml' -Recurse | ForEach (Get-Content $_).Replace('[MYID]', 'MyValue') | Set-Content $_
实际上,你确实需要那个内部的foreach
,因为Get-Content 做了一些你可能没想到的事情……它返回一个字符串数组,其中每个字符串都是文件中的一行。如果您正在循环访问与正在运行的脚本位于不同位置的目录(和子目录),您将需要这样的内容:Get-ChildItem $Directory -File -Recurse | ForEach (Get-Content $_.FullName) | ForEach $_ -replace '[MYID]', 'MyValue' | Set-Content $_.FullName
其中$Directory
是包含您要修改的文件的目录.
“上面那个”的答案是什么?【参考方案5】:
你可以试试这样的:
$path = "C:\testFile.txt"
$word = "searchword"
$replacement = "ReplacementText"
$text = get-content $path
$newText = $text -replace $word,$replacement
$newText > $path
【讨论】:
【参考方案6】:这是我使用的,但在大型文本文件上速度很慢。
get-content $pathToFile | % $_ -replace $stringToReplace, $replaceWith | set-content $pathToFile
如果您要替换大型文本文件中的字符串并且速度是一个问题,请考虑使用System.IO.StreamReader 和System.IO.StreamWriter。
try
$reader = [System.IO.StreamReader] $pathToFile
$data = $reader.ReadToEnd()
$reader.close()
finally
if ($reader -ne $null)
$reader.dispose()
$data = $data -replace $stringToReplace, $replaceWith
try
$writer = [System.IO.StreamWriter] $pathToFile
$writer.write($data)
$writer.close()
finally
if ($writer -ne $null)
$writer.dispose()
(以上代码未经测试。)
可能有一种更优雅的方式来使用 StreamReader 和 StreamWriter 来替换文档中的文本,但这应该是一个很好的起点。
【讨论】:
我认为 set-content 考虑编码文件(如 UTF-8)。但不是 Out-File ***.com/questions/5596982/…【参考方案7】:我从 Payette 的 Windows Powershell in Action 中找到了一种鲜为人知但非常酷的方法。可以引用变量之类的文件,类似于$env:path,但是需要加上花括号。
$c:file.txt = $c:file.txt -replace 'oldvalue','newvalue'
【讨论】:
如果文件名在$myFile
等变量中怎么办?
@ΩmegaMan 嗯到目前为止只有这个$a = 'file.txt'; invoke-expression "`$c:$a = `$c:$a -replace 'oldvalue','newvalue'"
好招!可惜它需要绝对文件路径或至少需要磁盘盘符......或者有没有办法说文件位于当前目录中?
不错。但距离 Linux 的 sed -i "s/oldValue/newValue/g" file.txt
还很远 :-(【参考方案8】:
感谢@rominator007
我把它包装成一个函数(因为你可能想再次使用它)
function Replace-AllStringsInFile($SearchString,$ReplaceString,$FullPathToFile)
$content = [System.IO.File]::ReadAllText("$FullPathToFile").Replace("$SearchString","$ReplaceString")
[System.IO.File]::WriteAllText("$FullPathToFile", $content)
注意:这不区分大小写!!!!!
看到这个帖子:String.Replace ignoring case
【讨论】:
【参考方案9】:如果您需要替换多个文件中的字符串:
应该注意的是,这里发布的不同方法在完成时间方面可能大不相同。对我来说,我经常有大量的小文件。为了测试什么是最高效的,我在 40,693 个单独的文件中提取了 5.52 GB(5,933,604,999 字节)的 XML,并查看了我在这里找到的三个答案:
## 5.52 GB (5,933,604,999 bytes) of XML files (40,693 files)
$xmls = (Get-ChildItem -Path "I:\TestseT\All_XML" -Recurse -Filter *.xml).FullName
#### Test 1 - Plain Replace
$start = Get-Date
foreach ($xml in $xmls)
(Get-Content $xml).replace("'", " ") | Set-Content $xml
$end = Get-Date
New-TimeSpan –Start $Start –End $End
# TotalMinutes: 103.725113128333
#### Test 2 - Replace with -Raw
$start = Get-Date
foreach ($xml in $xmls)
(Get-Content $xml -Raw).replace("'", " ") | Set-Content $xml
$end = Get-Date
New-TimeSpan –Start $Start –End $End
# TotalMinutes: 10.1600227983333
#### Test 3 - .NET, System.IO
$start = Get-Date
foreach ($xml in $xmls)
$txt = [System.IO.File]::ReadAllText("$xml").Replace("'"," ")
[System.IO.File]::WriteAllText("$xml", $txt)
$end = Get-Date
New-TimeSpan –Start $Start –End $End
# TotalMinutes: 5.83619516833333
【讨论】:
问题是关于替换给定文件中的字符串,而不是多个文件。 这可能就是为什么答案会在顶部显示“如果您需要替换多个文件中的文本”。【参考方案10】:由于这经常出现,我为它定义了一个函数。我默认使用区分大小写、基于正则表达式的匹配,但我包含了用于定位文字文本和忽略大小写的开关。
# Find and replace text in each pipeline string. Omit the -Replace parameter to delete
# text instead. Use the -SimpleMatch switch to work with literal text instead of regular
# expressions. Comparisons are case-sensitive unless the -IgnoreCase switch is used.
Filter Edit-String
Param([string]$Find, [string]$Replace='', [switch]$SimpleMatch, [switch]$IgnoreCase)
if ($SimpleMatch)
if ($IgnoreCase)
return $_.Replace($Find, $Replace,
[System.StringComparison]::OrdinalIgnoreCase)
return $_.Replace($Find, $Replace)
if ($IgnoreCase)
return $_ -replace $Find, $Replace
return $_ -creplace $Find, $Replace
Set-Alias replace Edit-String
Set-Alias sc Set-Content
用法
# 1 file
$f = a.txt; gc $f | replace '[MYID]' 'MyValue' -SimpleMatch | sc $f
# 0 to many files
gci *.txt | % gc $_ | replace '\[MYID\]' 'MyValue' | sc $_
# Several replacements chained together
... | replace '[1-9]' T | replace a b -IgnoreCase | replace 'delete me' | ...
# Alias cheat sheet
# gci Get-ChildItem
# gc Get-Content
# sc Set-Conent
# % ForEach-Object
【讨论】:
【参考方案11】:这对我使用 PowerShell 中的当前工作目录很有用。您需要使用 FullName
属性,否则它在 PowerShell 版本 5 中不起作用。我需要在我的所有 CSPROJ
文件中更改目标 .NET 框架版本。
gci -Recurse -Filter *.csproj |
% (get-content "$($_.FullName)")
.Replace('<TargetFramework>net47</TargetFramework>', '<TargetFramework>net462</TargetFramework>') |
Set-Content "$($_.FullName)"
【讨论】:
【参考方案12】:有点老和不同,因为我需要在特定文件名的所有实例中更改某行。
另外,Set-Content
没有返回一致的结果,所以我不得不求助于Out-File
。
代码如下:
$FileName =''
$OldLine = ''
$NewLine = ''
$Drives = Get-PSDrive -PSProvider FileSystem
foreach ($Drive in $Drives)
Push-Location $Drive.Root
Get-ChildItem -Filter "$FileName" -Recurse | ForEach
(Get-Content $_.FullName).Replace($OldLine, $NewLine) | Out-File $_.FullName
Pop-Location
这是在这个 PowerShell 版本上最适合我的方法:
Major.Minor.Build.Revision
5.1.16299.98
【讨论】:
【参考方案13】:这是一个相当简单的,它支持多行正则表达式、多个文件(使用管道)、指定输出编码等。由于ReadAllText
方法,不推荐用于非常大的文件。
# Update-FileText.ps1
#requires -version 2
<#
.SYNOPSIS
Updates text in files using a regular expression.
.DESCRIPTION
Updates text in files using a regular expression.
.PARAMETER Pattern
Specifies the regular expression pattern.
.PARAMETER Replacement
Specifies the regular expression replacement pattern.
.PARAMETER Path
Specifies the path to one or more files. Wildcards are not supported. Each file is read entirely into memory to support multi-line searching and replacing, so performance may be slow for large files.
.PARAMETER CaseSensitive
Specifies case-sensitive matching. The default is to ignore case.
.PARAMETER SimpleMatch
Specifies a simple match rather than a regular expression match (i.e., the Pattern parameter specifies a simple string rather than a regular expression).
.PARAMETER Multiline
Changes the meaning of ^ and $ so they match at the beginning and end, respectively, of any line, and not just the beginning and end of the entire file. The default is that ^ and $, respectively, match the beginning and end of the entire file.
.PARAMETER UnixText
Causes $ to match only linefeed (\n) characters. By default, $ matches carriage return+linefeed (\r\n). (Windows-based text files usually use \r\n as line terminators, while Unix-based text files usually use only \n.)
.PARAMETER Overwrite
Overwrites a file by creating a temporary file containing all replacements and then replacing the original file with the temporary file. The default is to output but not overwrite.
.PARAMETER Force
Allows overwriting of read-only files. Note that this parameter cannot override security restrictions.
.PARAMETER Encoding
Specifies the encoding for the file when -Overwrite is used. Possible values for this parameter are ASCII, BigEndianUnicode, Unicode, UTF32, UTF7, and UTF8. The default value is ASCII.
.INPUTS
System.IO.FileInfo.
.OUTPUTS
System.String (single-line file) or System.String[] (file with more than one line) without the -Overwrite parameter, or nothing with the -Overwrite parameter.
.LINK
about_Regular_Expressions
.EXAMPLE
C:\> Update-FileText.ps1 '(Ferb) and (Phineas)' '$2 and $1' Story.txt
This command replaces the text 'Ferb and Phineas' with the text 'Phineas and Ferb' in the file Story.txt and outputs the content. Note that the pattern and replacement strings are enclosed in single quotes to prevent variable expansion.
.EXAMPLE
C:\> Update-FileText.ps1 'Perry' 'Agent P' Story2.txt -Overwrite
This command replaces the text 'Perry' with the text 'Agent P' in the file Story2.txt.
#>
[CmdletBinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
param(
[Parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true)]
[String[]] $Path,
[Parameter(Mandatory = $true,Position = 1)]
[String] $Pattern,
[Parameter(Mandatory = $true,Position = 2)]
[AllowEmptyString()]
[String] $Replacement,
[Switch] $CaseSensitive,
[Switch] $SimpleMatch,
[Switch] $Multiline,
[Switch] $UnixText,
[Switch] $Overwrite,
[Switch] $Force,
[ValidateSet("ASCII","BigEndianUnicode","Unicode","UTF32","UTF7","UTF8")]
[String] $Encoding = "ASCII"
)
begin
function Get-TempName
param(
$path
)
do
$tempName = Join-Path $path ([IO.Path]::GetRandomFilename())
while ( Test-Path $tempName )
$tempName
if ( $SimpleMatch )
$Pattern = [Regex]::Escape($Pattern)
else
if ( -not $UnixText )
$Pattern = $Pattern -replace '(?<!\\)\$','\r$'
function New-Regex
$regexOpts = [Text.RegularExpressions.RegexOptions]::None
if ( -not $CaseSensitive )
$regexOpts = $regexOpts -bor [Text.RegularExpressions.RegexOptions]::IgnoreCase
if ( $Multiline )
$regexOpts = $regexOpts -bor [Text.RegularExpressions.RegexOptions]::Multiline
New-Object Text.RegularExpressions.Regex $Pattern,$regexOpts
$Regex = New-Regex
function Update-FileText
param(
$path
)
$pathInfo = Resolve-Path -LiteralPath $path
if ( $pathInfo )
if ( (Get-Item $pathInfo).GetType().FullName -eq "System.IO.FileInfo" )
$fullName = $pathInfo.Path
Write-Verbose "Reading '$fullName'"
$text = [IO.File]::ReadAllText($fullName)
Write-Verbose "Finished reading '$fullName'"
if ( -not $Overwrite )
$regex.Replace($text,$Replacement)
else
$tempName = Get-TempName (Split-Path $fullName -Parent)
Set-Content $tempName $null -Confirm:$false
if ( $? )
Write-Verbose "Created file '$tempName'"
try
Write-Verbose "Started writing '$tempName'"
[IO.File]::WriteAllText("$tempName",$Regex.Replace($text,$Replacement),[Text.Encoding]::$Encoding)
Write-Verbose "Finished writing '$tempName'"
Write-Verbose "Started copying '$tempName' to '$fullName'"
Copy-Item $tempName $fullName -Force:$Force -ErrorAction Continue
if ( $? )
Write-Verbose "Finished copying '$tempName' to '$fullName'"
Remove-Item $tempName
if ( $? )
Write-Verbose "Removed file '$tempName'"
catch [Management.Automation.MethodInvocationException]
Write-Error $Error[0]
else
Write-Error "The item '$path' must be a file in the file system." -Category InvalidType
process
foreach ( $PathItem in $Path )
if ( $Overwrite )
if ( $PSCmdlet.ShouldProcess("'$PathItem'","Overwrite file") )
Update-FileText $PathItem
else
Update-FileText $PathItem
也可通过gist on Github 获得。
【讨论】:
【参考方案14】:对 Set-Content 命令的小修正。如果未找到搜索到的字符串,Set-Content
命令将清空(清空)目标文件。
您可以先验证您要查找的字符串是否存在。如果不是,它不会取代任何东西。
If (select-string -path "c:\Windows\System32\drivers\etc\hosts" -pattern "String to look for") `
(Get-Content c:\Windows\System32\drivers\etc\hosts).replace('String to look for', 'String to replace with') | Set-Content c:\Windows\System32\drivers\etc\hosts
Else"Nothing happened"
【讨论】:
欢迎来到 ***!请使用格式,如果需要帮助可以阅读this article。 这不是真的,如果使用正确的答案并且没有找到替换,它仍然写入文件,但没有任何更改。例如。set-content test.txt "hello hello world hello world hello"
和 (get-content .\test.txt).Replace("something", "awesome") | set-content .\test.txt
不会像这里建议的那样清空文件。以上是关于如何用 PowerShell 替换文件中每次出现的字符串?的主要内容,如果未能解决你的问题,请参考以下文章