IF with multi MATCH Comparison Operators - powershell

I facing with create shortest code for matching multiple words in single line.
My script file name could have long random name, but its always contains part of bellow names quoted in code.
This bellow code unfortunately does't work fine:
$ScriptFileName = (Get-Item $PSCommandPath).Basename
If (($ScriptFileName -match "Report", "Global", "Monthly")) {
Write-Output "All days"
} ElseIF (($ScriptFileName -match "Day", "Hour", "Present")) {
Write-Output "Today"
} Else {
Write-Output "Other file name"}
Pause
Output of this code is always caching last Else - "Other file name".
I think my problem here is -match isn't best operator for part of multiple file names.
Working code with -match is like that:
$ScriptFileName = (Get-Item $PSCommandPath).Basename
If (($ScriptFileName -match "Reaport") -or ($ScriptFileName -match "Global") -or ($ScriptFileName -match "Monthly")) {
Write-Output "All days"
} ElseIF (($ScriptFileName -match "Day") -or ($ScriptFileName -match "Hour") -or ($ScriptFileName -match "Present")) {
Write-Output "Today"
} Else {
Write-Output "Other file name"}
Pause
But is quite annoying to write it like this way :|
I also tried with operator: "-in", but its can't match only part of file name, right?
Can someone have some better idea - is this can be in possible shortest and clearer way written?

