使用多核上的sed或awk在bash中批量重命名一百万个文件的更快方法

使用多核上的sed或awk在bash中批量重命名一百万个文件的更快方法,bash,performance,batch-rename,Bash,Performance,Batch Rename,我编写了一个bash函数,它将文件名转换为标题大小写,然后将首字母缩写、camera _DSC/_IMG前缀等大写。它还将连词转换为小写 问题是速度很慢。作为一个测试,我在1000个文件上运行了它,用了4M40秒才完成我有100多万个文件要重命名。 该函数从文件中提取一组sed替换,并在初始转换后运行每一个替换以转换所需的大写/小写字母 以下是函数: (我只能使用BSD/OSX bash,所以${a,,}和sed's//\L&/g'对我不起作用…) 等等 这个列表中有很多条目,我需要添加更多条目

我编写了一个bash函数,它将文件名转换为标题大小写,然后将首字母缩写、camera _DSC/_IMG前缀等大写。它还将连词转换为小写

问题是速度很慢。作为一个测试,我在1000个文件上运行了它,用了4M40秒才完成我有100多万个文件要重命名。

该函数从文件中提取一组
sed
替换,并在初始转换后运行每一个替换以转换所需的大写/小写字母

以下是函数: (我只能使用BSD/OSX bash,所以
${a,,}
sed's//\L&/g'
对我不起作用…)

等等

这个列表中有很多条目,我需要添加更多条目才能完成

尝试解决方案以加快速度:

因此,我将输入文件列表拆分为16个数组,并在每个部分上同时执行该函数。这使得它速度更快,因为现在它可以在多个核上运行

现在在我的4核(8线程)机器上,我的速度降到了1m30s

但它的速度还不足以处理100万个文件

以下是多核bash的精彩之处:

