删除除第 12 个文件之外的所有文件

删除除第 12 个文件之外的所有文件

我有几千个 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命令删除文件,您可能需要在第一次运行该命令时将其替换rmecho,以检查是否获得了要删除的预期文件。

此解决方案是非递归的,这意味着它只会处理当前目录中的文件,而不会进入任何子目录。

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 个参数。

相关内容