Home > Mobile >  Replacing content in a text file if it matches patterns while leaving non-matches Intact
Replacing content in a text file if it matches patterns while leaving non-matches Intact

Time:01-12

I have a text file where I need to edit some rows, but the script only outputs the rows that were edited. I want the output to be the original file, just with the rows I want to edit being the only thing that changes. My file is tab delimited and looks like this

File.txt  
ABC 123456 987654321 DEF 0 More Text  
ABC 123457 987654320 DEF 0 More Text  
XYZ 123457 987654320 DEF 0 More Text  

I want the file to output this

ABC 123456 987654321 DEF 0 More Text  
ABC 123457 987654320 GHI 1 More Text  
XYZ 123457 987654320 DEF 0 More Text

This is what I have. $arrExcelValues is an array of values pulled from an excel file

foreach ($file in $files){
    get-content -Path $file | 
             where {$_ | Select-String -Pattern '^ABC. '}| 
             where {$_ | Select-String -Pattern $arrExcelValues} | % {$_ -replace "DEF`t0","GHI`t1"} | Set-Content $out
}

Powershell Identifies the rows to edit based on matching patterns, but my output is just the edited row

ABC 123457 987654320 GHI 1 More Text

I believe that the issue is that where is filtering out everything that is not a match, but once the matching rows have been changed, I can't figure out how to get an output with all of the original file contents & my edited rows

CodePudding user response:

Both Select-String and Where-Object filter their input, so by definition you will only get filtered (potentially modified) output, not all input.

As an aside:

  • Import-Csv / Export-Csv can process TSV (tab-separated values) data as well, using -Delimiter "`t"
  • However, you may have to supply headers via the -Headers parameter, and in Windows PowerShell you'll invariably get double-quoted fields in the output (you can opt-out in Powershell (Core) 7 ).
  • The convenience of OOP processing via these cmdlets comes at the cost of diminished performance.

Sticking with your plain-text parsing approach, I suggest using a switch statement, which allows you to combine selective processing with passing all (potentially modified) lines through; as an added bonus, switch -File is faster than a Get-Content-based pipeline:

# Simulate file input as an array of lines.
$lines = @'
ABC 123456  987654321   DEF 0   More    Text
ABC 123457  987654320   DEF 0   More    Text
XYZ 123457  987654320   DEF 0   More    Text
'@ -split '\r?\n'

# The values to match the 2nd column against.
$arrExcelValues = '123457'

# Note:
#  * The following outputs to the *display* by default.
#  * Replace `($lines)` with `-File $file` in your code.
#  * Wrap the entire statement in `& { ... }` so you can pipe to `Set-Content`
#    * Use `$(...)` instead if you want to write back to the input file.
switch -Regex ($lines) {
  '^ABC\t' {
    $fields = $_ -split "`t"
    if ($fields[1] -in $arrExcelValues) {
      $fields[3] = 'GHI'
      $fields[4] = 1
      $fields -join "`t"
    } else {
      $_ # pass through
    }
  }
  default { $_ } # pass through
}
  • Related