如何使用Powershell列出其中一个文件夹中存在的文件夹结构中的重复文件

时间:2014-11-21 17:26:17

标签: powershell powershell-v3.0

我有一个源树,比如c:\ s,有很多子文件夹。其中一个子文件夹名为“c:\ s \ Includes”,它可以递归地包含一个或多个.cs文件。

我想确保c:\ s \ Includes ...中没有任何.cs文件存在于c:\ s下的任何其他文件夹中,递归。

我编写了以下PowerShell脚本,但是我不确定是否有更简单的方法。我有不到24小时的PowerShell经验,所以我觉得有更好的方法。

我可以假设至少使用了PowerShell 3。

我会接受任何改进我的剧本的答案,但我会等几天才接受答案。当我说“改进”时,我的意思是它使它更短,更优雅或更好的性能。

任何人的帮助都将不胜感激。

当前代码:

$excludeFolder = "Includes"

$h = @{}
foreach ($i in ls $pwd.path *.cs -r -file | ? DirectoryName -notlike ("*\" + $excludeFolder + "\*")) { $h[$i.Name]=$i.DirectoryName }
ls ($pwd.path + "\" + $excludeFolder) *.cs -r -file | ? { $h.Contains($_.Name) } | Select @{Name="Duplicate";Expression={$h[$_.Name] + " has file with same name as " + $_.Fullname}}

5 个答案:

答案 0 :(得分:2)

我会或多或少地做同样的事情,除了我从includes文件夹的内容构建哈希表,然后运行其他所有内容来检查重复项:

$root     = 'C:\s'
$includes = "$root\includes"

$includeList = @{}
Get-ChildItem -Path $includes -Filter '*.cs' -Recurse -File |
  % { $includeList[$_.Name] = $_.DirectoryName }

Get-ChildItem -Path $root -Filter '*.cs' -Recurse -File |
  ? { $_.FullName -notlike "$includes\*" -and $includeList.Contains($_.Name) } |
  % { "Duplicate of '{0}': {1}" -f $includeList[$_.Name], $_.FullName }

答案 1 :(得分:2)

1

我盯着这看了一会儿,决心写下它而不研究现有的答案,但我已经看了一眼Matt提到Group-Object的答案的第一句话。经过一些不同的方法,我得到了基本相同的答案,除了他是长形式和强大的正则表达式转义和设置变量,我的简洁,因为你要求更短的答案,因为这更有趣。

$inc = '^c:\\s\\includes'
$cs = (gci -R 'c:\s' -File -I *.cs) | group name
$nopes = $cs |?{($_.Group.FullName -notmatch $inc)-and($_.Group.FullName -match $inc)}
$nopes | % {$_.Name; $_.Group.FullName}

示例输出:

someFile.cs
c:\s\includes\wherever\someFile.cs
c:\s\lib\factories\alt\someFile.cs
c:\s\contrib\users\aa\testing\someFile.cs

概念是:

  1. 获取整个源代码树中的所有.cs文件
  2. 将它们分成{filename:{共享此文件名的文件}}
  3. 对于每个组,仅保留文件集包含任何文件的文件,该文件的路径与包含文件夹匹配,并且包含路径与包含文件夹不匹配的任何文件。这一步涵盖
    1. 重复(如果文件只有在无法通过两个测试时才存在)
    2. 在{includes / not-includes}之间重复划分,而不是在一个分支中重复
    3. 处理三次重复,n-tuplicates。
  4. 编辑:我将^添加到$inc表示它必须在字符串的开头匹配,因此正则表达式引擎可以更快地针对不匹配的路径失败。也许这算是过早的优化。


    2

    经过那次非常密集的尝试之后,更清晰的答案的形状要容易得多:

    1. 获取所有文件,将其拆分为包含,不包含数组。
    2. 针对每个其他文件嵌套for循环测试每个文件。
    3. 更长,但极大写得更快(虽然运行速度较慢),我觉得对于不知道它做什么的人来说更容易阅读。

      $sourceTree = 'c:\\s'
      
      $allFiles = Get-ChildItem $sourceTree -Include '*.cs' -File -Recurse
      
      $includeFiles = $allFiles | where FullName -imatch "$($sourceTree)\\includes"
      $otherFiles = $allFiles | where FullName -inotmatch "$($sourceTree)\\includes"
      
      foreach ($incFile in $includeFiles) {
          foreach ($oFile in $otherFiles) {
              if ($incFile.Name -ieq $oFile.Name) {
                  write "$($incFile.Name) clash"
                  write "* $($incFile.FullName)"
                  write "* $($oFile.FullName)"
                  write "`n"
              }
          }
      }
      

      3

      因为代码高尔夫很有趣。如果哈希表更快,那么即使是更少测试的单行程怎么样......

      $h=@{};gci c:\s -R -file -Filt *.cs|%{$h[$_.Name]+=@($_.FullName)};$h.Values|?{$_.Count-gt1-and$_-like'c:\s\includes*'}
      

      编辑:此版本的解释:它与版本1的解决方法大致相同,但分组操作在哈希表中明确发生。哈希表的形状变为:

      $h = {
          'fileA.cs': @('c:\cs\wherever\fileA.cs', 'c:\cs\includes\fileA.cs'),
          'file2.cs': @('c:\cs\somewhere\file2.cs'),
          'file3.cs': @('c:\cs\includes\file3.cs', 'c:\cs\x\file3.cs', 'c:\cs\z\file3.cs')
      }
      

      它为所有.cs文件命中一次磁盘,迭代整个列表以构建哈希表。对于那一点,我认为它不能做比这更少的工作。

      它使用+=,因此它可以将文件添加到该文件名的现有数组中,否则它将覆盖每个哈希表列表,并且它们只是最近看到的文件的一个项目。

      它使用@() - 因为当它第一次到达文件名时,$h[$_.Name]将不会返回任何内容,并且脚本需要首先将数组放入哈希表中,而不是字符串。如果它是+=$_.FullName那么第一个文件将作为字符串进入哈希表,下一次+=将进行字符串连接,这对我没用。这会强制哈希表中的第一个文件通过强制每个文件成为一个项目数组来启动数组。获得此结果的最少代码方式是+=@(..),但为每个文件创建一次性数组的流失是不必要的工作。也许将其更改为更长的代码,这会减少阵列创建会有所帮助吗?

      更改部分

      %{$h[$_.Name]+=@($_.FullName)}
      

      类似

      %{if (!$h.ContainsKey($_.Name)){$h[$_.Name]=@()};$h[$_.Name]+=$_.FullName}
      

      (我猜,我对最有可能是缓慢的PowerShell代码并没有太多直觉,并且没有经过测试)。

      之后,使用h.Values不是第二次遍历每个文件,它会遍历散列表中的每个数组 - 每个唯一文件名一个。这必须检查数组大小并修剪不重复,但-and操作短路 - 当Count -gt 1失败时,右边的位检查路径名称不会运行

      如果数组中包含两个或更多文件,则-and $_ -like ...执行并匹配模式以查看是否至少有一个重复项位于includes路径中。 (错误:如果所有重复项都在c:\cs\includes而其他任何地方都没有,它仍会显示它们。)

      -

      4

      这是带有哈希表初始化调整的编辑版本3,现在它跟踪$ s中看到的文件,然后只考虑那些不止一次看过的文件。

      $h=@{};$s=@{};gci 'c:\s' -R -file -Filt *.cs|%{if($h.ContainsKey($_.Name)){$s[$_.Name]=1}else{$h[$_.Name]=@()}$h[$_.Name]+=$_.FullName};$s.Keys|%{if ($h[$_]-like 'c:\s\includes*'){$h[$_]}}
      

      无论如何,假设它有效,那就是它的作用。

      - 编辑主题分支;我一直认为应该有一种方法来处理System.Data命名空间中的事情。任何人都知道你是否可以将System.Data.DataTable().ReadXML()连接到gci | ConvertTo-Xml而不需要大量的样板文件?

答案 2 :(得分:1)

我对此并没有像我想的那样留下深刻的印象,但我认为Group-Object可能在这个问题中占有一席之地,所以我提出以下内容:

$base = 'C:\s'
$unique = "$base\includes"
$extension = "*.cs"

Get-ChildItem -Path $base -Filter $extension -Recurse | 
        Group-Object $_.Name | 
        Where-Object{($_.Count -gt 1) -and (($_.Group).FullName -match [regex]::Escape($unique))} | 
        ForEach-Object {
            $filename = $_.Name
            ($_.Group).FullName -notmatch [regex]::Escape($unique) | ForEach-Object{
                "'{0}' has file with same name as '{1}'" -f (Split-Path $_),$filename
            }
        }

使用扩展名过滤器$extension收集所有文件。根据文件名称对文件进行分组。然后,这些组中的每个组都会找到有多个特定文件的组,其中一个组成员至少在目录$unique中。获取这些组并打印出不属于唯一目录的所有文件。

来自评论

这是值得的,这是我用于测试创建一堆文件的东西。 (我知道文件夹9是空的)

$base = "E:\Temp\dev\cs"
Remove-Item "$base\*" -Recurse -Force
0..9 | %{[void](New-Item -ItemType directory "$base\$_")}
1..1000 | %{
    $number = Get-Random -Minimum 1 -Maximum 100
    $folder = Get-Random -Minimum 0 -Maximum 9
    [void](New-Item -Path $base\$folder -ItemType File -Name "$number.txt" -Force)
}

答案 3 :(得分:1)

在看完所有其他人之后,我想我会尝试不同的方法。

$includes = "C:\s\includes"
$root = "C:\s"

# First script
Measure-Command {
    [string[]]$filter = ls $includes -Filter *.cs -Recurse | % name
    ls $root -include $filter -Recurse -Filter *.cs | 
        Where-object{$_.FullName -notlike "$includes*"}
}

# Second Script
Measure-Command {
    $filter2 = ls $includes -Filter *.cs -Recurse 
    ls $root -Recurse -Filter *.cs | 
        Where-object{$filter2.name -eq $_.name -and $_.FullName -notlike "$includes*"}
}

在我的第一个脚本中,我将所有包含文件放入字符串数组中。然后我使用该字符串数组作为get-childitem上的include参数。最后,我从结果中过滤掉了包含文件夹。

在我的第二个脚本中,我枚举所有内容,然后在管道后进行过滤。

删除measure-command以查看结果。我用它来检查速度。使用我的数据集,第一个数据集的速度提高了40%。

答案 4 :(得分:0)

colleges/update_cities
  1. 创建要查找的文件名列表。
  2. 查找列表中的所有文件,但不是
  3. 生成列表的目录的一部分
  4. 打印他们的名字和目录