如何用PowerShell替换文件中出现的每一个字符串?


289

使用PowerShell,我想用替换[MYID]给定文件中所有的确切出现MyValue。这样做最简单的方法是什么?


有关在内存消耗方面比在此问题的答案中提供的解决方案更有效的解决方案,请参阅在大文件中查找和替换
Martin Prikryl

Answers:


444

使用(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

3
谢谢-我收到一个错误“替换:方法调用失败,因为[System.Object []]不包含名为'replace'的方法。” 虽然?
2013年

\作为我刚刚发现的ps v4中的转义作品。谢谢。
ErikE 2013年

4
@rob管的结果集的内容或出文件,如果你想保存修改
卢瓦克MICHEL

2
我收到错误消息“由于[System.Object []]不包含名为'replace'的方法,所以方法调用失败。” 因为我试图在只有V2的计算机上运行V3版本。
SFlagg

5
警告:针对大型文件(几百兆字节左右)运行这些脚本可能会占用大量内存。如果您在生产服务器上运行,请确保您有足够的头部空间:D
新手

89
(Get-Content file.txt) | 
Foreach-Object {$_ -replace '\[MYID\]','MyValue'}  | 
Out-File file.txt

请注意,圆括号(Get-Content file.txt)必须为:

如果没有括号,则一次读取一行内容,然后沿管道向下流动,直到到达文件外或set-content为止,后者尝试写入同一文件,但是已经由get-content打开,您可以一个错误。括号使内容读取操作执行一次(打开,读取和关闭)。只有这样,当所有行都被读取后,它们才会一次被管道传输,并且当它们到达管道中的最后一条命令时,它们才能被写入文件。与$ content = content相同;$ content | 哪里...


5
如果可以的话,我会把我的赞成票改为反对票。在PowerShell 3中,这会从文件中静默删除所有内容!使用Set-Content而不是Out-Fileyuou会收到类似“该进程无法访问文件'123.csv”的警告,因为该文件正在被另一个进程使用。”
伊恩·塞缪尔·麦克莱恩

9
当get-content放在括号中时,不应发生这种情况。它们导致操作打开,读取和关闭文件,因此不会发生您收到的错误。您可以使用示例文本文件再次对其进行测试吗?
谢伊·利维

2
Get-Content括号括起来可以起作用。您能否在答案中解释为什么需要括号?我仍将替换Out-FileSet-Content它,因为它更安全。如果您忘记了括号,它可以防止您清除目标文件。
伊恩·塞缪尔·麦克莱恩

6
问题文件编码UTF-8 。保存文件时,更改编码。不一样。stackoverflow.com/questions/5596982/…。我认为 设置内容考虑了编码文件(如UTF-8)。但不超出文件
Kiquenet

1
当我使用它时,此解决方案会造成不必要的误导,并引起问题。我正在更新一个配置文件,该文件在安装过程中立即使用。配置文件仍由进程保留,安装失败。使用Set-Content代替Out-File是更好,更安全的解决方案。对不起,必须投票。
Martin Basista 2015年

81

我更喜欢使用.NET的File-class及其静态方法,如以下示例所示。

$content = [System.IO.File]::ReadAllText("c:\bla.txt").Replace("[MYID]","MyValue")
[System.IO.File]::WriteAllText("c:\bla.txt", $content)

这具有使用单个String代替Get-Content的String-array的优势。这些方法还可以处理文件的编码(UTF-8 BOM等),而无需您花费很多时间。

与使用Get-Content并通过管道传递到Set-Content的算法相比,这些方法也不会弄乱行尾(可能会使用Unix行尾)。

所以对我来说:多年来可能会发生的事情减少了。

使用.NET类时,鲜为人知的是,在PowerShell窗口中键入“ [System.IO.File] ::”后,您可以Tab按键以逐步浏览那里的方法。


您还可以通过命令查看方法 [System.IO.File] | gm
fbehrens

为什么此方法采用相对路径C:\Windows\System32\WindowsPowerShell\v1.0
阿德里安

是这样吗?这可能与在PowerShell中启动.NET AppDomain的方式有关。可能是,使用cd时当前路径未得到更新。但这不过是有根据的猜测。我没有对此进行测试或查找。
rominator007 '16

2
这比为不同版本的Powershell编写不同的代码要容易得多。
Willem van Ketwich,2016年

这种方法似乎也是最快的。将其与已指出的好处结合起来,问题应该是:“为什么还要使用其他任何东西?”
DBADon

21

上面的一个仅适用于“一个文件”,但是您也可以针对文件夹中的多个文件运行该文件:

Get-ChildItem 'C:yourfile*.xml' -Recurse | ForEach {
     (Get-Content $_ | ForEach  { $_ -replace '[MYID]', 'MyValue' }) |
     Set-Content $_
}

请注意,我使用了.xml,但是您可以用.txt替换
John V Hobbs Jr,

真好 除了使用内部,foreach您还可以执行此操作Get-ChildItem 'C:\folder\file*.xml' -Recurse | ForEach { (Get-Content $_).Replace('[MYID]', 'MyValue') | Set-Content $_ }
KCD

1
实际上,您确实需要使用inner foreach,因为Get-Content会执行您可能不希望的操作...它返回一个字符串数组,其中每个字符串都是文件中的一行。如果要循环浏览的目录(和子目录)与运行脚本的位置不同,则需要这样的目录: Get-ChildItem $Directory -File -Recurse | ForEach { (Get-Content $_.FullName) | ForEach { $_ -replace '[MYID]', 'MyValue' } | Set-Content $_.FullName }其中$Directory包含要修改的文件的目录在哪里。
birdamongmen16年

1
什么是“以上那个”?
Peter Mortensen

10

您可以尝试这样的事情:

$path = "C:\testFile.txt"
$word = "searchword"
$replacement = "ReplacementText"
$text = get-content $path 
$newText = $text -replace $word,$replacement
$newText > $path

7

这就是我使用的方法,但是在大型文本文件上速度很慢。

get-content $pathToFile | % { $_ -replace $stringToReplace, $replaceWith } | set-content $pathToFile

如果要替换大型文本文件中的字符串,并且需要考虑速度,请考虑使用System.IO.StreamReaderSystem.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替换文档中的文本可能是一种更为优雅的方法,但这应该为您提供一个良好的起点。


我认为集合内容考虑了编码文件(如UTF-8)。但不超出文件stackoverflow.com/questions/5596982/...
Kiquenet

2

我从Payette的Windows Powershell in Action中找到了一种鲜为人知但非常酷的方法。您可以引用类似于$ env:path的变量之类的文件,但需要添加花括号。

${c:file.txt} = ${c:file.txt} -replace 'oldvalue','newvalue'

如果文件名在诸如这样的变量中$myFile怎么办?
ΩmegaMan

到目前为止,@ΩmegaMan嗯$a = 'file.txt'; invoke-expression "`${c:$a} = `${c:$a} -replace 'oldvalue','newvalue'"
js2010年

2

如果您需要替换多个文件中的字符串:

应该注意的是,此处完成的时间可能与此处发布的不同方法完全不同。对我来说,我经常有大量的小文件。为了测试最高性能,我在40,693个单独的文件中提取了5.52 GB(5,933,604,999字节)的XML,并通过以下三个答案进行了测试:

## 5.52 GB (5,933,604,999 bytes) of XML files (40,693 files) 

#### Test 1 - Plain Replace
$start = get-date
$xmls = (Get-ChildItem -Path "I:\TestseT\All_XML" -Recurse -Filter *.xml).FullName
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
$xmls = (Get-ChildItem -Path "I:\TestseT\All_XML" -Recurse -Filter *.xml).FullName
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
$xmls = (Get-ChildItem -Path "I:\TestseT\All_XML" -Recurse -Filter *.xml).FullName
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
#>

问题是关于替换给定文件而不是多个文件中的字符串。
PL

1

归功于@ rominator007

我将其包装到一个函数中(因为您可能想再次使用它)

function Replace-AllStringsInFile($SearchString,$ReplaceString,$FullPathToFile)
{
    $content = [System.IO.File]::ReadAllText("$FullPathToFile").Replace("$SearchString","$ReplaceString")
    [System.IO.File]::WriteAllText("$FullPathToFile", $content)
}

注意:这不区分大小写!

参见这篇文章:String.Replace忽略大小写


0

使用PowerShell中的当前工作目录,这对我有用。您需要使用该FullName属性,否则在PowerShell版本5中将无法使用。我需要在所有CSPROJ文件中更改目标.NET Framework版本。

gci -Recurse -Filter *.csproj |
% { (get-content "$($_.FullName)")
.Replace('<TargetFramework>net47</TargetFramework>', '<TargetFramework>net462</TargetFramework>') |
 Set-Content "$($_.FullName)"}

0

有点陈旧和不同,因为我需要在特定文件名的所有实例中更改特定的行。

另外,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版本上,这是最适合我的方法:

主要,次要,构建,修订

5.1.16299.98


-1

对“设置内容”命令进行了较小的更正。如果找不到搜索到的字符串,该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"}

3
欢迎来到StackOverflow!请使用格式,如果需要帮助,可以阅读本文
CodenameLambda

1
这是不正确的,如果使用正确的答案但未找到替换项,它仍会写入文件,但没有任何更改。例如set-content test.txt "hello hello world hello world hello",然后(get-content .\test.txt).Replace("something", "awesome") | set-content .\test.txt不会按照此建议清空文件。
Ciantic
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.