我有数千个文件名为filename.12345.end的文件。我只想保留每个第12个文件,所以file.00012.end,file.00024.end ... file.99996.end并删除其他所有内容。
这些文件的文件名中可能还包含数字,通常采用以下格式: file.00064.name.99999.end
我使用的是Bash shell,无法弄清楚如何遍历文件,然后找出数字并检查是否number%%12=0
删除了文件。谁能帮我?
谢谢多丽娜
我有数千个文件名为filename.12345.end的文件。我只想保留每个第12个文件,所以file.00012.end,file.00024.end ... file.99996.end并删除其他所有内容。
这些文件的文件名中可能还包含数字,通常采用以下格式: file.00064.name.99999.end
我使用的是Bash shell,无法弄清楚如何遍历文件,然后找出数字并检查是否number%%12=0
删除了文件。谁能帮我?
谢谢多丽娜
Answers:
这是一个Perl解决方案。对于数千个文件,这应该更快:
perl -e '@bad=grep{/(\d+)\.end/ && $1 % 12 != 0}@ARGV; unlink @bad' *
可以进一步浓缩为:
perl -e 'unlink grep{/(\d+)\.end/ && $1 % 12 != 0}@ARGV;' *
如果文件太多而不能使用simple *
,则可以执行以下操作:
perl -e 'opendir($d,"."); unlink grep{/(\d+)\.end/ && $1 % 12 != 0} readdir($dir)'
至于速度,这是这种方法与其他答案之一中提供的shell的比较:
$ touch file.{01..64}.name.{00001..01000}.end
$ ls | wc
64000 64000 1472000
$ time for f in ./* ; do file="${f%.*}"; if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then rm "$f"; fi; done
real 2m44.258s
user 0m9.183s
sys 1m7.647s
$ touch file.{01..64}.name.{00001..01000}.end
$ time perl -e 'unlink grep{/(\d+)\.end/ && $1 % 12 != 0}@ARGV;' *
real 0m0.610s
user 0m0.317s
sys 0m0.290s
如您所见,差异很大, 与预期。
-e
是只是告诉perl
运行在命令行上给出的脚本。@ARGV
是一个特殊变量,其中包含提供给脚本的所有参数。由于我们给了它*
,它将包含当前目录中的所有文件(和目录)。该grep
会通过文件名列表中搜索和查找任何匹配的一串数字,点和end
(/(\d+)\.end/)
。
由于数字(\d
)在捕获组(括号)中,因此将它们另存为$1
。所以grep
它将检查该数字是否为12的倍数,如果不是12的倍数,则将返回文件名。换句话说,该数组@bad
保存要删除的文件列表。
然后将列表传递到该列表,unlink()
以删除文件(但不删除目录)。
假设您的文件名采用以下格式 file.00064.name.99999.end
,我们首先需要删除除数字外的所有内容。我们将使用for
循环执行此操作。
我们还需要告诉Bash shell使用以10为底的数字,因为Bash算术会将它们以0开头的数字视为8的底数。
作为脚本,要在包含文件的目录中启动时使用:
#!/bin/bash
for f in ./*
do
if [[ -f "$f" ]]; then
file="${f%.*}"
if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then
rm "$f"
fi
else
echo "$f is not a file, skipping."
fi
done
或者,您可以使用此非常长的丑陋命令来执行相同的操作:
for f in ./* ; do if [[ -f "$f" ]]; then file="${f%.*}"; if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then rm "$f"; fi; else echo "$f is not a file, skipping."; fi; done
要解释所有部分:
for f in ./*
意味着对当前目录中的所有内容都执行...。这会将找到的每个文件或目录设置为变量$ f。if [[ -f "$f" ]]
检查找到的项目是否是文件,如果不是,则跳至 echo "$f is not...
部分,这意味着我们不会意外删除目录。file="${f%.*}"
将$ file变量设置为文件名,修剪掉最后一个之后的内容 .
。if [[ $((10#${file##*.} % 12)) -eq 0 ]]
是主要算术运算符所在的位置。在我们的文件名中${file##*.}
,最后一个字符之前的所有内容都会被修剪.
,不带扩展名。$(( $num % $num2 ))
是Bash算术使用模运算的语法,10#
开始时告诉Bash使用基数10,以处理那些讨厌的前导0。$((10#${file##*.} % 12))
然后将剩下的文件名数字除以12。然后-ne 0
检查是否“不等于”零。rm
命令删除该文件,您可能希望在首次运行该命令时将其替换rm
为echo
,以检查是否已删除了期望的文件。该解决方案是非递归的,这意味着它将仅处理当前目录中的文件,而不会进入任何子目录。
该if
用语句echo
作为命令,警告有关目录是不是真的有必要rm
在它自己会抱怨目录,而不是删除它们,那么:
#!/bin/bash
for f in ./*
do
file="${f%.*}"
if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then
rm "$f"
fi
done
要么
for f in ./* ; do file="${f%.*}"; if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then rm "$f"; fi; done
也将正常工作。
rm
几千次会很慢。我建议echo
,而不是文件名和管道回路的输出xargs rm
(根据需要添加的选项): for f in *; do if ... ; then echo "$f"; fi; done | xargs -rd '\n' -- rm --
。
xargs
了2分钟48秒,版本花费了5分钟1秒。这可能是由于echo
@DavidFoerster的开销造成的吗?
time { for f in *; do echo "$f"; done | xargs rm; }
与vs.1m11.450s / 0m10.695s / 0m16.800s time { for f in *; do rm "$f"; done; }
在tmpfs上。Bash是v4.3.11,内核是v4.4.19。
您可以使用Bash方括号扩展来生成包含第12个数字的名称。让我们创建一些测试数据
$ touch file.{0..9}{0..9}{0..9}{0..9}{0..9}.end # create test data
$ mv file.00024.end file.00024.end.name.99999.end # testing this form of filenames
然后我们可以使用以下内容
$ ls 'file.'{00012..100..12}* # print these with numbers less than 100
file.00012.end file.00036.end file.00060.end file.00084.end
file.00024.end.name.99999.end file.00048.end file.00072.end file.00096.end
$ rm 'file.'{00012..100000..12}* # do the job
但是,对于大量文件而言,工作的速度慢得令人望而却步-生成数千个名称需要花费时间和内存,因此,这是实际有效的解决方案。
简而言之,我认为这个解决方案比其他答案要好得多:
find . -name '*.end' -depth 1 | awk 'NR%12 != 0 {print}' | xargs -n100 rm
一点解释:首先,我们使用生成文件列表find
。我们得到所有名称以结尾的文件.end
且深度为1的文件(也就是说,它们直接位于工作目录中,而不位于任何子文件夹中。如果没有子文件夹,则可以将其忽略)。输出列表将按字母顺序排序。
然后,将该列表通过管道传递到awk
,在其中使用特殊变量NR
(即行号)。通过在文件中打印文件,我们将第12个文件遗漏了NR%12 != 0
。该awk
命令可以缩短为awk 'NR%12'
,因为取模运算符的结果将被解释为布尔值,并且{print}
无论如何都将隐式完成。
因此,现在我们有了一个需要删除的文件列表,我们可以使用xargs和rm进行处理。使用标准输入作为参数xargs
运行给定命令(rm
)。
如果文件很多,则会出现类似“参数列表太长”之类的错误消息(在我的机器上,该限制为256 kB,POSIX要求的最小值为4096字节)。可以通过使用-n 100
标志来避免这种情况,该标志每100个字(不是行,而是要注意的文件名是否包含空格)将rm
参数分割开,并执行一个单独的命令,每个命令仅包含100个参数。
-depth
需要先于-name
; ii)如果任何文件名包含空格,此操作将失败;iii)您假设文件将按升序列出(这就是您awk
要测试的内容),但是几乎可以肯定不是这种情况。因此,这将删除一组随机文件。
-depth
。尽管如此,这是这里最少的问题,最重要的是您要删除的是随机文件集,而不是OP想要的文件集。
-depth
它没有价值,它的作用与您认为的相反。请参阅man find
:“在目录本身之前深度处理每个目录的内容”。因此,这实际上将下降到子目录中,并在各处造成严重破坏。
-depth n
又-maxdepth n
存在。前者要求深度精确为n,而后者则必须为<= n。II)。是的,这很糟糕,但是对于此特定示例,无需担心。您可以使用修复此问题find ... -print0 | awk 'BEGIN {RS="\0"}; NR%12 != 0' | xargs -0 -n100 rm
,它使用空字节作为记录分隔符(文件名中不允许使用)。III)再一次,在这种情况下,假设是合理的。否则,您可以sort -n
在find
和之间插入一个awk
,或重定向find
到一个文件并根据需要对其进行排序。
find
。但是,主要问题还是再次假设您find
返回的是已排序列表。没有。
对于仅使用bash,我的第一种方法是:1.将要保留的所有文件移到另一个目录(即,文件名中的数字为12的倍数的所有文件),然后2.删除目录中的所有剩余文件,然后3.将您保留的12的多个文件放回原处。所以这样的事情可能会起作用:
cd dir_containing_files
mkdir keep_these_files
n=0
while [ "${n}" -lt 99999 ]; do
padded_n="`echo -n "00000${n}" | tail -c 5`"
mv "filename${padded_n}.end" keep_these_files/
n=$[n+12]
done
rm filename*.end
mv keep_these_files/* .
rmdir keep_these_files
filename
如果零件不一致,如何生成零件?