簡體   English   中英

如何從 PowerShell 檢索遞歸目錄和文件列表,不包括某些文件和文件夾?

[英]How to retrieve a recursive directory and file list from PowerShell excluding some files and folders?

我想編寫一個 PowerShell 腳本,它將遞歸搜索目錄,但排除指定的文件(例如, *.logmyFile.txt ),並排除指定的目錄及其內容(例如, myDir和所有文件和myDir文件夾)。

我一直在使用Get-ChildItem CmdLet 和Where-Object CmdLet,但我似乎無法獲得這種確切的行為。

我喜歡 Keith Hill 的回答,但它有一個錯誤,阻止它遞歸超過兩個級別。 這些命令顯示了錯誤:

New-Item level1/level2/level3/level4/foobar.txt -Force -ItemType file
cd level1
GetFiles . xyz | % { $_.fullname }

使用希爾的原始代碼,您會得到:

...\level1\level2
...\level1\level2\level3

這是一個更正且稍微重構的版本:

function GetFiles($path = $pwd, [string[]]$exclude)
{
    foreach ($item in Get-ChildItem $path)
    {
        if ($exclude | Where {$item -like $_}) { continue }

        $item
        if (Test-Path $item.FullName -PathType Container)
        {
            GetFiles $item.FullName $exclude
        }
    }
} 

有了這個錯誤修復,你會得到這個更正的輸出:

...\level1\level2
...\level1\level2\level3
...\level1\level2\level3\level4
...\level1\level2\level3\level4\foobar.txt

我也喜歡 ajk簡潔的回答,但正如他指出的那樣,效率較低。 順便說一下,它效率較低的原因是,Hill 的算法在找到修剪目標時停止遍歷子樹,而 ajk 的算法仍在繼續。 但是 ajk 的回答也有一個缺陷,我稱之為祖先陷阱 考慮這樣的路徑,其中包含兩次相同的路徑組件(即 subdir2):

\usr\testdir\subdir2\child\grandchild\subdir2\doc

將您的位置設置在兩者之間,例如cd \\usr\\testdir\\subdir2\\child ,然后運行 ​​ajk 的算法來過濾掉較低的subdir2並且您根本不會得到任何輸出,即它過濾掉所有內容,因為subdir2較高的存在路徑。 不過,這是一個極端情況,不太可能經常被擊中,因此我不排除由於這個問題而使用 ajk 的解決方案。

盡管如此,我在這里提供了第三種選擇,它沒有上述兩個錯誤中的任何一個。 這是基本算法,並附有一個或多個要修剪的路徑的便捷定義——您只需將$excludeList修改$excludeList您自己的一組目標即可使用它:

