11

For every file being processed, its name is being checked to satisfy the condition. For example, given the following list of filters:

$excludeFiles = @"
aaa.bbb
ccccc.*
ddddd???.exe
"@ | SplitAndTrim;

It should exclude a file from processing if it matches any of the lines. Trying to avoid match/regex, because this script needs to be modifiable by someone who does not know it, and there are several places where it needs to implemented.

$excludedFiles and similar are defined as a here-string on purpose. It allows the end user/operator to paste a bunch of file names right from the CMD/Powershell window.

It appears that Powershell does not accept -like against an array, so I cannot write like this:

"ddddd000.exe" -like @("aaa.bbb", "ccccc.*", "ddddd???.exe")

Did I miss an option? If it's not natively supported by Powershell, what's the easiest way to implement it?

4
  • 1
    See answer from Josh Einstein: stackoverflow.com/questions/848859/… Commented Oct 22, 2012 at 22:35
  • 1
    Thanks, @dugas, but I don't see why those complications - compare to the two lines below - see my accepted answer. Commented Oct 23, 2012 at 1:18
  • It converts the right side to a string, "aaa.bbb ccccc.* ddddd???.exe". Btw you don't need @( ) Commented Nov 17 at 13:26
  • Regex is the obvious answer -match "aaa.bbb|ccccc.|ddddd....exe" Commented Nov 18 at 14:51

5 Answers 5

16

Here is a short version of the pattern in the accepted answer:

($your_array | %{"your_string" -like $_}) -contains $true

Applied to the case in the OP one obtains

PS C:\> ("aaa.bbb", "ccccc.*", "ddddd???.exe" | %{"ddddd000.exe" -like $_}) -contains $true
True
Sign up to request clarification or add additional context in comments.

6 Comments

This is worse than the accepted answer in performance; the accept answer make use of break to skip evaluation of items after finding a match, while this answer maps every single string to a boolean.
Performance often doesn't matter for this kind of tasks, and if it does, I would go with neither this or the accepted answer. Conciseness, on the other hand, is usually a good thing to have. So, as always, feel free to use what you prefer.
I'm made the comment because in your answer you said "Here is a short version of the pattern in the accepted answer", which isn't technically true. But yeah I see your point
Ok, I see your's as well, not true indeed :-)
I'm curious as to what solution you would use for performance as well? I thought powershell's foreach function in the accepted answer is pretty good already 🤔
If you have time to spare could you please look at my answer as well
11

You can perform a pattern match against a collection of names, but not against a list of patterns. Try this:

foreach($pattern in $excludeFiles)
{
    if($fileName -like $pattern) {break}
}

Here is how it can be wrapped into a function:

function like($str,$patterns){
    foreach($pattern in $patterns) { if($str -like $pattern) { return $true; } }
    return $false;
}

3 Comments

That's what I thought at first... just don't like the idea of having to implement this loop every time I need this functionality. I will accept this answer, if nobody else provides an alternative.
Wrap it in a function and reuse it :)
That's exactly what I did - I also updated your answer to include my solution.
4

I suppose you could use the Get-ChildItem -Exclude parameter:

Get-ChildItem $theFileToCheck -exclude $excludeFiles

If you have an array of files to check, Get-ChildItem accepts an array of paths:

Get-ChildItem $filesToCheck -exclude $excludeFiles

2 Comments

Thanks - I can confirm that Get-ChildItem or simply dir (I am more used to Windows terminology) correctly excludes by the same pattern as LIKE, and it works for an array of strings, which can be very convenient for many uses. Unfortunately, it does not work for my needs, because in some cases I need to work with strings directly, not file objects. Still, your answer deserves a +1.
It doesn't work is the wildcard in the pattern is not at the beginning
2

$excludeFiles.Where{$stringToTest -like $_}

Powershell array has a Where method that can take an expression input, hence the {} instead of (). Feed in a string to test and it will iterate over the array using the standard pipe so $_ represents the element of the array.

Outputs a list of matching elements.

PS H:\> $excludeFiles = @("aaa.bbb", "ccccc.*", "ddddd???.exe")
PS H:\> $stringToTest = "ddddd000.exe"
PS H:\> $excludeFiles.Where{$stringToTest -like $_}
ddddd???.exe

Comments

0

To deal with your problem and all similar existential quantification related problems, we might want a generic solution that allows us to do something like:

PS> "aaa.bbb", "ccccc.*", "ddddd???.exe"| any { "ddddd000.exe" -like $_ }
True


PS> "aaa.bbb", "ccccc.*", "ddddd???.exe"| any { "ddd000.exe" -like $_ }
False

Solution: Utility function Test-Any

Simply put this function in your (profile) scripts:

function Test-Any {
    <#
        .SYNOPSIS
            Check if any element satisfies a predicate, returns a boolean.
        .PARAMETER InputObject
            Prefer using pipeline over parameter.
            Scalar argument provided through parameter will be wrapped into an array.
        .PARAMETER Predicate
            ScriptBlock function: ([Object]$Element) -> [Boolean]$Result
        .EXAMPLE
            Test-Any -InputObject 1,2,3,4 {$_ % 2}
            True
        .EXAMPLE
            1,(2,3),4 | Test-Any {$_.Count -eq 3}
            False
    #>
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline)][AllowNull()]
        [Object]$InputObject,
        [Parameter(Mandatory, Position = 0)][ArgumentCompletions('{}')]
        [ScriptBlock]$Predicate
    )
    # Replace `$InputObject` with pipeline value, if value is provided from pipeline.
    if ($MyInvocation.ExpectingInput) { $InputObject = $input }
    else { $InputObject = @($InputObject) }

    foreach ($o in $InputObject) {
        if ($Predicate.InvokeWithContext($null, [PSVariable]::new('_', $o))) {
            return $true
        }
    }
    return $false
}

I would also recommend adding the alias any for ease of use:

New-Alias -Name any -Value Test-Any

Bonus: Utility function Test-All

Might as well have a function for universal quantification too!

function Test-All {
    <#
        .SYNOPSIS
            Check if all elements satisfy a predicate, returns a boolean.
        .PARAMETER InputObject
            Prefer using pipeline over parameter.
            Scalar argument provided through parameter will be wrapped into an array.
        .PARAMETER Predicate
            ScriptBlock function: ([Object]$Element) -> [Boolean]$Result
        .EXAMPLE
            1,2,3,4 | Test-All {$_ % 2}
            False
        .EXAMPLE
            1,3,5 | Test-All {$_ % 2}
            True
    #>
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline)][AllowNull()]
        [Object]$InputObject,
        [Parameter(Mandatory, Position = 0)][ArgumentCompletions('{}')]
        [ScriptBlock]$Predicate
    )

    # Replace `$InputObject` with pipeline value, if value is provided from pipeline.
    if ($MyInvocation.ExpectingInput) { $InputObject = $input }
    else { $InputObject = @($InputObject) }

    foreach ($o in $InputObject) {
        if (!$Predicate.InvokeWithContext($null, [PSVariable]::new('_', $o))) {
            return $false
        }
    }
    return $true
}

(These functions and their naming are inspired by the ones in Kotlin standard library, any and all).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.