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


14

我有数千个文件名为filename.12345.end的文件。我只想保留每个第12个文件,所以file.00012.end,file.00024.end ... file.99996.end并删除其他所有内容。

这些文件的文件名中可能还包含数字,通常采用以下格式: file.00064.name.99999.end

我使用的是Bash shell,无法弄清楚如何遍历文件,然后找出数字并检查是否number%%12=0 删除了文件。谁能帮我?

谢谢多丽娜


文件号仅取决于文件名吗?
Arronical

另外,文件是否总是有5位数字,并且后缀和前缀是否始终相同?
Arronical

是的,它始终是5位数字。我不确定我是否正确回答了您的第一个问题。用不同的文件名的文件是不同的,我需要它正好有号码00012,00024等,这些特定的文件
Dorina

3
@Dorina,请编辑您的问题并明确说明。它改变了一切!
terdon

2
它们都在同一个目录中,对吗?
Sergiy Kolodyazhnyy

Answers:


18

这是一个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()以删除文件(但不删除目录)。


12

假设您的文件名采用以下格式 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检查是否“不等于”零。
  • 如果余数不等于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

也将正常工作。


5
拨打rm几千次会很慢。我建议echo,而不是文件名和管道回路的输出xargs rm(根据需要添加的选项): for f in *; do if ... ; then echo "$f"; fi; done | xargs -rd '\n' -- rm --
David Foerster

我进行了编辑,以包括您建议的速度改进。
Arronical

实际上,在对包含55999个文件的目录进行测试之后,原始版本花费xargs了2分钟48秒,版本花费了5分钟1秒。这可能是由于echo@DavidFoerster的开销造成的吗?
Arronical

奇。对于60.000个文件,我得到0m0.659s / 0m0.545s / 0m0.380s(real / user / sys)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。
大卫·佛斯特

6

您可以使用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

但是,对于大量文件而言,工作的速度慢得令人望而却步-生成数千个名称需要花费时间和内存,因此,这是实际有效的解决方案。


我喜欢这一代码。
David Foerster

1

有点长,但这是我想到的。

 for num in $(seq 1 1 11) ; do
     for sequence in $(seq -f %05g $num 12 99999) ; do
         rm file.$sequence.end.99999;
     done
 done

说明:每十二个文件删除11次。


0

简而言之,我认为这个解决方案比其他答案要好得多:

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


3
您的方法有两个问题: -depth需要先于-name; ii)如果任何文件名包含空格,此操作将失败;iii)您假设文件将按升序列出(这就是您awk要测试的内容),但是几乎可以肯定不是这种情况。因此,这将删除一组随机文件。
terdon

天哪!您说得对,我很糟糕(评论已编辑)。由于错误的放置位置我弄错了,不记得-depth。尽管如此,这是这里最少的问题,最重要的是您要删除的是随机文件集,而不是OP想要的文件集。
terdon

哦,不,-depth它没有价值,它的作用与您认为的相反。请参阅man find:“在目录本身之前深度处理每个目录的内容”。因此,这实际上将下降到子目录中,并在各处造成严重破坏。
terdon

我)既存在-depth n-maxdepth n存在。前者要求深度精确为n,而后者则必须为<= n。II)。是的,这很糟糕,但是对于此特定示例,无需担心。您可以使用修复此问题find ... -print0 | awk 'BEGIN {RS="\0"}; NR%12 != 0' | xargs -0 -n100 rm,它使用空字节作为记录分隔符(文件名中不允许使用)。III)再一次,在这种情况下,假设是合理的。否则,您可以sort -nfind和之间插入一个awk,或重定向find到一个文件并根据需要对其进行排序。
user593851 '16

3
嗯,那您可能正在使用OSX。那是的非常不同的实现find。但是,主要问题还是再次假设您find返回的是已排序列表。没有。
terdon's

0

对于仅使用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如果零件不一致,如何生成零件?
Arronical '16
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.