簡體   English   中英

PowerShell - 使用帶有字符串數組的管道 where-object 過濾文件名

[英]PowerShell - Filter filenames using pipeline where-object with array of strings

Windows 10 PS 5.1

我正在嘗試確認文件名是否包含一個或多個字符串。 它們不是完全匹配,因此應該能夠處理通配符,但是,如果比較運算符不接受通配符,則不需要使用通配符。 在我的場景中,我正在遍歷管道中的許多文件名並使用 where-object 過濾文件名。 我只能使用單個字符串進行過濾,而無法使用字符串數組進行過濾。 我已將代碼上傳到 github。

https://github.com/ChrisK847/WordSearcher

這是代碼的關鍵行

#Line 29
$FileNameLike = "2016-09", "2016-10" #, "2016-08", "2016-10"

#Line 70
if($FileNameLike -eq ""){$FileNameLike = "*"}elseif($FileNameLike -ne ""){$FileNameLike = $FileNameLike | %{ $_ -replace $_,"*$_*"}} #{$FileNameLike = "*$FileNameLike*"}

#Line 103
Where-Object FullName -like $FileNameLike |

我嘗試進行更改的最重要的行是第 103 行。

我嘗試過使用 -in、-match(不使用 *)、-contains 和 -like。 他們都沒有在 arrays 上工作。 Microsoft 文檔“關於比較運算符”不包含單詞數組。 我嘗試使用每個比較運算符翻轉對象,例如“Where-object {$FileNameLike -like $_.FullName},但這不起作用。Microsoft 文檔“About Where-Object”包含一個示例,但它在我的情況下不起作用。

#From About Where-Object
Get-Process | where -Property ProcessName -in -Value "Svchost", "TaskHost", "WsmProvHost"

在 About Where-Object 示例中,它們沒有提供帶有通配符的示例。 我事先不知道文件的全名,因此需要通配符或執行類似於帶有通配符的運算符的運算符。 我只想在第 29 行列出我的關鍵字。

我正在搜索的目錄有 56,000 個文件,所以我需要使用管道,否則最終會耗盡我的 PC 的 memory。

因此,您不必創建一個包含 56,000 個具有不同名稱的文件的目錄並創建新目錄,從而使測試更快,我一直在使用示例代碼進行測試,該示例代碼應該可以復制我正在嘗試做的事情。 最簡單的例子我仍然沒有運氣

cls
$Matches = $null
$keyWords = "3", "r"
$fileNames = "file1","file2","file3"

#$null -ne ($keyWords | ? { $fileNames -match $_ }) #THIS WORKS, BUT DOES NOT CONFORM WITH THE ORDER IN MY SCRIPT.
ForEach($fileName in $fileNames){
    $fileName
    $fileName | ?{$keyWords -contains $_}
    $fileName | ?{$keyWords -like $_}
    $fileName | ?{$keyWords -in $_}
    $fileName | ?{$keyWords -match $_}
    $fileName | ?{$_ -contains $keyWords}
    $fileName | ?{$_ -like $keyWords}
    $fileName | ?{$_ -in $keyWords}
    $fileName | ?{$_ -match $keyWords}
}

在示例代碼中,請注意這一行 #$null -ne ($keyWords |? { $fileNames -match $_ })

這在我的管道 where-object Fullname -like $FileNameLike 之外有效,並且是唯一有效的示例,但是,我如何將它放在我的 where-object 第 103 行中?

您不能使用 -like 運算符指定多個模式。 要使用 like 指定多個模式,您必須指定由 -and 或 -or 分隔的多個表達式

Where-Object {$_.Name -like "*2016-09*" -or $_.Name -like "*2016-10*"}

您可以構建表達式,然后將其用作您的 Where-Object 過濾器。

(更新為使用腳本塊而不是根據評論使用調用表達式 - 謝謝 zelt42)

$FileNameLike = "2016-09", "2016-10" #, "2016-08", "2016-10"