Allowing multiple patterns as the RHS of -like and -match - so as to return $true if any of them match - is not implemented in PowerShell as of 7.3.2, but would be a helpful future improvement - GitHub issue #2132 asks for just that.
Olaf has provided the crucial pointer: use the regex alternation construct |, to match multiple patterns using with a single regex string:
if (($ScriptFileName -match 'Report|Global|Monthly')) { # ...
# With an array variable as input, using the -join operator.
$patterns = "Report", "Global", "Monthly"
if (($ScriptFileName -match ($patterns -join '|'))) { # ...
The alternative is to use Select-String, which does support multiple patterns; with the -Quiet switch, it returns $true in case of a match, like -match:[1]
# You may even omit quoting around the patterns in this simple case.
if ($ScriptFileName | Select-String -Quiet "Report", "Global", "Monthly") { # ...
Note: -Quiet additionally ensures that matching against multiple inputs stops once a match is found.
The Select-String approach, while slower than -match, has the advantage that you can opt to have the patterns interpreted as literal substrings rather than as regexes, namely with with the -SimpleMatch switch:
# -SimpleMatch treats the patterns as *literals*.
# Without it, an error would occur, because the patterns aren't valid *regexes*.
'A * is born', 'A+', 'No match' | Select-String -SimpleMatch *, +
By contrast, -match invariably treats its RHS string operand as a regex, so if you want to treat the search strings as literal substrings, you'll have to \-escape regex metachacharacters, such as ., * and +, or call [regex]::Escape() on the string as a whole:
# With metacharacter-individual \-escaping:
# Note that -match, as all comparison operators, can operate on an
# *array of inputs*, in which case it acts as a *filter* and returns
# a *subarray of matching strings* (and the automatic $Matches variable is then
# *not* populated).
'A * is born', 'A+', 'No match' -match (('\*', '\+') -join '|')
# With whole-pattern escaping:
'A * is born', 'A+', 'No match' -match (('*', '+').ForEach({ [regex]::Escape($_) }) -join '|')
Further considerations:
Unfortunately, PowerShell has no operator for literal substring matching (but does offer Select-String -SimpleMatch, as discussed): -match uses regexes, -like uses wildcard expressions) and matches input strings in full. Escaping is needed in order for these operators to treat a pattern as a literal string (as already shown for -match above); to do this programmatically is cumbersome:
$pattern = '*'
# Same as:
# 'A * is born' -match '\*'
'A * is born' -match [regex]::Escape($pattern)
# Same as:
# 'A * is born' -like '*`**'
'A * is born' -like ('*{0}*' -f [wildcardPattern]::Escape($pattern))
The -contains operator - despite its similarity in name to the [string] type's .Contains() method - does not perform substring matching and instead tests whether a collection/array contains a given (single) element in full. The -in operator behaves the same, albeit with reversed operands:
# -> $true, but only because search string 'two'
# matches an array element *in full*.
'one', 'two', 'three' -contains 'two'
# Equivalent -in operation.
'two' -in 'one', 'two', 'three'
Using the .Contains() method - available on a single input string and with a single substring to search for - is an option, but this method is invariably case-sensitive in Windows PowerShell; in PowerShell (Core) 7+, case-insensitivity is available on demand (whereas it is the default for PowerShell's operators, which offer c-prefixed variants such as -cmatch for case-sensitivity). The workaround for Windows PowerShell is to use the .IndexOf() method:
$substring = '* is'
# Case-SENSITIVE
'A * is born'.Contains($substring)
# Case-INSENSITIVE
# PowerShell 7+ ONLY:
# 'InvariantCultureIgnoreCase' is auto-converted to
# [StringComparison]::InvariantCultureIgnoreCase in this case.
'A * IS BORN'.Contains($substring, 'InvariantCultureIgnoreCase')
# Windows PowerShell workaround, via .IndexOf()
-1 -ne 'A * IS BORN'.IndexOf($substring, [StringComparison]::InvariantCultureIgnoreCase)
Select-String -SimpleMatch is the superior alternative, given that it supports multiple search strings as well as multiple input strings and also works in both PowerShell editions, case-insensitively by default (as PowerShell is in general), with a -CaseSensitive opt-in (still, introducing a substring-matching operator as well would help, both for performance and brevity):
# Single input string, single search string.
# Add -CaseSensitive for case-sensitivity.
# -Quiet returns $true if the match succeeds.
$substring = '* is'
'A * IS BORN' | Select-String -Quiet -SimpleMatch $substring
# Multiple input strings, multiple search strings.
# Due to NOT using -Quiet, all matches are returned, similar
# to the filtering behavior of -match with an array LHS.
# `ForEach-Object Line` returns the matching *strings* only,
# by extracting the .Line property value from the match-info objects.
$substrings = '* is', ' A+'
'A * IS BORN', 'Got an A+', 'Not me' |
Select-String -SimpleMatch $substrings |
ForEach-Object Line
# Simpler PowerShell 7+-only alternative:
# The -Raw switch returns the matching input strings directly.
$substrings = '* is', ' A+'
'A * IS BORN', 'Got an A+', 'Not me' |
Select-String -SimpleMatch -Raw $substrings
[1] Unlike with -match (with a single-string LHS), if there is no match, nothing is returned, but in a Boolean context such as an if statement that is equivalent to $false.

Arrays aren't meant to be on the right side, but on the left is fine:
'Report', 'Global', 'Monthly' -match 'L'
Global
Monthly
I've seen this question with -match or -like few times. People want this. But it converts the right side to one string, and this ends up true:
if ('Report Global Monthly' -match 'Report', 'Global', 'Monthly') {
$true
}
True
Select-string can take an array of patterns. You can use "select-string -quiet", but any result ends up being true anyway.
if('Report' | select-string 'Report', 'Global', 'Monthly') {
$true
}
True

Related

powershell filter filenames with regex

I am building a list of files that I'm putting into my $list variable.
Then I want to filter the list based on the $filter variable. The current solution works, but it doesn't work with a regex.
$filter = #("test.txt","Fake","AnotherFile\d{1..6}")
######### HTML TESTS #############
[string]$list = #"
FakeFile.txt
test120119.txt
AnotherFile120119.txt
LastFile.txt
"#
[array]$files = $list -split '\r?\n'
$files = $files | Where-Object {$_} | Where {$_ -notin $filter} # filter out empty items from the array...
$files
My idea is to put regex patterns in the $filter variable so I can catch filenames that have datestamps in them such as test120119.txt in the $list variable above.
How can I change my code to allow for regex? I tried some variations of select-string without splitting my $list, but was not fruitful. I also tried changing my -notin to -notmatch but this doesn't work at all of course.
If you want to use regex, I think it would be easier to just fully commit to regex with your $filter array.
$filter = "^test\d{0,6}\.txt","^Fake","^AnotherFile\d{0,6}\.txt" -join '|'
$list = #"
FakeFile.txt
test120119.txt
AnotherFile120119.txt
LastFile.txt
"#
$files = $list -split '\r?\n'
$files | Where {$_ -notmatch $filter}
The thing to keep in mind is remembering to escape special regex characters if you want them treated literally. You can use the [regex]::Escape() method to do this for you but not if you already purposely injected regex characters.
Once you have your regex filter list, you can join each item with a regex or using the | character.
Not all operators recognize regex language. -match and -notmatch are among the few that do. -match and -notmatch are not case-sensitive. If you want to match against case, you should use the -c variants of the operators, namely -cmatch and -cnotmatch.
The regex items can be tweaked to your liking. More requirements would need to be given in order to come up with an exact solution. Here are some examples to consider:
\d{0,6} matches 0 to 6 consecutive digits. 122619 will match successfully, but so will 1226. If you want only 0 or 6 digits to match, you can use (\d{6})?.
^ should be used if you want to start each match at the beginning of the input string. So if you want the regex or to apply from the beginning of the string, you need to include ^ in each item or group items succeeding the initial ^ with () accordingly. ^item1|^item2 will return the same capture group 0 match as ^(item1|item2).
\ escape the literal . characters.
Not using anchor characters like ^ and $ create a lot of flexibility and potentially unwanted results. 'FakeFile' -match 'Fake' returns true but so does 'MyFakeFile' -match 'Fake'. However, 'MyFakeFile' -match 'Fake$' returns false and 'MyFake' -match 'Fake$' returns true.

Leftpad doesn't work when using with regex in Powershell

Here are two code blocks which show the strange behavior that Leftpad does.
$array = "z", "fg"
$array -replace "(\w{1,2})", '$1'.PadLeft(2, "0")
# output: z, fg
$array = "z", "fg"
$array -replace "(\w{1,2})", '$1'.PadLeft(3, "0")
# output: 0z, 0fg
How comes the pad length not fixed?
EDIT: #LotPings
I ask this another question is because of your way of doing it when applied to rename-item statement will not affect files with brackets in its names.
$file_names = ls "G:\Donwloads\Srt Downloads\15" -Filter *.txt
# the files are: "Data Types _ C# _ Tutorial 5.txt", "If Statements (con't) _ C# _ Tutorial 15.txt"
$file_names |
ForEach{
if ($_.basename -match '^([^_]+)_[^\d]+(\d{1,2})$')
{
$file_names |
Rename-Item -NewName `
{ ($_.BaseName -replace $matches[0], ("{0:D2}. {1}" -f [int]$matches[2],$matches[1])) + $_.Extension }
}
}
# output: 05. Data Types.txt
# If Statements (con't) _ C# _ Tutorial 15.txt
As for the .PadLeft, I thought that the regex replacement group is of string type, it should work with .PadLeft but it doesn't.
Ansgars comment to your last question should have shown that your assumption on the order of actions was wrong.
And Lee_Dailey proved you wrong another time.
My answer to your previous question presented an alternative which also works here:
("Data Types _ C# _ Tutorial 5", "If Statements (con't) _ C# _ Tutorial 15") |
ForEach{
if ($_ -match '^([^_]+)_[^\d]+(\d{1,2})$'){
"{0:D2}. {1}" -f [int]$matches[2],$matches[1]
}
}
Sample output:
05. Data Types
15. If Statements (con't)
The last edit to your question is in fact a new question...
Rename-Item accepts piped input, so no ForEach-Object neccessary when also using
Where-Object with the -match operator to replace the if.
the $Matches collection is supplied the same way.
I really don't know why you insist on using the -replace operator when building the NewName from scratch with the -format operator.
$file_names = Get-ChildItem "G:\Donwloads\Srt Downloads\15" -Filter *.txt
$file_names | Where-Object BaseName -match '^([^_]+)_[^\d]+(\d{1,2})$' |
Rename-Item -NewName {"{0:D2}. {1}{2}" -f [int]$matches[2],$matches[1].Trim(),$_.Extension} -WhatIf
Several days after asking this question, I happen to figure out the problem.
The $number capture group references in -replace syntax are merely literal strings!
Powershell never treats them as anything special, but the Regex engine does. Look at the example below:
$array = "z", "fg"
$array -replace "(\w{1,2})", '$1'.Length
#output: 2
# 2
Looks strange? How comes the $1 capture group has both Lengths of 2 with "z" and "fg"? The answer is that the length being calculated is the string $1 rather than "z","fg"!
Let's look at another example, this time lets replace a letter within the capture group, see what happens:
$array -replace "(\w{1,2})", '$1'.Replace("z", "6")
#output: z
# fg
The output shows the .replace didn't apply to the capture group 1.
$array -replace "(\w{1,2})", '$1'.Replace("1", "6")
#output: $6
# $6
See? The string being replaced is $1 itself.
Now the cause of the .padleft problem should be understood. PS pad the literal string $1 and show the result with the content of the group.
When I pad it with .Padleft(2, "0"), nothing happens because the "$1" itself has the length of 2.
$array -replace "(\w{1,2})", '$1'.PadLeft(2, "0")
# output: z
# fg
If instead, I pad it with .Padleft(3, "0"), this time the pad method does take effect, it applies the extra "0" to $1 but shows the result with the "0" preceding the content of $1.
$array -replace "(\w{1,2})", '$1'.PadLeft(3, "0")
#output: 0z
# 0fg

Contains operator not working in Powershell

I have never got the -contains operator to work in Powershell I don't know why.
Here's an example of where it isn't working. I use -like in its place but I'd love it if you could tell me why this isn't working.
PS HKLM:\Software\Microsoft\Windows NT\CurrentVersion> (gp . P*).ProductName
Windows 10 Enterprise
PS HKLM:\Software\Microsoft\Windows NT\CurrentVersion> (gp . P*).ProductName -contains "Windows"
False
PS HKLM:\Software\Microsoft\Windows NT\CurrentVersion> (gp . P*).ProductName | gm | select TypeName | Get-Unique
TypeName
--------
System.String
The -contains operator is not a string operator, but a collection containment operator:
'a','b','c' -contains 'b' # correct use of -contains against collection
From the about_Comparison_Operators help topic:
Type Operator Description
Containment -contains Returns true when reference value contained in a collection
-notcontains Returns true when reference value not contained in a collection
-in Returns true when test value contained in a collection
-notin Returns true when test value not contained in a collection
Usually you would use the -like string operator in PowerShell, which supports Windows-style wildcard matching (* for any number of any characters, ? for exactly one of any character, [abcdef] for one of a character set):
'abc' -like '*b*' # $true
'abc' -like 'a*' # $true
Another alternative is the -match operator:
'abc' -match 'b' # $true
'abc' -match '^a' # $true
For verbatim substring matching, you would want to escape any input pattern, since -match is a regex operator:
'abc.e' -match [regex]::Escape('c.e')
An alternative is to use the String.Contains() method:
'abc'.Contains('b') # $true
With the caveat that, unlike powershell string operators, it's case-sensitive.
String.IndexOf() is yet another alternative, this one allows you to override the default case-sensitivity:
'ABC'.IndexOf('b', [System.StringComparison]::InvariantCultureIgnoreCase) -ge 0
IndexOf() returns -1 if the substring is not found, so any non-negative return value can be interpreted as having found the substring.
The '-contains' operator is best used for comparison to lists or arrays, e.g.
$list = #("server1","server2","server3")
if ($list -contains "server2"){"True"}
else {"False"}
output:
True
I'd suggest using '-match' instead for string comparisons:
$str = "windows"
if ($str -match "win") {"`$str contains 'win'"}
if ($str -match "^win") {"`$str starts with 'win'"}
if ($str -match "win$") {"`$str ends with 'win'"} else {"`$str does not end with 'win'"}
if ($str -match "ows$") {"`$str ends with 'ows'"}
output:
$str contains 'win'
$str starts with 'win'
$str does not end with 'win'
$str ends with 'ows'

How to use FINDSTR in PowerShell to find lines where all words in the search string match in any order

The following findstr.exe command almost does what I want, but not quite:
findstr /s /i /c:"word1 word2 word3" *.abc
I have used:
/s for searching all subfolders.
/c:
Uses specified text as a literal search string
/i Specifies that the search is not to be case-sensitive.
*.abc Files of type abc.
The above looks for word1 word2 word3 as a literal, and therefore only finds the words in that exact order.
By contrast, I want all words to match individually, in any order (AND logic, conjunction).
If I remove /c: from the command above, then lines matching any of the words are returned (OR logic, disjunction), which is not what I want.
Can this be done in PowerShell?
You can use Select-String to do a regex based search through multiple files.
To match all of multiple search terms in a single string with regular expressions, you'll have to use a lookaround assertion:
Get-ChildItem -Filter *.abc -Recurse |Select-String -Pattern '^(?=.*\bword1\b)(?=.*\bword2\b)(?=.*\bword3\b).*$'
In the above example, this is what's happening with the first command:
Get-ChildItem -Filter *.abc -Recurse
Get-ChildItem searches for files in the current directory
-Filter *.abc shows us only files ending in *.abc
-Recurse searches all subfolders
We then pipe the resulting FileInfo objects to Select-String and use the following regex pattern:
^(?=.*\bword1\b)(?=.*\bword2\b)(?=.*\bword3\b).*$
^ # start of string
(?= # open positive lookahead assertion containing
.* # any number of any characters (like * in wildcard matching)
\b # word boundary
word1 # the literal string "word1"
\b # word boundary
) # close positive lookahead assertion
... # repeat for remaining words
.* # any number of any characters
$ # end of string
Since each lookahead group is just being asserted for correctness and the search position within the string never changes, the order doesn't matter.
If you want it to match strings that contain any of the words, you can use a simple non-capturing group:
Get-ChildItem -Filter *.abc -Recurse |Select-String -Pattern '\b(?:word1|word2|word3)\b'
\b(?:word1|word2|word3)\b
\b # start of string
(?: # open non-capturing group
word1 # the literal string "word1"
| # or
word2 # the literal string "word2"
| # or
word3 # the literal string "word3"
) # close positive lookahead assertion
\b # end of string
These can of course be abstracted away in a simple proxy function.
I generated the param block and most of the body of the Select-Match function definition below with:
$slsmeta = [System.Management.Automation.CommandMetadata]::new((Get-Command Select-String))
[System.Management.Automation.ProxyCommand]::Create($slsmeta)
Then removed unnecessary parameters (including -AllMatches and -Pattern), then added the pattern generator (see inline comments):
function Select-Match
{
[CmdletBinding(DefaultParameterSetName='Any', HelpUri='http://go.microsoft.com/fwlink/?LinkID=113388')]
param(
[Parameter(Mandatory=$true, Position=0)]
[string[]]
${Substring},
[Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
[Alias('PSPath')]
[string[]]
${LiteralPath},
[Parameter(ParameterSetName='Any')]
[switch]
${Any},
[Parameter(ParameterSetName='Any')]
[switch]
${All},
[switch]
${CaseSensitive},
[switch]
${NotMatch},
[ValidateNotNullOrEmpty()]
[ValidateSet('unicode','utf7','utf8','utf32','ascii','bigendianunicode','default','oem')]
[string]
${Encoding},
[ValidateNotNullOrEmpty()]
[ValidateCount(1, 2)]
[ValidateRange(0, 2147483647)]
[int[]]
${Context}
)
begin
{
try {
$outBuffer = $null
if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
{
$PSBoundParameters['OutBuffer'] = 1
}
# Escape literal input strings
$EscapedStrings = foreach($term in $PSBoundParameters['Substring']){
[regex]::Escape($term)
}
# Construct pattern based on whether -Any or -All was specified
if($PSCmdlet.ParameterSetName -eq 'Any'){
$Pattern = '\b(?:{0})\b' -f ($EscapedStrings -join '|')
} else {
$Clauses = foreach($EscapedString in $EscapedStrings){
'(?=.*\b{0}\b)' -f $_
}
$Pattern = '^{0}.*$' -f ($Clauses -join '')
}
# Remove the Substring parameter argument from PSBoundParameters
$PSBoundParameters.Remove('Substring') |Out-Null
# Add the Pattern parameter argument
$PSBoundParameters['Pattern'] = $Pattern
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Utility\Select-String', [System.Management.Automation.CommandTypes]::Cmdlet)
$scriptCmd = {& $wrappedCmd #PSBoundParameters }
$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
$steppablePipeline.Begin($PSCmdlet)
} catch {
throw
}
}
process
{
try {
$steppablePipeline.Process($_)
} catch {
throw
}
}
end
{
try {
$steppablePipeline.End()
} catch {
throw
}
}
<#
.ForwardHelpTargetName Microsoft.PowerShell.Utility\Select-String
.ForwardHelpCategory Cmdlet
#>
}
Now you can use it like this, and it'll behave almost like Select-String:
Get-ChildItem -Filter *.abc -Recurse |Select-Match word1,word2,word3 -All
Another (admittedly less sophisticated) approach would be to simply daisy-chain filters, since the order of the words doesn't matter. Filter your files for one word first, then filter the output for lines that also contain the second word, then filter that output for lines that also containt the third word.
findstr /s /i "word1" *.abc | findstr /i "word2" | findstr /i "word3"
Using PowerShell cmdlets the above would look like this:
Get-ChildItem -Filter '*.abc' -Recurse | Get-Content | Where-Object {
$_ -like '*word1*' -and
$_ -like '*word2*' -and
$_ -like '*word3*'
}
or (using aliases):
ls '*.abc' -r | cat | ? {
$_ -like '*word1*' -and
$_ -like '*word2*' -and
$_ -like '*word3*'
}
Note that aliases are just to save time typing on the commandline, so I do not recommend using them in scripts.
Note:
The first part of this answer does not solve the OP's problem - for solutions, see Mathias R. Jessen's helpful answer and Ansgar Wiecher's helpful answer; alternatively, see the bottom of this answer, which offers a generic solution adapted from Mathias' code.
(Due to an initial misreading of the question), this part of the answer uses disjunctive logic - matching lines that have at least one matching search term - which is the only logic that findstr.exe and PowerShell's Select-String (directly) support.
By contrast, the OP is asking for conjunctive logic, which requires additional work.
This part of the answer may still be of interest with respect to translating findstr.exe commands to PowerShell, using Select-String.
The PowerShell equivalent of the findstr command from the question, but without /c: -
FINDSTR /s /i "word1 word2 word3" *.abc
is:
(Get-ChildItem -File -Filter *.abc -Recurse |
Select-String -SimpleMatch -Pattern 'word1', 'word2', 'word3').Count
/s -> Get-ChildItem -File -Filter *.abc -Recurse outputs all files in the current directory subtree matching *.abc
Note that wile Select-String is capable of accepting a filename pattern (wildcard expression) such as *.abc, it doesn't support recursion, so the separate Get-ChildItem call is needed, whose output is piped to Select-String.
findstr -> Select-String, PowerShell's more flexible counterpart:
-SimpleMatch specifies that the -Pattern argument(s) be interpreted as literals rather than as regexes (regular expressions). Note how they defaults differ:
findstr expects literals by default (you can switch to regexes with /R).
Select-String expects regexes by default (you can switch to literal with -SimpleMatch).
-i -> (default behavior); like most of PowerShell, case-insensitivity is Select-String's default behavior - add -CaseSensitive to change that.
"word1 word2 word3" -> -Pattern 'word1', 'word2', 'word3'; specifying an array of patterns looks for a match for at least one of the patterns on each line (disjunctive logic).
That is, all of the following lines would match: ... word1 ..., ... word2 ..., ... word2 word1 ..., ... word3 word1 word2 ...
/c -> (...).Count: Select-String outputs a collection of objects representing the matching lines, which this expression simply counts.
The objects output are [Microsoft.PowerShell.Commands.MatchInfo] instances, which not only include the matching line, but metadata about the input and the specifics of what matched.
A solution, building on Mathias R. Jessen's elegant wrapper function:
Select-StringAll is a conjunctive-only wrapper function around the disjunctive-only Select-String cmdlet that uses the exact same syntax as the latter, with the exception of not supporting the -AllMatches switch.
That is, Select-StringAll requires that all patterns passed to it - whether they're regexes (by default) or literals (with -SimpleMatch) - match a line.
Applied to the OP's problem, we get:
(Get-ChildItem -File -Filter *.abc -Recurse |
Select-StringAll -SimpleMatch word1, word2, word3).Count
Note the variations compared to the command at the top:
The -Pattern parameter is implicitly bound, by argument position.
The patterns are specified as barewords (unquoted) for convenience, though it's generally safer to quote, because it's not easy to remember what needs quoting.
The following will work if you DO NOT HAVE ANY OF THE WORDS REPEATED IN THE SAME LINE as:
word1 hello word1 bye word1
findstr /i /r /c:"word[1-3].*word[1-3].*word[1-3]" *.abc
If repeated word1/word2/word3 is not there, or you do want those occurrences in your result, then can use it.

powershell multiple block expressions

I am replacing multiple strings in a file. The following works, but is it the best way to do it? I'm not sure if doing multiple block expressions is a good way.
(Get-Content $tmpFile1) |
ForEach-Object {$_ -replace 'replaceMe1.*', 'replacedString1'} |
% {$_ -replace 'replaceMe2.*', 'replacedString2'} |
% {$_ -replace 'replaceMe3.*', 'replacedString3'} |
Out-File $tmpFile2
You don't really need to foreach through each replace operations. Those operators can be chained in a single command:
#(Get-Content $tmpFile1) -replace 'replaceMe1.*', 'replacedString1' -replace 'replaceMe2.*', 'replacedString2' -replace 'replaceMe3.*', 'replacedString3' |
Out-File $tmpFile2
I'm going to assume that your patterns and replacements don't really just have a digit on the end that is different, so you might want to execute different code based on which regex actually matched.
If so you can consider using a single regular expression but using a function instead of a replacement string. The only catch is you have to use the regex Replace method instead of the operator.
PS C:\temp> set-content -value #"
replaceMe1 something
replaceMe2 something else
replaceMe3 and another
"# -path t.txt
PS C:\temp> Get-Content t.txt |
ForEach-Object { ([regex]'replaceMe([1-3])(.*)').Replace($_,
{ Param($m)
$head = switch($m.Groups[1]) { 1 {"First"}; 2 {"Second"}; 3 {"Third"} }
$tail = $m.Groups[2]
"Head: $head, Tail: $tail"
})}
Head: First, Tail: something
Head: Second, Tail: something else
Head: Third, Tail: and another
This may be overly complex for what you need today, but it is worth remembering you have the option to use a function.
The -replace operator uses regular expressions, so you can merge your three script blocks into one like this:
Get-Content $tmpFile1 `
| ForEach-Object { $_ -replace 'replaceMe([1-3]).*', 'replacedString$1' } `
| Out-File $tmpFile2
That will search for the literal text 'replaceMe' followed by a '1', '2', or '3' and replace it with 'replacedString' followed by whichever digit was found (the '$1').
Also, note that -replace works like -match, not -like; that is, it works with regular expressions, not wildcards. When you use 'replaceMe1.*' it doesn't mean "the text 'replaceMe1.' followed by zero or more characters" but rather "the text 'replaceMe1' followed by zero or more occurrences ('*') of any character ('.')". The following demonstrates text that will be replaced even though it wouldn't match with wildcards:
PS> 'replaceMe1_some_extra_text_with_no_period' -replace 'replaceMe1.*', 'replacedString1'
replacedString1
The wildcard pattern 'replaceMe1.*' would be written in regular expressions as 'replaceMe1\..*', which you'll see produces the expected result (no replacement performed):
PS> 'replaceMe1_some_extra_text_with_no_period' -replace 'replaceMe1\..*', 'replacedString1'
replaceMe1_some_extra_text_with_no_period
You can read more about regular expressions in the .NET Framework here.