Warm tip: This article is reproduced from serverfault.com, please click

Powershell scipt for replacing a line in every file in directory doesn't work

发布于 2020-11-30 14:45:25

I wanted to make a powershell script to make altering a line in multiple files go faster.

It's really the first time I'm trying to make something like this. What I basically want to do :

  1. Select all the files in a directory that have a ".xml" extension
  2. Go through every line per file and check if the it contains de following text (example: <package>true</package>) /
  3. Then I want to change the above example text to the new text (example: <package>false</package>) Also if the file doesn't contain <package>true</package> I also want to add <package>false</package>
  4. After that it should just save the file not create a new one somewhere else.

I've never really used powershell before so I did some digging and came up with this code:

$files = Get-ChildItem –Path 'C:\test-XML\XML' -Recurse -Filter *.xml 
$tempFilePath = 'C:\test-XML\newXml'
$find = '<packag>true</package>'
$replace = '<packag>false</package>'


foreach ($f in $files){
   (Get-Content $f) -replace $find, $replace | Add-Content -Path $tempFilePath
}

With this code I should be able to get all the files in the directory and then loop trough it and change the text with the newer text. But it doesn't work and it also doesn't cover all the points I needed to make the task easier.

Does anyone have insights to make it work and or even make it better than it is now.

Questioner
imkeVr
Viewed
0
mklement0 2020-12-01 02:14:38

Generally speaking, it's worth heeding zett42's advice: For processing XML files, it is more robust to use a dedicated XML parser, notably [xml] (System.Xml.XmlDocument).

If plain-text processing is an option - and it sounds like it is for you - you can use the following approach, which updates all files in place; it is optimized for performance:

Get-ChildItem –LiteralPath C:\test-XML\XML -Recurse -Filter *.xml |
  ForEach-Object {
    # CAUTION: This updates files *in place*.
    #          Adjust the encoding as needed.
    Set-Content -Encoding Utf8 $_.FullName -Value (
      (Get-Content -Raw $_.FullName) -replace '<package>true</package>', '<package>false</package>'
    )
  }

Note:

  • The primary problem with your code was presumably just a typo: <packag> -> <package>.

  • The file content is updated in place, so it's best to back up the files first, to be safe. There's a hypothetical risk of file corruption if the process of writing back the updated content gets interrupted.

  • For efficiency, Get-Content -Raw reads the entire file into a single, multi-line string (the default behavior is to output an array of lines, which is much slower), so that only a single -replace operation is needed. If nothing matches in the input string, it is returned as-is.

  • Note that PowerShell always uses a file-writing cmdlet's default character encoding on output, irrespective of the encoding that the input file uses, hence the explicit use of Set-Content's
    -Encoding parameter.

    • See this answer for more information about default encodings in Windows PowerShell vs. PowerShell [Core] v6+.
  • Note that the updated content is passed to Set-Content -Value parameter rather than via the pipeline ($content | Set-Content ...); while it makes little difference in performance here, given that only one string is passed, with array-of-lines processing it would: if, say, $content contains the array of lines to write to file, Set-Content ... -Value $content will be much faster than $content | Set-Content ...

  • For brevity, the Set-Content and Get-Content call pass the filename argument positionally, which implicitly binds to the -Path parameter, which, strictly speaking, accepts wildcard patterns; typically, that works as expected even if you pass a literal filename, but the more robust approach is to use the -LiteralPath parameter, as shown in the Get-ChildItem call above; specifically, literal filenames that contain [ and ] result in misinterpretation by the -Path parameter, except if they're escaped .