我有几千个 filename.12345.end 格式的文件。我只想保留每 12 个文件,因此 file.00012.end、file.00024.end ... file.99996.end 并删除其他所有内容。
这些文件的文件名前面也可能带有数字,通常采用以下格式:file.00064.name.99999.end
我使用 Bash shell,但不知道该如何循环遍历文件,然后取出编号并检查是否正在number%%12=0
删除文件(如果没有)。有人能帮帮我吗?
谢谢你,多丽娜
答案1
这是一个 Perl 解决方案。对于数千个文件来说,这应该会快得多:
perl -e '@bad=grep{/(\d+)\.end/ && $1 % 12 != 0}@ARGV; unlink @bad' *
可以进一步浓缩为:
perl -e 'unlink grep{/(\d+)\.end/ && $1 % 12 != 0}@ARGV;' *
如果你有太多文件并且不能使用简单的*
,你可以执行以下操作:
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 的倍数,如果不是,则返回文件名。换句话说,该数组@bad
保存要删除的文件列表。然后将列表传递给
unlink()
删除文件(但不删除目录)。
答案2
假设您的文件名采用 格式file.00064.name.99999.end
,我们首先需要删除除数字之外的所有内容。我们将使用循环for
来完成此操作。
我们还需要告诉 Bash shell 使用十进制,因为 Bash 算术会将以 0 开头的数字视为八进制,这会给我们带来麻烦。
作为脚本,在包含文件的目录中启动时使用:
#!/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##*.}
最后一个字符之前的所有内容,不带扩展名。是 Bash 算术使用模运算的语法,开头的告诉 Bash 使用十进制,以处理那些讨厌的前导 0。然后得到文件名除以 12 的余数。检查余数是否“不等于”零。.
$(( $num % $num2 ))
10#
$((10#${file##*.} % 12))
-ne 0
- 如果余数不等于 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
也能正常工作。
答案3
您可以使用 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
但是对于大量文件来说,它的运行速度非常慢 - 生成数千个名称需要时间和内存 - 所以它更多的是一个技巧,而不是真正有效的解决方案。
答案4
谦虚地讲,我认为这个解决方案比其他答案好得多:
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 个参数。