我有一个目录,其中包含数十万个名称相当复杂的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不支持它。