【问题标题】:Powershell recursively move 20,000 files at a timePowershell 一次递归移动 20,000 个文件
【发布时间】:2017-03-09 08:39:22
【问题描述】:

我是 Powershell 的新手。我正在尝试将要处理的数据移动到其他地方,这些数据对一次可以处理的文件数量有限制。我的源文件夹有数百个子文件夹,每个子文件夹又可能有多达 100 个子文件夹,总计多达数百万个小文件。假定文件名是导出的唯一 25 位输出,因此如果排除检查可加快整个过程,则无需在每次移动时检查现有文件名。

我想使用 Powershell 自动将这些文件从源路径移动(而不是复制)到每个包含 20,000 个文件的增量命名目标文件夹。目标输出文件夹名称并不重要,只要它们在每个目标中的文件不超过 20,000 个并且不与现有文件夹名称重叠即可。

例子:

来源:C:\MyFiles 有 100 个子文件夹,每个子文件夹也有 100 个子文件夹。

C:\Myfiles\Folder000\Folder000A\file1.txt C:\Myfiles\Folder000\Folder000A\file2.txt C:\Myfiles\Folder000\Folder000A\file3.txt C:\Myfiles\Folder000\Folder000B\file1.txt C:\Myfiles\Folder000\Folder000B\file1.txt C:\Myfiles\Folder001\Folder000A\file1.txt ...etc

目标 D:\My20Kfiles

D:\My20kFiles\000001 [20,000 messages] D:\My20kFiles\000002 [next 20,000 messages] D:\My20kFiles\000003 [next 20,000 messages] ...etc

【问题讨论】:

  • 执行gci |select -first 10时你有什么想法吗;-)
  • 对,但我怎样才能一次循环通过该输出一行将其移动到目的地?我知道我可以使用 -first like get-ChildItem -recurse -Filter *.txt -file | 获得输出到屏幕的完整路径选择 -first 10000 | % { $_.FullName }
  • 澄清一下:在将文件移动到目标文件夹时,您可以展平输入层次结构吗?这与您的示例数据不一致,该示例数据具有非唯一文件名,例如 file1.txt
  • 我已修改代码以通过路径和 $testmode=$false 传递日志文件文件名

标签: powershell batch-file increment move


【解决方案1】:

列出文件,将它们移动到名为 (counter++ / 20000) 的文件夹中。

