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.