Search code examples
powershellfull-text-searchtext-filesstring-operations

powershell import data from text file after string


There seems to be questions that are similar floating around on the web although the answers vary and i just cannot get the outcome i am looking for.

Below simply appends the content within the txt file at the end of the file

Add-Content -Path "HeartBeat.txt" -Value (Get-Content "insertme.txt")

although i want to using powershell to find a string in a text file and then copy the contents from another text file after this found string.

Factitious example of a text file searching for string "HeartBeat"

<xml>
<Server>
  <HeartBeat>1</HeartBeat>
</Server>

There is another text file called "insertme.txt" that will insert its contents on a new line after "HeartBeat"

<dummydata>
<port></port>
<ipaddress></ipaddress>

Once appended will be

<xml>
<Server>
  <HeartBeat>1</HeartBeat>
  <dummydata>
  <port></port>
  <ipaddress></ipaddress>
</Server>

Any help would be appreciated.

Thank you


Solution

  • If you want a truly robust solution, use XML processing, which PowerShell supports via the [xml](System.Xml.XmlDocument) .NET type.

    If you still want a purely text-based solution, try the following (PSv3+; reads both files into memory in full; I'm using a LF-only newline ("`n") in the code; replace it with "`r`n" if you want CRLF newlines):

    (Get-Content -Raw Heartbeat.txt) -replace
      '(?m)^.*<HeartBeat>.*$',
      ('$&' + "`n" + (Get-Content -Raw InsertMe.txt).TrimEnd() -replace '(?m)^', '  '))|
        Set-Content HeartBeat.txt # PSv5+: Add -NoNewline to avoid extra newline
    

    Note: The only reason that it is possible to both read from Heartbeat.txt and write back to it in a single pipeline is that its content is read into memory up-front, in full, due to the (...) around the Get-Content command.
    While this approach is convenient, it also bears a slight risk of data loss, which can occur if the pipeline is interrupted before all content has been written back to the file.

    • Get-Content -Raw reads a file into memory in full, as a single string.

    • Regex '(?m)^.*<HeartBeat>.*$' matches a single line that contains substring <HeartBeat> (note that the -replace operator generally looks for multiple matches)

    • Replacement expression '$&' + "`n" + (Get-Content -Raw InsertMe.txt).TrimEnd() replaces the matched line with itself ($&), followed by a newline and the content of file InsertMe.txt.

      • .TrimEnd() is used to remove trailing newlines (and whitespace in general) so that insertion does not vary based on whether the file happens to end in a newline or not.
    • If you want to avoid an extra newline at the end of the updated file,
      use Set-Content -NoNewline, but note that this requires PSv5+.
      In earlier versions you could apply .TrimEnd() to the entire expression before sending it to Set-Content.


    If you want to modify the insertion to apply a fixed indentation to each line of the inserted content:

    (Get-Content -Raw Heartbeat.txt) -replace
      '(?m)^.*<HeartBeat>.*$',
      ('$&' + "`n" + (Get-Content -Raw InsertMe.txt).TrimEnd() -replace '(?m)^', '  '))|
        Set-Content HeartBeat.txt # PSv5+: Add -NoNewline to avoid extra newline