从名称中包含特定字符串的目录树中删除文件的最快方法

时间:2017-04-14 13:47:44

标签: linux bash perl shell delete-file

我有一个包含子目录的目录,我想从中删除名称中包含out的所有文件。这样做的最快方法是什么?

我尝试过几件事。

简单:

rm */*out*

的Perl:

perl -e 'for ( <*/*out*> ) { ( (stat)[9] < (unlink) ) }'

每一项似乎都需要花费大量时间。对于1,000个子目录,每个子目录包含大约50个与*out*匹配的文件,需要:

Perl:        ~25 mins
rm */*out* : ~18 mins

我还尝试rsync,首先将文件移动到文件夹然后与删除同步,但这需要很长时间。

有没有人有更快的方法摆脱这些文件,因为这对我来说似乎过于缓慢?

5 个答案:

答案 0 :(得分:4)

我发现test3是最快的(11-25秒)。但为什么不亲自测试呢?

您的文件系统会对性能产生重大影响。

测试使用GNU Parallel

# Make test set: 150000 files, 50000 named *.seq
testset() {
  doit() { mkdir -p $1 ; cd $1 && parallel --results ./{} seq ::: {1..50}; }
  export -f doit
  seq 1000 | parallel --bar doit >/dev/null

  # Drop caches before starting a test
  echo 3 | sudo tee /proc/sys/vm/drop_caches >/dev/null
}
export -f testset

# Define tests
test1() {
  find . -name '*seq' | perl -ne 'chop;unlink'
}
export -f test1
test2() {
  find . -name '*seq' -delete
}
export -f test2
test3() {
  find . -name '*seq' | parallel --pipe -N1000 -q perl -ne 'chop;unlink'
}
export -f test3
test4() {
  find . -name '*seq' -print0 | xargs -0 -P2 rm
}
export -f test4
test5() {
  find . -name '*seq' -print0 | xargs -0 rm
}
export -f test5
test6() {
  find . -name '*seq' | perl -e 'chomp(@a=<>);unlink @a'
}
export -f test6
test7() {
  # sort by inode
  ls -U -i */*seq* | sort -k1,1 -n| cut -d' ' -f2- | perl -e 'chomp(@a=<>);unlink @a'
}
export -f test7

# Run testset/test? alternating
eval parallel --joblog jl -uj1 ::: testset' 'test{1..7} 
# sort by runtime
sort -nk4 jl

答案 1 :(得分:1)

我曾经遇到过一个类似的问题,一个工具变得狂暴,并在一段时间后留下了400.000个临时文件。 我使用rm *find . -name ... -exec rm {} +和一些Perl解决方案进行了很多实验。

令我惊讶的是,这是迄今为止最快的方法:

  • 确定Perl中的文件名并将其存储在列表中。
  • 致电unlink @list

示例:

if ( my $dh = IO::Dir->new($dir) ) {
    my @files_to_delete = ();
    while ( my $file = $dh->read() ) {
        $file = "$dir/$file";
        if ( -f $file ) {
            push @files_to_delete, $file;
        }
    }
    $dh->close();

    my $deleted = unlink @files_to_delete;
    print "deleted $deleted files\n";
}

还有其他方法可以确定@files_to_delete(例如glob等),但关键点是unlink @files_to_delete步骤。 立即使用尽可能多的文件调用unlink。文件数量似乎没有限制(内存除外)。

这实际上让我感到惊讶,因为我rm *(或其等价物)会比Perl unlink更快 - 但它不是。

答案 2 :(得分:1)

你的命令似乎确实执行得异常缓慢。

