It seems redundant to provide -match and -imatch if -match is already case-insensitive. Is there any difference between them?
To elaborate on Doug Maurer's comment:
The i-prefixed variants of PowerShell operators that (also) operate on strings are never necessary. In fact, they are simply aliases of their non-prefixed forms, so that -imatch is the same as -match, for instance, and - with string input - always acts case-insensitively, as PowerShell generally does.
These variants exist for symmetry with the c-prefixed operator variants, which explicitly request case-sensitive operation (with string input).
In other words: you can use the i-prefixed variants to make it explicit that a given operation is case-insensitive.
However, to someone familiar with PowerShell's fundamentally case-insensitive nature, that isn't necessary - and that's probably why you rarely see the i-prefixed variants in practice.
Related
I'm trying to work out if a string exists in an array, even if it's a substring of a value in the array.
I've tried a few methods and just can't get it to work, not sure where I'm going wrong.
I have the below code, you can see that $val2 exists within $val1, but I always get a FALSE when I run it.
$val1 = "folder1\folder2\folder3"
$val2 = "folder1\folder2"
$val3 = "folder9"
$val_array = #()
$val_array += $val1
$val_array += $val3
$null -ne ($val_array | ? { $val2 -match $_ }) # Returns $true
I also tried:
foreach ($item in $val_array) {
if ($item -match $val2) {
Write-Host "yes"
}
}
The -Match operator does a regular expression comparison. Where the backslash character (\) has a special meaning (it escapes the following character).
Instead you might use the -Like operator:
$val_array -Like "*$val2*"
Yields:
folder1\folder2\folder3
iRon's helpful answer offers the best solution to your problem, using wildcard matching via the -like operator.
Note:
The need to escape select characters in a search pattern in order for the pattern to be taken verbatim in principle also applies to the wildcard-based -like operator, not just to the regex-based -match operator, but since wildcard expressions have far fewer metacharacters than regexes - namely just *, ?, and [ - the need for such escaping doesn't often arise in practice; whereas regexes require \ as the escape characters, wildcards use `, and programmatic escaping can be achieved with [WildcardPattern]::Escape()
Unfortunately, as of PowerShell 7.2, there is no dedicated operator for verbatim substring matching:
A workaround for this limitation is to call the [string] .NET type's .Contains() method (on a single input string only), however, this performs case-sensitive matching, whereas PowerShell operators are case-insensitive by default, but offer case-sensitive variants simply by prefixing the operator name with c (e.g., -clike, -cmatch).
In Windows PowerShell, .Contains() is invariably case-sensitive, but in PowerShell (Core) 7+ an additional overload is available that offers case-insensitive matching:
'Foo'.Contains('fo') # -> $false, due to case difference
# PowerShell (Core) 7+ *only*:
'Foo'.Contains('fo', 'InvariantCultureIgnoreCase') # -> $true
Caveat: Despite the name similarity, PowerShell's -contains operator does not perform substring matching; instead, it tests whether a collection contains a given element (in full).
As for what you tried:
Your primary problem is that you've accidentally swapped the -match operator's operands: the search pattern - which is invariably interpreted as a regex (regular expression) - must be on the RHS.
As iRon points out, in order for your search pattern to be taken verbatim (literally), you need to escape regex metacharacters with \, and the robust, programmatic way to do this is with [regex]::Escape().
Therefore, the immediate fix would have been (? is a built-in alias of the Where-Object cmdlet):
# OK, but SLOW.
$val_array | ? { $_ -match [regex]::Escape($val2) }
However, this solution is inefficient (it involves the pipeline and a cmdlet).
Fortunately, PowerShell's comparison operators can be applied to arrays (collections) directly, in which case they act as filters, i.e. they return the sub-array of matching elements - see the docs.
iRon's answer uses this technique with -like, but it equally works with -match, so that your expression can be simplified to the following, much more efficient form:
# MUCH FASTER.
$val_array -match [regex]::Escape($val2)
Try the string method Contains:
$null -ne ($val_array | ? { $_.Contains($val2) })
I have the following Regular Expression: ([a-z])([A-Z])
When I plug it into RegEx 101 it seems to work perfectly: https://regex101.com/r/vhifNL/1
But when I plug it into Powershell to have the matches replaced with dashes, it goes crazy:
"JavaScript" -replace '([a-z])([A-Z])', '$1-$2'
I expect to get Java-Script. But instead I get:
J-av-aS-cr-ip-t
Why is it not matching the same way that RegEx101 has it match?
NOTE: This question is not tagged with RegEx on purpose. I would take it as a kindness if no-one added it. The RegEx folks have a different set of rules they run by for questions and will likely close my question.
PowerShell's -replace operator, like all PowerShell operators that can operate on strings (notably -match, -eq, -like, -contains and their negated counterparts), and like PowerShell in general, is case-insensitive by default.
However, all such operators have case-sensitive variants, selected by simply prepending c to the operator name, namely -creplace in the case at hand:
PS> "JavaScript" -creplace '([a-z])([A-Z])', '$1-$2'
Java-Script
As for what you tried:
Due to -replace being case-insensitive (which you can optionally signal explicitly with the
-ireplace alias), your regex was essentially equivalent to:
([a-zA-Z][a-zA-Z])
and therefore matched any two consecutive (ASCII-range) letters, and not the desired transition from a lowercase to an uppercase letter.
This would likely be a non-issue with expert regex comprehension. And only matters because I am running multiple chained replace commands that affect some of the same text in a text file. I also imagine partitioning the txt files based on how delimiter words --that are requiring multiple replaces-- are used, before replace, would help. With that said basic structural knowledge of powershell is useful and I have not found many great resources (open to suggestions!).
The question: Do chained powershell replace commands execute one after the other?
-replace "hello:","hello " `
-replace "hello ","hello:"
} | out-file ...
Would this silly example above yield hello:'s where there were initially hello:'s?
From working through some projects I gather that the above works most of the time. Yet there always seem to be some edge cases. Is this another aspect of the script or is the order that chained commands (decent number of them) execute in never variable?
What you have there are operators, not commands.
I say that not to be pedantic, but because "command" has a specific meaning in PowerShell (it is a general name encompassing functions, cmdlets, aliases, applications, filters, configurations (this is a DSC construct), workflows, and scripts), and because the way they can be used together is different.
Most operators are reserved words that begin with - (but other things count as operators, like casting), and you can indeed use them chained together. They also execute in order.
I need to clarify; they don't necessarily execute in the order given when you mix operators. Multiple of the same operator will because they all have the same precedence, but you should check about_Operator_Precedence to see the order that will be used when you combine them.
Note that some operators can "short-circuit" (which may sound like a malfunction, but it isn't), that is the result of certain boolean operators will not evaluate later operations if the boolean result can not change.
For example:
$true -or $false
In this example, the $false part of the expression will never actually be evaluated. This is important if the next part of the expression is complex or even invalid. Consider these:
$true -or $(throw)
$false -or $(throw)
The first will return $true because (presumably) nothing in the coming expression could make it $false.
The second line must evaluate the second expression, and in doing so it throws an exception, halting the program.
So, aside from that aside, yes, you can continue to chain your operators. You also don't need a line continuation character (backtick `) at the end of the line if the operator itself is at the end. More useful with boolean operators:
$a -and
$b -or
$c -xor
$false
A little awkward with something like replace:
'apple' -replace
'p',
'z'
Regarding this:
And only matters because I am running multiple chained replace
commands that affect some of the same text in a text file.
These operators aren't touching anything in a file, they are working with data in memory, as literals or variables in your script (what you do with it then, like writing to a file is your business).
Further, even then it doesn't change any values already in variables, it returns new ones, which you may assign to a variable or use in any other way.
$var = 'apple'
$var -replace 'p','Z'
$var
The value of the replacement will be returned, but nothing was done with it so it went out to the console. Then you can see that $var was not modified at all, as opposed to:
$var = 'apple'
$var = $var -replace 'p','Z'
$var
Where the value of $var was overwritten.
If there are edge cases, it's likely to be a misunderstanding of something in the sequence of events (an incorrect regular expression, not assigning or using a value, incorrect logic, etc.), as the order of operations will be consistent. If you have any such edge cases, please post them!
Get-Help Get-ChildItem displays -Filter parameter, with displayed wording "Specifies a filter in the provider's format or language". That language differs between what Powershell calls "providers", and file system is declared as one of them. But I have not found any syntax descriptions on file system provider's filter syntax. Any help?
The filter syntax supported by the FileSystem provider is sparsely (if it all) documented, probably because there's nothing much to say.
In short, it only supports simple wildcard matching as you know it from Windows XP-era Search:
Any file with an extension:
*.*
Any file with the .txt extension:
*.txt
Partial wildcard matching:
*something*.txt
Single-character matching (matches myfile1.jpg but not myfile01.jpg):
myfile?.*
Simple character sets (this matches bear and beer):
be[ae]r
Simple character ranges (this matches filea.txt, fileb.txt and filec.txt):
file[a-c].txt
Note: It only supports a single expression per filter, so this is illegal:
*.jpg|*.txt
I have created a Powershell routine for setting mp3 tags on songs, where I'd like some of my parameters to act as either a regular expession or a "simple" string. To be specific, if the parameter can be said to work as a regular expression, the function should try to use this for retrieving its value; if it can't, it should simply use that value.
I've just browsed Parameter sets, and don't think this would suit me since I want to be flexible with the parameter handling; i.e. I'd like several parameters to act this way independently. But maybe I'm wrong in this? Anyway, help would be appreciated.
You don't really need the try/catch if you use:
IF ($string -as [regex])
If the cast is successful it will return the regex, if not it will return $null, so used as a boolean test in the IF, it will be $true if it is a valid regex, and $false if it is not.
That being said, the I'd agree with Joey that you should settle on a single match type (either wildcard or regex) and stick with that. There's too much potential for unintended consequences in trying to determine if a regex metacharacter was intended to be match literally or not.
You can try converting the string to a regular expression and look for failures. If there is an exception, just use it as string:
$isParamRegex = $(try { $null = [regex]$Param; $true } catch { $false })
As for the parameter type, just make it a string and document it appropriately.
However, I'd say you might want to go a different route there:
Either make the argument always a regex, to avoid surprises with metacharacters.
Or make it a pattern for -like instead of -match which is a bit more predictable for users (imho).
In both cases provide a LiteralParam argument, akin to LiteralPath to just pass things as plain strings which are handled as such.