Find files which does not contains selected string - powershell

I am trying to find all files, which does not contains a selected string. Find files which contains is easy:
gci | select-string "something"
but I do not have an idea how to negate this statement.

You can use Where-Object;
gci | Where-Object { !( $_ | Select-String "something" -quiet) }

I'm not sure if it can be done without the foreach-object but this works:
gci |foreach-object{if (-not (select-string -inputobject $_ -Pattern "something")){$_}}

As mentionend in How do I output lines that do not match 'this_string' using Get-Content and Select-String in PowerShell?
Select-String has the NotMatch parameter.
So you could use it:
gci | Select-String -notmatch "something"

foreach($line in Get-Content .\file.txt)
{
if(findstr $line "dummyText.txt" ){
# Work here or skip here
}
else {
echo $line
}

Related

select-string with multiple conditions with powershell

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
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"
}
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.

Replacing characters from multiple files with regex and Powershell

I would like to change _ to - in all .md files from folder FOO. The code below does what I need but I don't how to save results in folder FOO or some other...
$mdfiles = gci *.md
gc $mdfiles | ForEach-Object {if ( $_ -match '^!') {$_ -replace '_', '-'} else {$_}} | out-file ...
A ForEach-Object is needed to iterate over the file names as well. Ternary expressions are in PowerShell Core, but I am not sure about Windows PowerShell. This is not tested, but might give a start.
Get-ChildItem -File -Path '.' -Filter '*.md' |
ForEach-Object {
$OutFile = ".\foo\$($_.Name)"
Get-Content -Path $_.FullName |
ForEach-Object { ($_ -match '^!') ? ($_ -replace '_','-') : ($_) } |
Out-File -FilePath $OutFile
}
Also, it is bad practice to use alias commands in a stored script.

powershell: find all strings matching pattern in a file

I am looping through all files matching the pattern maint-*.js.
These files contains tokens in the form of __MyTokenA__, __MyTokenB__, etc...so I would like to find all of these tokens. I've tried the regex below, but it doesn't find anything.
I'd like to store the tokens in an array. What would be the correct way ?
$files = Get-ChildItem dist/main-*.js
Foreach ($file in $files)
{
$matches = $file | Select-String ', "(?:\(__\))(.*?)(?:\(__\))"' -AllMatches
echo $matches
}
I think you can simplyfy your regex, then enumerate the matches property.
$file | Select-String '__(.*?)__' -AllMatches | ForEach-Object {
$_
$_.Matches | Select-Object Value
}

Powershell if changed Set-Content

I have a PowerShell script that I use to change text in a number of files. The following script will work & changes the text as expected.
Get-ChildItem $FileFolder -Recurse |
select -ExpandProperty fullname |
foreach {
(Get-Content $_) |
ForEach-Object {$_ -replace $old $new } |
Set-Content $_
}
The problem is though, it changes every file that it opens, so everything has a timestamp of when the job was run even if nothing was changed.
I have tried something similar to what is here but it gives me an error:
The term 'if' is not recognized as the name of a cmdlet, function, etc...
Here is the code I am trying to run:
Get-ChildItem $FileFolder -Recurse |
select -ExpandProperty fullname |
foreach {
$b = ($a = Get-Content $_) |
ForEach-Object {$_ -replace $old $new } |
if (Compare $a $b -PassThru) {
$b | Set-Content $_
}
}
I know that the code isn't right, but if I move it inside the ForEach-Object, it won't run either.
What I want to do is to use the Set-Content statement only if the contents of the file have changed. Would appreciate any thoughts as to how best to do this.
What you can do is look for the string before getting and setting content. Something like:
Get-ChildItem $FileFolder -Recurse |
select -ExpandProperty fullname |
foreach {
If(Select-String -Path $_ -SimpleMatch $old -quiet){
(Get-Content $_) |
ForEach-Object {$_ -replace $old $new } |
Set-Content $_
}
}

Is there a PowerShell "string does not contain" cmdlet or syntax?

In PowerShell I'm reading in a text file. I'm then doing a Foreach-Object over the text file and am only interested in the lines that do NOT contain strings that are in $arrayOfStringsNotInterestedIn.
What is the syntax for this?
Get-Content $filename | Foreach-Object {$_}
If $arrayofStringsNotInterestedIn is an [array] you should use -notcontains:
Get-Content $FileName | foreach-object { `
if ($arrayofStringsNotInterestedIn -notcontains $_) { $) }
or better (IMO)
Get-Content $FileName | where { $arrayofStringsNotInterestedIn -notcontains $_}
You can use the -notmatch operator to get the lines that don't have the characters you are interested in.
Get-Content $FileName | foreach-object {
if ($_ -notmatch $arrayofStringsNotInterestedIn) { $) }
To exclude the lines that contain any of the strings in $arrayOfStringsNotInterestedIn, you should use:
(Get-Content $FileName) -notmatch [String]::Join('|',$arrayofStringsNotInterestedIn)
The code proposed by Chris only works if $arrayofStringsNotInterestedIn contains the full lines you want to exclude.