至于您尝试的内容

  • rm */*out*

    • 适用于少量匹配文件。
    • 减慢了大量文件的速度,更重要的是,会因超出最大值而冒破坏的风险。命令行长度,由getconf ARG_MAX报告,因为模式(glob)*/*out*由shell 预先扩展,以及生成的文件名列表会立即传递给外部实用程序rm
  • 我假设您意味着使用的perl命令是perl -e 'unlink <*/*out*>'

    • 在我的测试中,它的表现优于rm */*out*,但YMMV(见下文);它确实具有不受getconf ARG_MAX限制的明显优势;但是,随着输入集变大,它确实会变慢 - 见下文。
  • 使用并行执行可以帮助

    • GNU和BSD / macOS xargs具有非标准-P <n>选项,允许并行运行指定命令的大多数<n>个实例; GNU xargs支持0 <n>,这意味着&#34; xargs将运行尽可能多的进程&#34;,根据{{1}页面;虽然细节不清楚,并且它也说明了,&#34;使用man选项或-n选项-L;否则很可能只有一名执行官会完成&#34;在实践中它似乎确实有所作为。

        事实证明
      • -P是我测试中最快的 - YMMV。
    • GNU Parallel - 通常预装 - 值得考虑一般的并行执行的复杂控制,尽管可能在这种特殊情况下没有帮助;它是一个强大的工具,但不是灵丹妙药 - YMMV。

下面是一个find . -name '*out*' -print0 | xargs -0 -P0 rm脚本,用于统计各种命令;默认情况下,它使用以下参数:

  • 1000个子文件夹
  • 要在每个子文件夹中删除的50个文件
  • 另外100个文件以及每个文件夹中要删除的文件。

脚本自行清理并且没有bash以外的先决条件(例如,您也可以在macOS上运行它);那些假设存在GNU bash的测试如果不存在则会失败 您可以通过从命令行传递参数或修改默认值,以及通过添加/删除命令来轻松调整脚本。

以下是来自在macOS 10.12.4主机上运行Ubuntu 16.04的双核VMware Fusion VM的 示例时序,这是2012年底 - 4月4日配备Fusion Drive(混合SSD + HHD)的核心Intel i5 3.2 Ghz机器。

再次:YMMV - 有很多因素在起作用,例如文件系统,硬盘驱动器,CPU核心数,系统负载......

parallel

find . -name '*out*' -print0 | xargs -0 -P0 rm 0.704 find . -name '*out*' -print0 | xargs -0 -P2 rm 0.745 find . -name '*out*' -print0 | xargs -0 rm 1.067 find . -name '*out*' | perl -e 'chomp(@a=<>); unlink @a' 1.070 perl -e 'unlink <*/*out*>' 1.089 find . -name '*out*' -delete 1.094 find . -mindepth 2 -maxdepth 2 -name '*out*' -delete 1.110 rm */*out* 1.369 find . -name '*out*' | parallel --pipe -n 1000 perl -nle 'unlink' 1.460 find . -name '*out*' | parallel --pipe -n 1000 -q perl -e 'chomp(@a=<>); unlink @a' 1.493 find . -name '*out*' | parallel --xargs rm 2.294 find . -name '*out*' | parallel -n 1000 rm 2.465 find结合起来似乎是胜利者,即使没有并行调用的xargs -P0也很快。

也许令人惊讶的是,单一实用程序解决方案(xargsrm最快,随着输入集的大小增加,并行解决方案变得相对更快

使用GNU perl在这里没有帮助,但它可能包含更大的数字。

这是测试脚本的源代码,其唯一先决条件是Bash v3 +;缺少/不同的GNU parallel和/或超出限制的parallel命令行的大小将在结果中单独注明。

rm

答案 3 :(得分:0)

使用单个rm删除所有文件(使用find):

find /path/to/dir -name "*out*" -exec rm {} +

答案 4 :(得分:-1)

怎么样:

find . -mindepth 2 -maxdepth 2 -name '*out*' -delete

使用find . -mindepth 2 -maxdepth 2,您可以获得.内深度为2的所有元素,这意味着当前目录(.)的子目录中的每个文件。在这种情况下,“文件”可以是任何内容:常规文件,目录,管道等。如果您只想删除常规文件,可以添加-type f选项:

find . -mindepth 2 -maxdepth 2 -name '*out*' -type f -delete

使用-name '*out*',您将获得名称包含out的所有文件。

-delete告诉find删除找到的每个文件。请注意,-delete的所有版本均不支持find,但您的find很可能已经支持{{1}}。