####### 16 THREADS:
declare -a array1
declare -a array2
declare -a array3
declare -a array4
declare -a array5
declare -a array6
declare -a array7
declare -a array8
declare -a array9
declare -a array10
declare -a array11
declare -a array12
declare -a array13
declare -a array14
declare -a array15
declare -a array16
total=${#inputfiles[@]}
div1="$(expr $total / 16)"
div2="$(expr $div1 + $div1)"
div3="$(expr $div1 + $div1 + $div1)"
div4="$(expr $div1 + $div1 + $div1 + $div1)"
div5="$(expr $div1 + $div1 + $div1 + $div1 + $div1)"
div6="$(expr $div1 + $div1 + $div1 + $div1 + $div1 + $div1)"
div7="$(expr $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1)"
div8="$(expr $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1)"
div9="$(expr $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1)"
div10="$(expr $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1)"
div11="$(expr $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1)"
div12="$(expr $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1)"
div13="$(expr $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1)"
div14="$(expr $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1)"
div15="$(expr $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1 + $div1)"
array1=("${inputfiles[@]:0:$div1}")
array2=("${inputfiles[@]:$div1:$div1}")
array3=("${inputfiles[@]:$div2:$div1}")
array4=("${inputfiles[@]:$div3:$div1}")
array5=("${inputfiles[@]:$div4:$div1}")
array6=("${inputfiles[@]:$div5:$div1}")
array7=("${inputfiles[@]:$div6:$div1}")
array8=("${inputfiles[@]:$div7:$div1}")
array9=("${inputfiles[@]:$div8:$div1}")
array10=("${inputfiles[@]:$div9:$div1}")
array11=("${inputfiles[@]:$div10:$div1}")
array12=("${inputfiles[@]:$div11:$div1}")
array13=("${inputfiles[@]:$div12:$div1}")
array14=("${inputfiles[@]:$div13:$div1}")
array15=("${inputfiles[@]:$div14:$div1}")
array16=("${inputfiles[@]:$div15}")
function tcmv {
    b="$(basename "$d")"
    pathtofile="$(dirname "$d")"
    mv "$d" "$pathtofile"/"$(tc "$b")"
}
for d in "${array1[@]}"; do tcmv; done &
for d in "${array2[@]}"; do tcmv; done &
for d in "${array3[@]}"; do tcmv; done &
for d in "${array4[@]}"; do tcmv; done &
for d in "${array5[@]}"; do tcmv; done &
for d in "${array6[@]}"; do tcmv; done &
for d in "${array7[@]}"; do tcmv; done &
for d in "${array8[@]}"; do tcmv; done &
for d in "${array9[@]}"; do tcmv; done &
for d in "${array10[@]}"; do tcmv; done &
for d in "${array11[@]}"; do tcmv; done &
for d in "${array12[@]}"; do tcmv; done &
for d in "${array13[@]}"; do tcmv; done &
for d in "${array14[@]}"; do tcmv; done &
for d in "${array15[@]}"; do tcmv; done &
for d in "${array16[@]}"; do tcmv; done &
wait
编辑:调用自定义大小写的转换示例:

20200908_LA_HOLLYWOOD AND HIGHLAND_BUSY STREET_TIME LAPSE_DSC0795.NEF
20200908_LA_Hollywood and Highland_Busy Street_Time Lapse_DSC0795.NEF
         ^^           ^^^                                 ^^^     ^^^

20180706_STADIUM_SFO VS NYC_BASKETBALL MATCH_04B8897_OK TO PUBLISH.JPG
20180706_Stadium_SFO vs NYC_Basketball Match_04B8897_OK to Publish.JPG
                 ^^^ ^^ ^^^                   ^      ^^ ^^         ^^^
所以我的问题是:

有什么方法可以让这更快吗? 怎么做

EDIT2:

根据评论中的建议,我想出了更好的办法:

IFS=$'\r\n' GLOBIGNORE='*' command eval  'capsarray=($(cat capslist.txt))'
cat tmplist | tr [[:upper:]] [[:lower:]] | sed "s^_^_ ^g" | sed "s^/^/ ^g" | awk '{for(i=1;i<=NF;i++){ $i=toupper(substr($i,1,1)) substr($i,2) }}1' > tmplistB
cat tmplist2 | tr [[:upper:]] [[:lower:]] | sed "s^_^_ ^g" | sed "s^/^/ ^g" | awk '{for(i=1;i<=NF;i++){ $i=toupper(substr($i,1,1)) substr($i,2) }}1' > tmplist2B
commandvar="sed"
commandvar2="sed"
for d in "${capsarray[@]}"; do
    commandvar+=" -e "s^$d^g""
    commandvar2+=" -e "s^$d^g""
done
commandvar+=' > tmplistC'
commandvar2+=' > tmplist2C'
cat tmplistB | eval $commandvar
cat tmplist2B | eval $commandvar2
cat tmplistC | sed "s^_ ^_^g" | sed "s^/ ^/^g" > tmplistD
cat tmplist2C | sed "s^_ ^_^g" | sed "s^/ ^/^g" > tmplist2D
IFS=$'\r\n' GLOBIGNORE='*' command eval  'renamedarray=($(cat tmplistD))'
IFS=$'\r\n' GLOBIGNORE='*' command eval  'renameddirarray=($(cat tmplist2D))'

i=0
for d in "${renamedarray[@]}"; do
    b="$(basename "$d")"
    pathtofile="$(dirname "${myarray[$i]}")"
    mv "${myarray[$i]}" "$pathtofile/$b"
    i=$((i+1))
done
IFS=$'\r\n'GLOBIGNORE='*'命令eval'capsarray=($(cat capslist.txt))'
cat tmplist | tr[:upper:][:lower:]| sed“s^ ^ ^ g”| sed“s^/^/^g”| awk'{for(i=1;i tmplistC'
commandvar2+='>tmplist2C'
cat tmplistB |评估$commandvar
cat tmplist2B |评估$commandvar2
cat tmplistC|sed“s^^^ g”| sed“s^/^/^g”>tmplistC
猫tmplist2C|sed“s^^^^ g”| sed“s^/^/^g”>tmplist2D
IFS=$'\r\n'GLOBIGNORE='*'命令eval'renamedarray=($(cat tmplistD))'
IFS=$'\r\n'GLOBIGNORE='*'命令eval'renameddirarray=($(cat tmplist2D))'
i=0
对于“${renamedarray[@]}”中的d;执行以下操作
b=“$(basename“$d”)”
pathtofile=“$(dirname“${myarray[$i]}”)
mv“${myarray[$i]}”“$pathtofile/$b”
i=$((i+1))
完成
现在,它可以对文本文件而不是真实文件进行所有艰苦的工作,并使用
sed-s
(感谢@Mark Setchell nad@thanasisp)

快多了。现在1000个文件的时间缩短到15秒,而无需将阵列拆分为16个线程。重命名一百万个文件只需4个多小时

(它也会先处理文件,然后处理文件夹,以避免在重命名文件夹中的文件之前重命名文件夹)


因此,就我而言,这足以满足我的需要,但我认为我不应该用上面的回答来回答我的问题,因为我觉得这可能会更有效。例如,使用perl和awk解决方案。(不过我仍在努力理解这些…继续谷歌搜索)

您可以停止创建太多进程,例如将
sed thingA | sed thingB
更改为
sed-e thingA-e thingB
。您可以通过将原始名称和新名称放入文件中并通过Perl运行文件来执行所有
mv
命令,这样每次重命名都只是一个库调用,而不是一个全新的进程。实际上,请使用另一种语言e、 Bash为每个命令创建进程它只会创建一个进程,而且速度会快得多。您还可以启动两个实例,如a*、b*、…z*来同时处理多个文件。我建议您集中精力以清晰的方式描述重命名规则,例如
a^a
的含义是什么?目前您作为
capslist.txt
的内容呈现的内容不是一个好的c用于描述替换模式的通信格式。可能还包括重命名前后的一些代表性文件名。此外,如上文所述,如果您的环境中存在
rename
命令,则应使用该命令。Bash不应像您的示例中那样,被用作高级任务管理器。如果如果你调用十个进程来重命名一个文件,它会非常慢。每次函数调用至少生成17个子shell,再加上在
“${capsarray[@]}”
中的每一个额外元素多生成3个子shell。这将花费一百万个文件的时间。与其显示部分
caplist.txt
,还需要解释(映射)您用来修改文件名的规则,然后您可能可以编写一个
awk
脚本,在对
awk
的一次调用中将每个规则应用于每个文件名。这将快几个数量级,并且可能在大约一分钟内处理一百万个文件。@David C.Rankin是的,这是非常低效的。因此这个问题。I添加了一个示例以使规则更加清晰,但正如您所看到的,我需要匹配大量的内容,例如国家、城市等。我不知道
awk
可以在一个脚本中完成这类操作。您介意给我举个示例吗?
20200908_LA_HOLLYWOOD AND HIGHLAND_BUSY STREET_TIME LAPSE_DSC0795.NEF
20200908_LA_Hollywood and Highland_Busy Street_Time Lapse_DSC0795.NEF
         ^^           ^^^                                 ^^^     ^^^

20180706_STADIUM_SFO VS NYC_BASKETBALL MATCH_04B8897_OK TO PUBLISH.JPG
20180706_Stadium_SFO vs NYC_Basketball Match_04B8897_OK to Publish.JPG
                 ^^^ ^^ ^^^                   ^      ^^ ^^         ^^^
IFS=$'\r\n' GLOBIGNORE='*' command eval  'capsarray=($(cat capslist.txt))'
cat tmplist | tr [[:upper:]] [[:lower:]] | sed "s^_^_ ^g" | sed "s^/^/ ^g" | awk '{for(i=1;i<=NF;i++){ $i=toupper(substr($i,1,1)) substr($i,2) }}1' > tmplistB
cat tmplist2 | tr [[:upper:]] [[:lower:]] | sed "s^_^_ ^g" | sed "s^/^/ ^g" | awk '{for(i=1;i<=NF;i++){ $i=toupper(substr($i,1,1)) substr($i,2) }}1' > tmplist2B
commandvar="sed"
commandvar2="sed"
for d in "${capsarray[@]}"; do
    commandvar+=" -e "s^$d^g""
    commandvar2+=" -e "s^$d^g""
done
commandvar+=' > tmplistC'
commandvar2+=' > tmplist2C'
cat tmplistB | eval $commandvar
cat tmplist2B | eval $commandvar2
cat tmplistC | sed "s^_ ^_^g" | sed "s^/ ^/^g" > tmplistD
cat tmplist2C | sed "s^_ ^_^g" | sed "s^/ ^/^g" > tmplist2D
IFS=$'\r\n' GLOBIGNORE='*' command eval  'renamedarray=($(cat tmplistD))'
IFS=$'\r\n' GLOBIGNORE='*' command eval  'renameddirarray=($(cat tmplist2D))'

i=0
for d in "${renamedarray[@]}"; do
    b="$(basename "$d")"
    pathtofile="$(dirname "${myarray[$i]}")"
    mv "${myarray[$i]}" "$pathtofile/$b"
    i=$((i+1))
done