$filter = if (!$FileNameLike) {
    [scriptblock]::Create('$_.Name -like "*"')
}
else {
    $output = foreach ($filter in $FileNameLike) {
        "`$_.Name -like '*$filter*'"
    }
    [scriptblock]::Create($output -join ' -or ')
}

#Line 103
Where-Object -FilterScript $filter |

或者, -match 運算符使用正則表達式,因此您可以執行類似的操作

Where-Object {$_.Name -match "2016-09|2016-10"} 

構建您的正則表達式模式並在您的 Where-Object 過濾器塊中使用它

$FileNameLike = "2016-09", "2016-10" #, "2016-08", "2016-10"

$regexPattern = if (!$FileNameLike) {
    ".*"
} else {
    ($FileNameLike | % {[regex]::Escape($_)}) -join '|'
}

Where-Object {$_.name -match $regexPattern }

我正在嘗試確認文件名是否包含一個或多個字符串。

多個搜索字符串代表多個查詢。 我建議在測試用例中編寫它們。

$location = "C:\lab"

Get-ChildItem -Path $location -Recurse -File | 
  Where-Object {
    switch -Wildcard ($_.Name)
    {
      '*2016-08*' {$true; Break}
      '*2016-09*' {$true; Break}
     #'*2016-10*' {$true; Break}
     #'*2016-11*' {$true; Break}
      default     {$false}
    }
  } | 
  Select-Object -Property Name 


PS > .\Stack Overflow Demo.ps1

Name
----
report 2016-09-01.txt


我只想在第 29 行列出我的關鍵字。

可以從變量生成代碼,但這會掩蓋您的意圖並且是不標准的。

如果您接受用戶輸入,這會使您的腳本容易受到代碼注入的影響

$location = "C:\lab"

$FileNameLike = "*2016-08*", "*2016-09*" , "*2016-10*", "*2016-11*"