gci "c:\myfiles" -R -File | mv -Dest {"c:\My20kFiles\{0:d6}\" -f [math]::DivRem($global:counter++, 20000, [ref]$null)} -Force -WhatIf

未经测试。是的。 mv 将使用 -Force 创建文件夹。删除-WhatIf 使其实际运行。

它实际上并不检查文件夹中的 20k 个文件,或者它是否与现有文件夹名称重叠。但我猜这个原则适用。

同样未经测试,这会尝试将 20k 个文件放在一个文件夹中,然后转到下一个未使用的文件夹编号。

$FolderNumber = 0
$FilesRemainingForThisFolder = 0

Get-ChildItem "c:\myfiles" -Recurse -File | ForEach-Object {

    if (0 -eq $FilesRemainingForThisFolder)
    {
        # Keep counting foldernumber until we get to a folder name that doesn't exist.
        do {
            $FolderNumber++
            $DestinationFolder = "c:\My20kFiles\{0:d6}\" -f $FolderNumber
        } while ((Test-Path $DestinationFolder))

        mkdir $DestinationFolder
        $FilesRemainingForThisFolder = 19999
    }
    else
    {
        Move-Item -LiteralPath $_.FullName -Destination $DestinationFolder -Force -WhatIf
        $FilesRemainingForThisFolder--
    }

}

从我的代码块自动生成的 PS 帮助链接(如果有):

【讨论】:

  • 干得好;不知道 pass-a-script-block-as-a-parameter-value-evaluate-with-the-current-pipeline-object-as-a-parameter 技巧。 (记录在哪里?)我建议将[Math]::Floor() 作为[Math]::DivRem() 的更简单替代方案(至少在这种情况下,如果不需要其余部分,为了获得积极的结果)。
  • @mklement0 你知道sort-object 的上下文吗?例如gci | sort -property { $_.substring(3,5) } 按名称的一部分对文件进行排序?我不知道它在哪里记录,尽管这篇博客文章 - learn-powershell.net/2014/10/11/… - cmets 中的 MVP Dave Wyatt 说它没有很好的记录,并讨论了它是如何工作的。编辑:Jeffrey Snover 博客,它发生在这里:blogs.msdn.microsoft.com/powershell/2006/06/23/…
  • 感谢分享,这里需要-ea sil..,看看效果gci | sort -property { $_.Name.substring(1,1) } -ea silentlycontinue
  • @LotPings 点;我通常将它与正则表达式替换一起使用,当它失败时没有错误,例如gci | sort { $_.Name -replace '^prefix[123]' } 或类似的。 gci | sort { Get-Random } 之类的东西也是可能的,其中脚本块根本不引用文件名。
  • 感谢您提供出色的背景信息。 Jeffrey Snover 的帖子(从 2006 年开始!)已过时,因为它声称您可以使用该功能 - 他只是将其称为 ScriptBlock 参数 - 只有 non i>-pipeline 参数,但因为 - 至少 - PSv2 它仅适用于为管道输入而设计的参数。因此,Sort-Object 是一个异常,因为它的-Property 参数不是被设计为接受管道输入(尽管它的工作原理相同:当前管道对象被传递给脚本块)。
【解决方案2】:

试试这个

 $yourdir="C:\MyFiles"
 $yourdestdir="D:\My20Kfiles"
 $loop=20000
 $i=$loop;
 gci $yourdir -Recurse -File -Filter *.txt  | %{$newdir="$yourdestdir\{0:d6}\" -f [math]::DivRem($i++, $loop, [ref]$null)   ;new-item -Path $newdir -Force -ItemType Directory; Move-Item -Path $_.FullName -Dest $newdir -Force }

【讨论】:

  • 运行并立即返回提示,没有输出,并且似乎对用于 $yourdir 的测试样本副本目录没有采取任何操作
  • 你有文件 .txt 到你的目录 C:\MyFiles 吗?它适用于我的测试
  • 还有一个错误,我忽略了更正扩展名!谢谢你,这工作得很好!
【解决方案3】:

以下脚本是可测试的并执行以下任务:

检查目标文件夹是否为空,如果不是则退出。

将移动文件操作记录到名为 log.txt 的文件中以供稍后查看。

可以在测试模式下工作,提前知道会发生什么。

可以定义源文件夹-目标文件夹-增量值

   function Move-Files {
     param ($Source ,$Targert,$Increment=5 , $TestMode, $Logfile)

        $currentpath=pwd
        if (!$LogFile) {$Logfile= "{0}\{1}" -f $currentpath,"log.txt" }
        "log file is created in $logfile"  
        # check that destination folder is empty and create 
         if(!(Test-Path -Path $Targert )){
                  $null=New-Item -ItemType directory -Path $Targert -Force
          }
         else 
         {
          $targetFiles = get-childitem $Targert -Recurse  -File
          if ($targetFiles -ne $null) {"Target folder not empty, Please remove it";return}
         }


        $files=get-childitem $source -Recurse  -File    
        $fileCounter=0
        $folderCounter=0
        $Nextfolder="00001"
        "Moving {0} to {1}| Total Files: {2} | Increment:{3}" -f $Source, $Targert , $files.count,$increment |tee  $Logfile
        $files  |foreach {
          $fileCounter++ 
          if ((($fileCounter - 1) % $increment) -eq 0)
          { 
               $folderCounter++ 
               $Nextfolder= "{0}\{1}" -f $targert, $folderCounter.ToString("000000")    
               "Creating new folder {0}  at counter={1}" -f $Nextfolder,$fileCounter |tee -Append $Logfile
               #create Folder 
                $null=New-Item -ItemType directory -Path $Nextfolder -Force

              }
              if ($testMode)
              {
                "mv {0}  {1}" -f $_.FullName,$Nextfolder  |tee -Append $Logfile 
              }
              else
              {

                mv $_.FullName -Destination  $Nextfolder  -Force  #-WhatIf
                "mv {0}  {1}" -f $_.FullName,$Nextfolder  |tee -Append $Logfile
              }
        }
    } 

使用方法:

    $source="i:\myfiles"
    $targert="i:\My20kFiles"
    $increment=5  # for a test, modify 20000

     #run in test mode to know what will happen
     $TestMode=$true # modify to $flase to actual moving files

     Move-Files  -Source $source -Targert $targert  -Increment  $increment -TestMode $TestMode  

输出示例:(您可以在日志文件 log.txt 中找到相同的输出)

    Moving i:\myfiles to i:\My20kFiles| Total Files: 42 | Increment:5
    Creating new folder i:\My20kFiles\000001  at counter=1
    mv I:\myfiles\1.txt  i:\My20kFiles\000001
    mv I:\myfiles\10.txt  i:\My20kFiles\000001
    mv I:\myfiles\11.txt  i:\My20kFiles\000001
    mv I:\myfiles\12.txt  i:\My20kFiles\000001
    mv I:\myfiles\13.txt  i:\My20kFiles\000001
    Creating new folder i:\My20kFiles\000002  at counter=6
    mv I:\myfiles\14.txt  i:\My20kFiles\000002
    mv I:\myfiles\15.txt  i:\My20kFiles\000002
    mv I:\myfiles\16.txt  i:\My20kFiles\000002
    mv I:\myfiles\17.txt  i:\My20kFiles\000002
    mv I:\myfiles\18.txt  i:\My20kFiles\000002
    Creating new folder i:\My20kFiles\000003  at counter=11
    mv I:\myfiles\19.txt  i:\My20kFiles\000003
    mv I:\myfiles\2.txt  i:\My20kFiles\000003
    .................

更新:

我修改了代码: 添加日志文件作为参数。默认路径将是当前的 powershell 工作文件夹

$Testmode 最初为 false,因此您移动文件。您可以传递参数 $Testmode=$true 以在测试模式下运行(不移动到文件),您可以查看描述文件移动位置的日志文件。

您可以根据需要自定义代码

【讨论】:

  • 将 20k 文件扁平化到每个集合的新单个文件夹中是我想要的结果。感谢您的选择 - 我将尝试这两个示例,并会尽快返回结果。
  • 我可能没有正确使用您的代码。我已经保存它并使用脚本中设置的变量执行并使用参数执行并且没有输出也没有 log.txt
  • log.txt 是在 ISE 的默认路径中创建的。您可以定义路径,例如 c:\temp\log.txt。设置 $testmode=$false 来移动文件。如果 $testmode=$true(默认值)不移动到文件,但会创建 log.txt,其中包含描述文件移动源和目标的消息。试着告诉我结果。
猜你喜欢
  • 1970-01-01
  • 2020-10-07
  • 1970-01-01
  • 2011-01-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多