如何从PowerShell中检索递归目录和文件列表,不包括某些文件和文件夹?

Sak*_*o73 27 powershell powershell-2.0

我想编写一个PowerShell脚本,它将递归搜索目录,但排除指定的文件(例如*.log,和myFile.txt),还排除指定的目录及其内容(例如,myDir下面的所有文件和文件夹myDir).

我一直在使用Get-ChildItemCmdLet和Where-ObjectCmdLet,但我似乎无法得到这种确切的行为.

Mic*_*ens 54

我喜欢Keith Hill的答案,除了它有一个错误,阻止它递过两个级别.这些命令显示错误:

New-Item level1/level2/level3/level4/foobar.txt -Force -ItemType file
cd level1
GetFiles . xyz | % { $_.fullname }
Run Code Online (Sandbox Code Playgroud)

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

...\level1\level2
...\level1\level2\level3
Run Code Online (Sandbox Code Playgroud)

这是一个经过修正且稍微重构的版本:

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
        }
    }
} 
Run Code Online (Sandbox Code Playgroud)

通过该错误修复,您可以获得此更正的输出:

...\level1\level2
...\level1\level2\level3
...\level1\level2\level3\level4
...\level1\level2\level3\level4\foobar.txt
Run Code Online (Sandbox Code Playgroud)

我也喜欢ajk的简洁回答,正如他所指出的那样,效率较低.顺便说一下,它效率较低的原因是因为当ajk继续时,Hill的算法在找到修剪目标时停止遍历子树.但是ajk的答案也有一个缺陷,我称之为祖先陷阱.考虑一个这样的路径,它包括两次相同的路径组件(即subdir2):

\usr\testdir\subdir2\child\grandchild\subdir2\doc
Run Code Online (Sandbox Code Playgroud)

将您的位置设置在两者之间,例如cd \usr\testdir\subdir2\child,然后运行ajk的算法来过滤掉较低的值subdir2,您将完全没有输出,即它会过滤掉所有内容,因为subdir2路径中存在较高的值.这是一个极端情况,并且不太经常被击中,所以我不排除ajk的解决方案,因为这个问题.

不过,我在这里提供了第三种选择,一个,它具有上述任何两个错误的.这是基本算法,包含修剪路径或路径的便利性定义 - 您只需修改$excludeList自己的目标集即可使用它:

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

我的算法相当简洁,但是像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 
Run Code Online (Sandbox Code Playgroud)

...在内部转换为:

Get-ChildItem | FilterExcludeTree | FilterSvn | FilterFullName
Run Code Online (Sandbox Code Playgroud)

每个过滤器必须符合某些规则:接受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 }
    }
  }
} 
Run Code Online (Sandbox Code Playgroud)

这里唯一的另一个部分是Coalesce-Args函数(在Keith Dahlby的这篇文章中找到),它只是在调用没有指定任何路径的情况下将当前目录发送到管道.

因为这个答案有点冗长,而不是详细介绍这个过滤器,我将感兴趣的读者引用到我最近发表的关于Simple-Talk.com的文章,名为Practical PowerShell:Pruning File Trees and extends Cmdlet,我在其中讨论Get-EnhancedChildItem更长的.不过,我要提到的最后一件事是我的开源库New-FileTree中的另一个函数,它允许您生成用于测试目的的虚拟文件树,以便您可以使用上述任何算法.当你在试验其中的任何一个时,我建议% { $_.fullname }像第一个代码片段中那样进行管道处理,以便检查更有用的输出.

  • @Tariq:你引用的语句是_not_来自我上面的代码; 这是来自Keith Hill的原始答案,实际上是我的代码所解决的问题之一.如果你看看我的GetFiles函数,你会发现我使用`$ item.FullName`而不仅仅是'$ item`作为`Test-Path`的第一个参数,这应该是你需要它才能使它适合你. (2认同)

Kei*_*ill 25

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


ajk*_*ajk 12

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

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

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

更新:请通过这种方法检查msorens的优秀答案是否存在边缘情况缺陷,以及整体更加充实的解决方案.