$beginning = @"
Get-ChildItem -Path `$location -Recurse -File | 
  Where-Object {
    switch -Wildcard (`$_.Name)
    {

"@

$middle = [System.Text.StringBuilder]::new()

foreach ($myString in $FileNameLike)
{  [void]$middle.AppendLine( "      '$myString' {`$true; Break}" )  }

$end = @"
      default     {`$false}
    }
  } | 
  Select-Object -Property Name 
"@

$myScript = $beginning + $middle.ToString() + $end 

Invoke-Expression -Command $myScript




PS > .\dynamic code generation 02.ps1

Name
----
report 2016-09-01.txt
report 2016-10-01.txt

解釋

$location = "C:\lab"

保存正在掃描的文件夾的路徑。


Get-ChildItem -Path $location -Recurse -File | 

列出$location的內容。 使用-Recurse掃描子文件夾。 -File限制為 -File s。 開始管道| .


  Where-Object {
  } | 

Where-Object返回腳本塊語句為真的所有對象。

繼續管道|


    switch -Wildcard ($_.Name)
    {
      '*2016-08*' {$true; Break}
      '*2016-09*' {$true; Break}
     #'*2016-10*' {$true; Break}
     #'*2016-11*' {$true; Break}
      default     {$false}
    }

使用一個開關來處理多個 If 語句

-Wildcard表示條件是通配符字符串。 比較不區分大小寫。

或者,您可以使用-CaseSensitive開關。

$_包含管道中的當前 object。 您可以在對每個 object 或管道中的選定對象執行操作的命令中使用此變量。

$_.Name是當前正在處理的文件的文件名。

'*2016-08*' {$true; Break}

每個 case 都包含一個通配符字符串和一個action 定義包含通配符的字符串。

請記住, Where-Object返回腳本塊語句為真的所有對象。 因此,當我們的字符串之一與文件名匹配時,使用該操作返回$true然后Break Break關鍵字停止處理並退出 Switch 語句。

如果沒有任何字符串匹配,則 output $false


Select-Object -Property Name 

Output 匹配的文件的Name


PS > .\Stack Overflow Demo.ps1

Name
----
report 2016-09-01.txt

運行腳本並顯示結果。

動態腳本說明

$FileNameLike = "*2016-08*", "*2016-09*" , "*2016-10*", "*2016-11*"

將搜索字符串存儲在一個數組中,包括它們的通配符。


$beginning = @"
"@

$middle = [System.Text.StringBuilder]::new()

$end = @"
"@

$myScript = $beginning + $middle.ToString() + $end 

Invoke-Expression -Command $myScript

如果我們要生成動態代碼,我們將需要一個字符串和Invoke-Expression

我們可以根據$FileNameLike的值動態生成測試用例,並將結果存儲在$middle

$beginning$end是 static 字符串。


$beginning = @"
Get-ChildItem -Path `$location -Recurse -File | 
  Where-Object {
    switch -Wildcard (`$_.Name)
    {

"@

使用雙引號here-string來定義$beginning

因為我們使用雙引號,所以使用 ` 轉義任何美元符號

請注意,像$location這樣的嵌入式變量將按預期工作,因為表達式是在當前 scope中評估和運行的。

留下一個尾隨換行符以准備$middle


$middle = [System.Text.StringBuilder]::new()

foreach ($myString in $FileNameLike)
{  [void]$middle.AppendLine( "      '$myString' {`$true; Break}" )  }

使用StringBuilder作為動態生成代碼的容器。 StringBuilder 為我們節省了在 memory 中復制字符串的成本。

使用AppendLine()將字符串換行符添加到我們正在構建的字符串中。

將方法調用強制轉換為[void]以從方法中丟棄 output。

使用foreach遍歷$FileNameLike的每個值

請注意, $myString沒有被轉義。 這就是允許將$FileNameLike中的值動態引入代碼的原因。


$myScript = $beginning + $middle.ToString() + $end 

要 output 來自 StringBuilder object 的字符串,請使用ToString()方法。


Invoke-Expression -Command $myScript

動態生成命令后,使用Invoke-Expression執行字符串的內容。

使用Invoke-Expression執行字符串存在安全風險 出現在字符串中的任何用戶輸入都可以執行或刪除基礎用戶可以執行的任何操作。


您提到有 56,000 個文件需要處理。 如果您能夠使用PowerShell 7 ,則可以使用ForEach-Object -Parallel來提高處理速度。 這些示例討論了您需要使用 -Parallel 的一些編碼實踐。

如果您想要一種快速可靠的文件搜索方式,您可能會喜歡X1 Search

如果您選擇使用正則表達式,請注意[regex]::Escape()

通過用轉義碼替換一組最小字符(、*、+、?、|、{、[、(,)、^、$、.、# 和空格)來轉義它們。

例如,括號]和大括號}不會被轉義。 這可能不會影響此用例。 請理解 [regex]::Escape() 的局限性,並准備好測試用例。


這是一個使用-FilterScript劫持 shell 的演示。

$location = "C:\lab" 

$FileNameLike = "'; Start-Process -FilePath powershell.exe -ArgumentList `"-Command ```{```"Hello```"```}`" -NoNewWindow; '", "2016-10" #, "2016-08", "2016-10"

$filter = if (!$FileNameLike) {
    [scriptblock]::Create('$_.Name -like "*"')
}
else {
    $output = foreach ($filter in $FileNameLike) {
        "`$_.Name -like '*$filter*'"
    }
    [scriptblock]::Create($output -join ' -or ')
}

#Line 103
Get-ChildItem -Path $location -Recurse -File | Where-Object -FilterScript $filter




PS > .\proof of concept.ps1

    Directory: C:\lab

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---            3/5/2021  7:45 AM              0 report 2016-09-01.txt
-a---            3/5/2021  7:45 AM              0 report 2016-10-01.txt
-a---            3/5/2021  7:45 AM              0 report 2017-09-01.txt

PS > Hello
HelloHello

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM