Home > Mobile >  select-string with multiple conditions with powershell
select-string with multiple conditions with powershell

Time:08-02

I'm looking for a way to find 2 different lines in a file and only if those 2 line exist I need to perform a task. So far this is my code

$folderPath = c:\test
$files = Get-ChildItem $Folderpath -Filter *.txt
$find = 'stringA'
$find2 = 'StringB'
$replace = 'something to replace with string b'
if ($files.Length -gt 0  ) {
    $files |
    select -ExpandProperty fullname |
    foreach {
        If(Select-String -Path $_ -pattern $find , $find2 -quiet )
        {
            (Get-Content $_) |
            ForEach-Object {$_ -replace $find2, $replace } |
            Set-Content $_
            write-host "File Changed : " $_    
        } 
    }
}
else {
    write-host "no files changed"
}

Currently if I run it once it will change the files but if I run it again it will also notify me that it changed the same files instead of the output "no files changed"

Is there a simpler way to make it happen? Thanks

CodePudding user response:

The Select-String cmdlet selects lines matching any of the patterns supplied to it. This means that the following file contains a match:

PS> Get-Content file.txt
This file contains only stringA

PS> Select-String -Pattern 'stringA', 'stringB' -Path file.txt
file.txt:1:This file contains only stringA

Passing the -Quiet flag to Select-String will produce a boolean result instead of a list of matches. The result is $True even though only one of the patterns is present.

PS> Get-Content file.txt
This file contains only stringA

PS> Select-String -Pattern 'stringA', 'stringB' -Path file.txt -Quiet
True

In your case, Select-String chooses all the files containing either 'stringA' or 'stringB', then replaces all instances of 'stringB' in those files. (Note that replacements are also performed in files you did not want to alter)

Even after the replacements, files containing only 'stringA' still exist: these files are caught and reported by your script the second time you run it.

One solution is to have two separate conditions joined by the -and operator:

If (
    (Select-String -Path $_ -Pattern 'stringA' -Quiet) -and
    (Select-String -Path $_ -Pattern 'stringB' -Quiet)
)

After this the script should work as intended, except that it won't report "no files changed" correctly.

If you fix your indentation you'll realise that the final else clause actually checks if there are no .txt files in the folder:

$files = Get-ChildItem $Folderpath -Filter *.txt
...
if ($files.length -gt 0) {
    ...
} else {
    # will only display when there are no text files in the folder!
    Write-Host "no files changed"
}

The way to resolve this would be to have a separate counter variable that increments every time you find a match. Then at the end, check if this counter is 0 and call Write-Host accordingly.

$counter = 0
...
foreach {
    if ((Select-String ...) ...) {
        ...
        $counter  = 1
    }
}
if ($counter -eq 0) {
    Write-Host "no files changed"
}

CodePudding user response:

To complement equatorialsnowfall's helpful answer, which explains the problem with your approach well, with a streamlined, potentially more efficient solution:

$folderPath = c:\test
$searchStrings = 'stringA', 'stringB'
$replace = 'something to replace string B with'
$countModified = 0


Get-ChildItem $Folderpath -Filter *.txt | ForEach-Object {
  if (
    (
      ($_ | Select-String -Pattern $searchStrings).Pattern | Select-Object -Unique
    ).Count -eq $searchStrings.Count
  ) {
    ($_ | Get-Content -Raw) -replace $searchStrings[1], $replace |
      Set-Content $_.FullName
      $countModified
    write-host "File Changed: " $_    
  }
}

if ($countModified -eq 0) {
  Write-Host "no files changed"
}
  • A single Select-String call is used to determine if all pattern match (the solution scales to any number of patterns):

    • Each Microsoft.PowerShell.Commands.MatchInfo output object has a .Pattern property that indicates which of the patterns passed to -Pattern matched on a given line.

    • If, after removing duplicates with Select-Object -Unique, the number of patterns associated with matching lines is the same as the number of input patterns, you can assume that all input patterns matched (at least once).

  • Reading each matching file as a whole with Get-Content's -Raw switch and therefore performing only a single -replace operation per file is much faster than line-by-line processing.

  • Related