Bash mv命令w/复杂文件名



我有一个目录,其中包含数十万个名称相当复杂的PDF文件。我需要能够将一些(不是所有文件)从它们所在的目录移动到另一个目录。下面是我的.sh脚本的一个例子:

#!/bin/bash
/usr/bin/echo "Moving subset 300-399"
# 300-399
/usr/bin/mv *-*-*-3[0-9][0-9]-*-*-*-*.pdf ../destination_folder/
/usr/bin/echo "Moving subset 450-499"
# 450-499
/usr/bin/mv *-*-*-4[5-9][0-9]-*-*-*-*.pdf ../destination_folder/
/usr/bin/echo "Moving subset 500-599"
# 500-599
/usr/bin/mv *-*-*-5[0-9][0-9]-*-*-*-*.pdf ../destination_folder/

因为有太多的文件,我认为mv正在对每一个文件进行评估,所以执行这项工作需要两个多小时。这是一个必须每天运行的脚本,所以我需要找到一种更有效的方法来完成这项工作。有没有一个更有效的命令可以在Windows环境中使用,或者有没有一种更有效的方法可以评估每个文件,以加快mv进程?

如注释中所述,powershell可能会更快,因为它是windows的原生版本。速度的差异将取决于您正在使用的bash的实现。

对于纯bash解决方案,您可以尝试:

#!/bin/bash
find /input/folder -regextype posix-extended -regex '^(?:[^-]+-){3}(?:4[5-9]|[35][0-9])[0-9](?:-[^-]+){4}.pdf$' -exec mv {} /destination/folder +

解释:

  • find /input/folder -regextype posix-extended -regex
    • 在输入文件夹中查找与正则表达式匹配的每个文件
  • '^(?:[^-]+-){3}(?:4[5-9]|[35][0-9])[0-9](?:-[^-]+){4}.pdf$'
    • 与文件匹配的模式。此处提供更多解释
  • -exec mv {} /destination/folder +
    • 对找到的每个文件执行mv命令
    • +符号表示,当find命令发现与正则表达式匹配的每个文件时,该命令将在尽可能少的调用中执行

值得一提的是,这些mv命令的持续时间当然取决于数据量:当前目录中pdf文件的总大小。

请注意,根据../destination_folder/目录的位置,mv命令至少有2种不同的行为,具有不同的性能:

  • 不同文件系统上的../destination_folder/*.pdf文件:mv命令是复制这些文件,然后将它们从源目录中删除
  • ../destination_folder/*.pdf文件在同一个文件系统上:只需重命名即可,速度极快

df命令可用于非常自然地显示../destination_folder/目录。


如果您可以选择目标目录,那么请确保它位于同一个文件系统上:预计会有很大的改进。


此外,如果../destination_folder/目录位于远程服务器上,则持续时间也取决于网络速度。如果是这种情况,那么应该测试在移动时压缩/解压缩文件:性能会更好。

如果您在Windows上有bash,您可以在后台使用&后缀运行每一个,并尝试将其并行化以获得更好的性能。使用wait关键字等待后台进程完成。例如:

/usr/bin/echo "Moving subset 300-399"
/usr/bin/mv *-*-*-3[0-9][0-9]-*-*-*-*.pdf ../destination_folder/ & # Run this line in the background
# Other async calls
# Wait for background processes to finish
wait

如果需要PowerShell,可以使用Start-Job在后台运行这些程序。以您的300个子集为例:

Write-Host "Moving subset 300-399"
$mv300jb = Start-Job {
$sourceFiles = Get-ChildItem -File .*-*-*-3*-*-*-*-*.pdf | Where-Object {
$_.FullName -match '\(w+-){3}3[0-9]{2}(-w+){4}.pdf$'
}
Move-Item -Path $sourceFiles "..destination_folder"
}
# Here you would also start other async jobs, assigning $mv400, $mv500, etc. like above
...
# Wait for job to complete
while( $mv300.State -notin 'Completed', 'Failed' ) {
Start-Sleep 30 # Change this to number of seconds to poll job again
}

荣誉奖

在Windows上的第二种选择是使用robocopy.exe,它可以比标准的复制和移动命令更高效地复制和移动文件。/mt参数将使用多线程。不幸的是,我在这里没有任何robocopy的例子可以分享。


解释正则表达式

注意:从那以后,我了解到可以将基本字符范围与Get-ChildItem和其他一些支持globbing的PowerShell cmdlet一起使用。有关更多信息,请参阅我在这个答案底部的编辑。

既然被问到了,下面是我用来在文件名上匹配的.NET正则表达式的分解:

\(w+-){3}3[0-9]{2}(-w+){4}.pdf$

  • \:文字字符
  • (w+-):查找一个或多个w单词字符后面跟一个-的组
    • {3}:在前一组中正好出现3次时匹配的量词
  • 3[0-9]:查找后面跟着数字字符的文字3
    • {2}:在前两个数字字符上匹配的量化器
  • (-w+):查找一个或多个-字符后跟至少一个单词字符w的组。
    • {4}:与前一组中出现的4次完全匹配的量词
  • .pdf:后面跟着pdf的文字.字符
  • $:输入结束/string

在撰写本文时,我不知道字符范围可以与Get-ChildItem中的globbing一起使用,所以我使用正则表达式来查找第4个字段中与特定数字模式匹配的字段的确切数量,同时确保找到的任何文件的8字段文件名都是完整的。

如果将此表达式插入https://regexr.com,它会分解表达式,并比我在这里更直观地解释一切,而不会让这个答案太长。


编辑

正如我前几天了解到的,您可以将字符范围与PowerShell的文件匹配一起使用,尽管这在Windows中的其他上下文中不起作用。在我上面的例子中,可以修改以下行以匹配字母和数字范围,而不必使用regex。如果您从上面获取以下代码:

$sourceFiles = Get-ChildItem -File .*-*-*-3*-*-*-*-*.pdf | Where-Object {
$_.FullName -match '\(w+-){3}3[0-9]{2}(-w+){4}.pdf$'
}

我们可以使用globbing来匹配文件名,而不必使用Where-Object或正则表达式,大大降低了这个位的复杂性:

$sourceFiles = Get-ChildItem -File .*-*-*-3[0-9][0-9]*-*-*-*-*.pdf

以下是为了避开正则表达式而使用globbing而修改的代码:

Write-Host "Moving subset 300-399"
$mv300jb = Start-Job {
$sourceFiles = Get-ChildItem -File .*-*-*-3*-*-*-*-*.pdf
Move-Item -Path $sourceFiles "..destination_folder"
}
# Here you would also start other async jobs, assigning $mv400, $mv500, etc. like above
...
# Wait for job to complete
while( $mv300.State -notin 'Completed', 'Failed' ) {
Start-Sleep 30 # Change this to number of seconds to poll job again
}

此功能的可用性似乎取决于PowerShell构造是否正在执行globbing(有效),或者它是否是Win32 API的本机构造(无效)。换句话说,PowerShell似乎支持它,但其他Windows API不支持它。

最新更新