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


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

你可以尝试这样做:

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

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

注意(Get-Content file.txt)周围的括号是必需的:

Without the parenthesis the content is read, one line at a time, and flows down the pipeline until it reaches out-file or set-content, which tries to write to the same file, but it's already open by get-content and you get an error. The parenthesis causes the operation of content reading to be performed once (open, read and close). Only then when all lines have been read, they are piped one at a time and when they reach the last command in the pipeline they can be written to the file. It's the same as $content=content; $content | where ...


这是我使用的,但它在大文本文件上很慢。

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替换文档中的文本可能还有一种更优雅的方式,但这应该是一个很好的起点。


我更喜欢使用。net的file类和它的静态方法,如下面的例子所示。

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

这样做的优点是使用单个String,而不是像Get-Content那样使用String数组。这些方法还负责文件的编码(UTF-8 BOM等),而不需要您在大多数时候负责。

此外,与使用Get-Content和管道连接到Set-Content的算法相比,这些方法不会弄乱行结束符(可能会使用Unix行结束符)。

所以对我来说:几年下来会坏掉的东西更少。

在使用. net类时,一个鲜为人知的事情是,当您键入“[System.IO.”在PowerShell窗口中,您可以按Tab键来步进那里的方法。


上面的只对“一个文件”运行,但你也可以对文件夹中的多个文件运行:

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

对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"}

使用PowerShell中的当前工作目录,这对我来说很有效。您需要使用FullName属性,否则它将在PowerShell版本5中不起作用。我需要在所有CSPROJ文件中更改目标. net框架版本。

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

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

此外,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


这要归功于@rominator007

我把它包装成一个函数(因为你可能想再次使用它)

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

注意:这是不区分大小写的!!!!!

请看这篇文章:字符串。替换忽略大小写


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

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

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

值得注意的是,这里发布的不同方法在完成所需时间方面可能有很大不同。对我来说,我经常有大量的小文件。为了测试哪个性能最好,我在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

下面是一个相当简单的例子,它支持多行正则表达式、多个文件(使用管道)、指定输出编码等。由于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
    }
  }
}

在Github上也可以找到。


因为这个经常出现,我为它定义了一个函数。我默认使用区分大小写、基于正则表达式的匹配,但我添加了针对文字文本和忽略大小写的开关。

# 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

示例替换文件夹内的所有字符串:

$path=$args[0]
$oldString=$args[1]
$newString=$args[2]

Get-ChildItem -Path $path -Recurse -File | 
ForEach-Object { 
    (Get-Content $_.FullName).replace($oldString,$newString) | Set-Content $_.FullName 
}