Imagine having a list of computer names from AD, like ABC0001, ABC0002, DEF0001, XYZ0001 and so on, and you only want to keep ABC* and XYZ*.
We want to filter so we'll use -Like, and try to use an array of comparison values.
Get-ADComputer | Where-Object ComputerName -like ABC*, XYZ*
ABC0001
ABC0002
XYZ0001
...
Single comparisons work
Get-ADComputer | Where-Object ComputerName -like ABC*
ComputerName
------------
ABC0001
ABC0002
ABC0003
ABC0004
ABC0005
Get-ADComputer | Where-Object ComputerName -like XYZ*
ComputerName
------------
XYZ0001
XYZ0002
XYZ0003
XYZ0004
XYZ0005
But combining them does not work
Where-Object ComputerName -like ABC*, XYZ*
PS>
With PowerShell being object based and having such a uniform syntax, it _feels_ like this should work, so people tend to be confused (like me) and when doing really complex filters, write garbage like this:
Or read from an arcane tome of RegEx Spells and come up with devilry like this example, from the wizard Dave Wyatt.
? { $_.e -match '^(atl|na|mdm|aus|okc|ssc|ga1|ok1|fns|acs)' }
> $PSVersionTable
Name Value
---- -----
PSVersion 5.1.14393.103
PSEdition Desktop
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
BuildVersion 10.0.14393.103
CLRVersion 4.0.30319.42000
WSManStackVersion 3.0
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
I usually write it in this form to avoid repeating -and
and -notlike
$patterns = @(
'atl*',
'na*',
'mdm*'
# etc
)
$w | ? {
$e = $_.e
-not ($patterns | ? ($e -like $_))
}
Changing the meaning of arrays could be a (probably rare but obscure) breaking change.
PS> "a b" -like ('a','b')
True
A new operator, e.g. -likeany
might be more readable. Note that these examples are similar to part of the generalized splatting rfc, which actually suggest another syntax that wouldn't be breaking: $a -like @b
.
For now, you can workaround this with -NotMatch
and -Match
. Instead of using
? { $_.e -match '^(atl|na|mdm|aus|okc|ssc|ga1|ok1|fns|acs)' }
You can write
? e -Match '^(atl|na|mdm|aus|okc|ssc|ga1|ok1|fns|acs)'
? e -Match "^($(('atl', 'na', 'mdm', 'aus', 'okc', 'ssc', 'ga1', 'ok1', 'fns', 'acs') -join '|'))"
# But it seems that you want -NotMatch
# as in your long "if"s?
This should be submitted as an RFC
But combining them does not work
Where-Object ComputerName -like ABC*, XYZ*
PS>With PowerShell being object based and having such a uniform syntax, it feels like this should work
It does work, it's telling you that you don't have a computer called "abc1, xyz2"
or similar.
In this example -like
is not an operator, it's a parameter of Where-Object
so the pattern is parsed in parameter parsing mode. It becomes a single string pattern containing a comma and a space. It matches text "abc anything comma space xyz anything":
@{test='abc1, xyz2'} |Where-Object test -like abc*, xyz*
That is, it does work in line with the uniform syntax, it's just misleading you about which syntax applies.
This is different from the operator used with quotes -like "abc*", "xyz*"
which is parsed as lzybkr describes - an array joined together using the value of $OFS
as the separator (space by default), so the comma doesn't become a part of the pattern but a space does.
And with the operator, trying to use the example with no quotes @('zzz', 'abc1', 'xyz2') -like abc*, xyz*
is a parse error.
If the suggested -likeany
became a parameter on where-object
then this slightly misleading pattern would apply to it in the same way:
|where test -likeany abc* # abc* wildcard
|where test -likeany abc*, def* # "abc*, def*" wildcard surprise
|where test -likeany "abc*", "def*" # multiple wildcards "abc*" and "def*"
And the original issue would still be here: Where-Object test -likeany abc*, def*
doesn't work as expected, because the expectation was not thinking about parameter parsing mode.
Presumably with a new operator there would be variants -clikeany
, -ilikeany
?
What about -notlikeany
, -cnotlikeany
, -inotlikeany
?
Should that pattern extend to -matchany
, -notmatchany
, -containsany
, -notcontainsany
, -anyin
, -notanyin
) and their case variants? -likeall
?
If not, then you get the weird situation where -likeany
becomes the de-facto -containsany
:
$collection -contains 'a'
#How do I test if it contains an 'a' or a 'b'?
#you can use:
$collection -likeany 'a', 'b'
# > why are they named differently?
# contains is an object in a collection, likeany is a string match but it works for a collection of strings
# that will bite you one day.
# oh and it acts as a filter instead of returning a [bool]..
I would vote for a breaking change to make it work with -like
, personally. Most, if not all, the existing operators work with an array on one side or the other, without needing a variant on the name specifically for the case of working with an array. And possibly adjust -contains @('a', 'b')
similarly, so it doesn't join the array.
I see the original issue - matching multiple patterns - appears over and over on StackOverflow as a thing people trip over, expecting there to be a clean way and there isn't. This seems like a helpful fix for that. But..
an arcane tome of RegEx Spells and come up with devilry like this example
In defence of regex, it is a language specifically for describing patterns in text, and the use of ^
, ()
and |
is not as arcane and devilish as claimed; regex is a first class citizen in PowerShell already - in common operators like -split
and -replace
, and it's available in every popular language, many popular text editors, and many other data processing tools.
At what point is it reasonable to say "honestly, your next move should be: spend an hour with a regex tutorial, it's really not that bad"? Is there anything that can be done to make it less intimidating?
I would vote for a breaking change to make it work with -like, personally.
I agree, given that:
this strikes me as a clear-cut Bucket 3: Unlikely Grey Area change
it avoids introducing yet another operator
extending the RHS to support arrays seems like a natural extension of the existing functionality.
This seems like a helpful fix for that.
But...
Depending on your requirements, sometimes regexes are your only choice.
But even if you have a decent grasp of regexes, it is convenient to use the less noisy and conceptually simpler wildcard patterns in cases where that is enough - which is often.
Wildcards are much more pervasive in PowerShell (think parameters that support them; e.g. Get-Process ba*
) and therefore a familiar feature.
That said, we could similarly introduce array RHS support for the -match
operator as well:
To me, there's something cleaner about writing
'foo vs. bar' -match 'foo', 'bar'
than (what I propose the above would be the equivalent of):
'foo vs. bar' -match '(?:foo|bar)'
For symmetry we'd have to add this to the potential future -matchall
operator as well - see #7867; though perhaps the naming of that new operator should be revisited in this context.
On a side note, @HumanEquivalentUnit:
I get the difference in parsing modes (of argument-mode ABC*, XYZ*
vs. expression-mode 'ABC*', 'XYZ*'
), but, quoting aside, _both_ are parsed as _arrays_ that are converted to a single string in which the elements are concatenated with $OFS
, which yields string 'ABC* XYZ*'
by default.
# OK: Quoted argument, literal match
PS> @{test='abc1, xyz2'} |Where-Object test -like 'abc1, xyz2'
Name Value
---- -----
test abc1, xyz2
# NO match with unquoted argument, because array abc1, xyz2 is converted
# to string 'abc1 xyzz2'
PS> @{test='abc1, xyz2'} |Where-Object test -like abc1, xyz2
# !! no match
# Therefore, a comma-less LHS value does match:
PS> @{test='abc1 xyz2'} |Where-Object test -like abc1, xyz2
Name Value
---- -----
test abc1 xyz2
# Example with $OFS
PS> & { $OFS='#'; @{test='abc1#xyz2'} |Where-Object test -like abc1, xyz2 }
Name Value
---- -----
test abc1#xyz2
Most helpful comment
It does work, it's telling you that you don't have a computer called
"abc1, xyz2"
or similar.In this example
-like
is not an operator, it's a parameter ofWhere-Object
so the pattern is parsed in parameter parsing mode. It becomes a single string pattern containing a comma and a space. It matches text "abc anything comma space xyz anything":That is, it does work in line with the uniform syntax, it's just misleading you about which syntax applies.
This is different from the operator used with quotes
-like "abc*", "xyz*"
which is parsed as lzybkr describes - an array joined together using the value of$OFS
as the separator (space by default), so the comma doesn't become a part of the pattern but a space does.And with the operator, trying to use the example with no quotes
@('zzz', 'abc1', 'xyz2') -like abc*, xyz*
is a parse error.If the suggested
-likeany
became a parameter onwhere-object
then this slightly misleading pattern would apply to it in the same way:And the original issue would still be here:
Where-Object test -likeany abc*, def*
doesn't work as expected, because the expectation was not thinking about parameter parsing mode.Presumably with a new operator there would be variants
-clikeany
,-ilikeany
?What about
-notlikeany
,-cnotlikeany
,-inotlikeany
?Should that pattern extend to
-matchany
,-notmatchany
,-containsany
,-notcontainsany
,-anyin
,-notanyin
) and their case variants?-likeall
?If not, then you get the weird situation where
-likeany
becomes the de-facto-containsany
:I would vote for a breaking change to make it work with
-like
, personally. Most, if not all, the existing operators work with an array on one side or the other, without needing a variant on the name specifically for the case of working with an array. And possibly adjust-contains @('a', 'b')
similarly, so it doesn't join the array.I see the original issue - matching multiple patterns - appears over and over on StackOverflow as a thing people trip over, expecting there to be a clean way and there isn't. This seems like a helpful fix for that. But..
In defence of regex, it is a language specifically for describing patterns in text, and the use of
^
,()
and|
is not as arcane and devilish as claimed; regex is a first class citizen in PowerShell already - in common operators like-split
and-replace
, and it's available in every popular language, many popular text editors, and many other data processing tools.At what point is it reasonable to say "honestly, your next move should be: spend an hour with a regex tutorial, it's really not that bad"? Is there anything that can be done to make it less intimidating?