【问题标题】:How to retrieve a recursive directory and file list from PowerShell excluding some files and folders?如何从 PowerShell 中检索递归目录和文件列表,不包括某些文件和文件夹?
【发布时间】:2011-12-22 20:55:33
【问题描述】:

我想编写一个 PowerShell 脚本,它将递归搜索目录,但排除指定文件(例如,*.logmyFile.txt),同时排除指定目录及其内容(例如,@987654325 @ 以及myDir 下的所有文件和文件夹)。

我一直在使用 Get-ChildItem CmdLet 和 Where-Object CmdLet,但我似乎无法获得这种确切的行为。

【问题讨论】:

    标签: powershell powershell-2.0


    【解决方案1】:

    我喜欢 Keith Hill 的回答,但它有一个错误,阻止它递归过去两个级别。这些命令显示了该错误:

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

    使用 Hill 的原始代码,您可以得到:

    ...\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,您将完全得到 no 输出,即它会过滤掉所有内容,因为路径中存在较高的 subdir2。不过,这是一个极端情况,不太可能经常被击中,所以我不会因为这个问题而排除 ajk 的解决方案。

    不过,我在这里提供第三种选择,一种 有上述两个错误之一的选择。以下是基本算法,还包含一个或多个修剪路径的便捷定义——您只需将$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--我对此感到很兴奋,并将其包含在我的open source Powershell library 中。除了树修剪之外,它还包括其他几个新功能。此外,代码被设计为可扩展的:如果您想添加新的过滤功能,这很简单。本质上,首先调用 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 的 this post 中找到),如果调用未指定任何路径,它只会将当前目录发送到管道中。

    因为这个答案有点冗长,所以我没有详细介绍这个过滤器,所以我建议感兴趣的读者阅读我最近在 Simple-Talk.com 上发表的题为Practical PowerShell: Pruning File Trees and Extending Cmdlets 的文章,我在其中更深入地讨论了 Get-EnhancedChildItem长度。不过,我要提到的最后一件事是我的开源库New-FileTree 中的另一个函数,它可以让您生成一个虚拟文件树用于测试目的,以便您可以使用上述任何算法。当您尝试其中任何一个时,我建议像在第一个代码片段中所做的那样通过管道连接到% { $_.fullname },以便检查更有用的输出。

    【讨论】:

    • +1 因为我自己的评论才注意到这个答案。真是好工作!我知道我的方法有点胶带和泡泡糖的味道,但你已经把解决方案提升到了一个新的水平。也感谢您指出祖先陷阱。虽然您是对的,它不太可能经常出现,但您应该它咬你之前意识到这一点。
    • 感谢@ajk 的客气话。但不要卖空自己;您的回答绝对有其简洁的优点。
    • 我已经设置了您的测试文件夹层次结构,然后尝试了您的“更正并略微重构”版本,但它仍然产生与 Keith Hill 版本相同的输出。即只显示level2和level3。我尝试了你的“第三种选择”并且那个有效。它显示所有级别和 foobar.txt 文件。我在 Win 7 上使用 PS 版本 2。仅供参考。
    • 是的,它们是不同的。这是一个子管道:$excludeList | where { $pathParts -like $_ } 所以$_ 采用排除列表中每个成员的值。现在让我们将原始行抽象地重写为if (not_on_exclusion_list) { $_ }。即如果满足条件,则输出当前管道的成员,即Get-ChildItem返回的当前项。
    • @Tariq:你引用的语句是 not 来自我上面的代码;这来自基思希尔的原始答案,实际上是我的代码解决的问题之一。如果您在上面查看我的 GetFiles 函数,您会发现我使用 $item.FullName 而不仅仅是 $item 作为 Test-Path 的第一个参数,这应该是让它为您工作所需要的全部内容。
    【解决方案2】:

    Get-ChildItem cmdlet 有一个-Exclude 参数,很想使用它,但它不能用于过滤掉我所知道的整个目录。试试这样的:

    函数 GetFiles($path = $pwd, [string[]]$exclude) { foreach(Get-ChildItem $path 中的 $item) { if ($exclude | Where {$item -like $_}) { continue } if (Test-Path $item.FullName -PathType 容器) { $项目 获取文件 $item.FullName $exclude } 别的 { $项目 } } }

    【讨论】:

    • 我喜欢你在内部使用带有管道的 if 的方式,出色的简洁语法,就像 @jonZ 说你忘记了递归调用中的 $exclude 参数
    • @jonZ,是的,arg 应该通过递归调用传递下去。很好的收获。
    • 一个旧帖子,但可能值得澄清。上面的 directory 检查在我的情况下不起作用,即Test-Path $item -PathType Container。我不得不改用$item.PSIsContainer。 PS:我正在使用带有 -Recurse 开关的 Get-ChildItem(以防它有任何影响)。
    • 博客文章 Practical PowerShell: Pruning File Trees and Extending Cmdlets 中提到了这个答案(包括有一个错误(?) - 与迈克尔索伦斯的回答(同一作者)中提到的相同错误(? ) .
    【解决方案3】:

    这是另一种选择,效率较低但更简洁。这就是我通常处理此类问题的方式:

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

    \\excludedir($|\\)' 表达式允许您同时排除目录及其内容。

    更新:请查看 msorens 的优秀答案,了解这种方法的边缘情况缺陷,以及更充实的整体解决方案。

    【讨论】:

    • +1 你能解释一下\\excludedir($|\\)这个表达式的作用吗?谢谢。
    • 没问题!它是一个正则表达式,匹配完整路径包含\excludedir 的任何文件或文件夹。 ($|\\) 部分表示模式匹配完整路径名的结尾或尾部反斜杠。所以它将匹配\dir1\dir2\excludedirdir1\excludedir\dir2。我强烈建议您查看@msorens 的答案。除了总体上是一个很好的答案之外,他还指出了我的方法中的一个缺点。
    • +1 谢谢,很喜欢这个表情,已经放在我的笔记本上了。另请参阅我的 cmets 对 msorens 的回答。仅供参考:您的解决方案也只有两个层次。不明白这是为什么。
    【解决方案4】:

    最近,我探索了参数化要扫描的文件夹以及将存储递归扫描结果的位置的可能性。最后,我也总结了扫描的文件夹数量和里面的文件数量。与社区分享,以防它对其他开发者有所帮助。

        ##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}";
    

    【讨论】:

      【解决方案5】:

      有点晚了,试试这个吧。

      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'
      

      【讨论】:

        【解决方案6】:

        在此处发表评论,因为这似乎是关于搜索文件同时在 powershell 中排除某些目录的主题的最流行答案。

        为了避免结果后过滤的问题(即避免权限问题等),我只需要过滤掉顶级目录,这就是本示例所基于的全部内容,因此虽然本示例不过滤子目录名称,如果您愿意,可以很容易地递归地支持这一点。

        快速分解 sn-p 的工作原理

        $folders

        $file

        foreach

        $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 }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-02-13
          • 1970-01-01
          • 2015-08-14
          • 1970-01-01
          • 2018-01-11
          • 2010-11-02
          相关资源
          最近更新 更多