$excludeList = @("stuff","bin","obj*")
Get-ChildItem -Recurse | % {
    $pathParts = $_.FullName.substring($pwd.path.Length + 1).split("\");
    if ( ! ($excludeList | where { $pathParts -like $_ } ) ) { $_ }
}

我的算法相當簡潔,但與 ajk 的算法一樣,它的效率低於 Hill 的算法(出於同樣的原因:它不會停止在修剪目標處遍歷子樹)。 然而,我的代碼比 Hill 有一個重要的優勢——它可以流水線化! 因此,可以適應過濾器鏈來制作 Get-ChildItem 的自定義版本,而 Hill 的遞歸算法則不能。 ajk 的算法也可以適用於管道使用,但指定要排除的一個或多個項目並不那么干凈,它被嵌入到正則表達式中,而不是我使用過的簡單項目列表中。

我已將我的樹修剪代碼打包到 Get-ChildItem 的增強版本中。 除了我相當缺乏想象力的名字——Get -EnhancedChildItem——我對它感到很興奮,並將它包含在我的開源 Powershell 庫中 除了樹修剪之外,它還包括其他幾個新功能。 此外,代碼被設計為可擴展的:如果您想添加新的過濾功能,這很簡單。 本質上,首先調用 Get-ChildItem,然后將其通過管道傳輸到您通過命令參數激活的每個后續過濾器中。 因此像這樣的事情......

Get-EnhancedChildItem –Recurse –Force –Svn
    –Exclude *.txt –ExcludeTree doc*,man -FullName -Verbose 

...在內部轉換為:

Get-ChildItem | FilterExcludeTree | FilterSvn | FilterFullName

每個過濾器都必須符合某些規則:接受 FileInfo 和 DirectoryInfo 對象作為輸入,生成與輸出相同的對象,並使用 stdin 和 stdout 以便它可以插入到管道中。 下面是重構以適應這些規則的相同代碼:

filter FilterExcludeTree()
{
  $target = $_
  Coalesce-Args $Path "." | % {
    $canonicalPath = (Get-Item $_).FullName
    if ($target.FullName.StartsWith($canonicalPath)) {
      $pathParts = $target.FullName.substring($canonicalPath.Length + 1).split("\");
      if ( ! ($excludeList | where { $pathParts -like $_ } ) ) { $target }
    }
  }
} 

這里唯一的附加部分是 Coalesce-Args 函數(在 Keith Dahlby 的這篇文章中找到),它僅在調用未指定任何路徑的情況下將當前目錄發送到管道中。

因為這個答案有點冗長,而不是詳細介紹這個過濾器,我建議感興趣的讀者閱讀我最近在 Simple-Talk.com 上發表的題為實用 PowerShell:修剪文件樹和擴展Cmdlets 的文章,我在其中討論了 Get-EnhancedChildItem在更大的長度。 不過,我要提到的最后一件事是我的開源庫New-FileTree 中的另一個函數,它允許您生成用於測試目的的虛擬文件樹,以便您可以使用上述任何算法。 當您嘗試其中任何一個時,我建議使用管道% { $_.fullname }就像我在第一個代碼片段中所做的那樣,以獲得更多有用的輸出來檢查。

Get-ChildItem cmdlet 有一個很容易使用的-Exclude參數,但它不適用於根據我的判斷過濾掉整個目錄。 嘗試這樣的事情:

function GetFiles($path = $pwd, [string[]]$exclude) 
{ 
    foreach ($item in Get-ChildItem $path)
    {
        if ($exclude | Where {$item -like $_}) { continue }

        if (Test-Path $item.FullName -PathType Container) 
        {
            $item 
            GetFiles $item.FullName $exclude
        } 
        else 
        { 
            $item 
        }
    } 
}

這是另一種選擇,效率較低但更簡潔。 這就是我通常處理此類問題的方式:

Get-ChildItem -Recurse .\targetdir -Exclude *.log |
  Where-Object { $_.FullName -notmatch '\\excludedir($|\\)' }

\\\\excludedir($|\\\\)'表達式允許您同時排除目錄及其內容。

更新:請檢查msorens的優秀答案,了解這種方法的邊緣情況缺陷,以及整體上更加充實的解決方案。

最近,我探索了參數化要掃描的文件夾以及存儲遞歸掃描結果的位置的可能性。 最后,我還總結了掃描的文件夾數量和里面的文件數量。 與社區共享,以防它可以幫助其他開發人員。

    ##Script Starts
    #read folder to scan and file location to be placed

    $whichFolder = Read-Host -Prompt 'Which folder to Scan?'  
    $whereToPlaceReport = Read-Host -Prompt 'Where to place Report'
    $totalFolders = 1
    $totalFiles = 0

    Write-Host "Process started..."

    #IMP separator ? : used as a file in window cannot contain this special character in the file name

    #Get Foldernames into Variable for ForEach Loop
    $DFSFolders = get-childitem -path $whichFolder | where-object {$_.Psiscontainer -eq "True"} |select-object name ,fullName

    #Below Logic for Main Folder
    $mainFiles = get-childitem -path "C:\Users\User\Desktop" -file
    ("Folder Path" + "?" + "Folder Name" + "?" + "File Name " + "?"+ "File Length" )| out-file "$whereToPlaceReport\Report.csv" -Append

    #Loop through folders in main Directory
    foreach($file in $mainFiles)
    {

    $totalFiles = $totalFiles + 1
    ("C:\Users\User\Desktop" + "?" + "Main Folder" + "?"+ $file.name + "?" + $file.length ) | out-file "$whereToPlaceReport\Report.csv" -Append
    }


    foreach ($DFSfolder in $DFSfolders)
    {
    #write the folder name in begining
    $totalFolders = $totalFolders + 1

    write-host " Reading folder C:\Users\User\Desktop\$($DFSfolder.name)"
    #$DFSfolder.fullName | out-file "C:\Users\User\Desktop\PoC powershell\ok2.csv" -Append
    #For Each Folder obtain objects in a specified directory, recurse then filter for .sft file type, obtain the filename, then group, sort and eventually show the file name and total incidences of it.

    $files = get-childitem -path "$whichFolder\$($DFSfolder.name)" -recurse

    foreach($file in $files)
    {
    $totalFiles = $totalFiles + 1
    ($DFSfolder.fullName + "?" + $DFSfolder.name + "?"+ $file.name + "?" + $file.length ) | out-file "$whereToPlaceReport\Report.csv" -Append
    }

    }


    # If running in the console, wait for input before closing.
    if ($Host.Name -eq "ConsoleHost")
    {

    Write-Host "" 
    Write-Host ""
    Write-Host ""

    Write-Host  "                            **Summary**"  -ForegroundColor Red
    Write-Host  "                            ------------" -ForegroundColor Red

    Write-Host  "                           Total Folders Scanned = $totalFolders "  -ForegroundColor Green
    Write-Host  "                           Total Files   Scanned = $totalFiles "     -ForegroundColor Green

    Write-Host "" 
    Write-Host "" 
        Write-Host "I have done my Job,Press any key to exit" -ForegroundColor white
        $Host.UI.RawUI.FlushInputBuffer()   # Make sure buffered input doesn't "press a key" and skip the ReadKey().
        $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyUp") > $null
    }

##Output

在此處輸入圖片說明

##Bat Code to run above powershell command

@ECHO OFF
SET ThisScriptsDirectory=%~dp0
SET PowerShellScriptPath=%ThisScriptsDirectory%MyPowerShellScript.ps1
PowerShell -NoProfile -ExecutionPolicy Bypass -Command "& {Start-Process PowerShell -ArgumentList '-NoProfile -ExecutionPolicy Bypass -File ""%PowerShellScriptPath%""' -Verb RunAs}";

有點晚了,但試試這個。

function Set-Files($Path) {
    if(Test-Path $Path -PathType Leaf) {
        # Do any logic on file
        Write-Host $Path
        return
    }

    if(Test-Path $path -PathType Container) {
        # Do any logic on folder use exclude on get-childitem
        # cycle again
        Get-ChildItem -Path $path | foreach { Set-Files -Path $_.FullName }
    }
}

# call
Set-Files -Path 'D:\myFolder'

在這里發表評論,因為這似乎是關於搜索文件同時排除 powershell 中的某些目錄的主題的最受歡迎的答案。

為了避免結果的后過濾問題(即避免權限問題等),我只需要過濾掉頂級目錄,這就是本示例的全部內容,因此雖然本示例不過濾子目錄名稱,但它可能非常如果您願意,很容易遞歸以支持這一點。

快速細分代碼段的工作原理

$folders << 使用 Get-Childitem 查詢文件系統並執行文件夾排除

$file << 我要找的文件的模式

foreach << 使用 Get-Childitem 命令迭代 $folders 變量執行遞歸搜索

$folders = Get-ChildItem -Path C:\ -Directory -Name -Exclude Folder1,"Folder 2"
$file = "*filenametosearchfor*.extension"

foreach ($folder in $folders) {
   Get-Childitem -Path "C:/$folder" -Recurse -Filter $file | ForEach-Object { Write-Output $_.FullName }
}

暫無
暫無

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

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