如何用PowerShell替换文件中每次出现的String?

时间:2013-06-17 09:29:36

标签: powershell

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

14 个答案:

答案 0 :(得分:374)

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

答案 1 :(得分:86)

(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相同; $ content |哪里......

答案 2 :(得分:71)

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

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

Get-Content一样,这样做的好处是可以使用单个String而不是String-array。这些方法还负责文件的编码(UTF-8 BOM等),而不必在大多数时间都小心。

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

对我而言:这些年来可能会破坏的事情会减少。

使用.NET类时,一个鲜为人知的事情就是输入" [System.IO.File] ::"在PowerShell窗口中,您可以按 Tab 键来逐步执行那里的方法。

答案 3 :(得分:19)

上面的一个只运行" One File"但是,你也可以为你文件夹中的多个文件运行它:

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

答案 4 :(得分:10)

您可以尝试这样的事情:

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

答案 5 :(得分: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替换文档中的文本可能有一种更优雅的方式,但这应该会给你一个很好的起点。

答案 6 :(得分: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
#>

答案 7 :(得分:1)

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

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

答案 8 :(得分:0)

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

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

答案 9 :(得分: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版本最适合我的方式:

  

Major.Minor.Build.Revision

     

5.1.16299.98

答案 10 :(得分:0)

贷记到@ rominator007

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

1634

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

查看此信息:String.Replace ignoring case

答案 11 :(得分:0)

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

答案 12 :(得分:-1)

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

答案 13 :(得分:-2)

搜索过多后,我找出了最简单的行,没有更改编码

@IBAction func  btnAction(_ sender : UIButton){

        // get Indexpath with Button position

         let buttonPosition = sender.convert(CGPoint.zero, to: self.tableview)
         let indexPath: IndexPath? = tableview.indexPathForRow(at: buttonPosition)

        self.yourArrayDatasource.remove(at: indexPath.row)  // don't forget to replace your array of data source

        self.tableview.deleteRows(at: [indexPath!], with: UITableViewRowAnimation.automatic)


    }