如何递归遍历目录以删除具有某些扩展名的文件

Posted

技术标签:

【中文标题】如何递归遍历目录以删除具有某些扩展名的文件【英文标题】:How to loop through a directory recursively to delete files with certain extensions 【发布时间】:2011-06-06 01:11:05 【问题描述】:

我需要递归遍历一个目录并删除所有扩展名为.pdf.doc 的文件。我正在设法递归地遍历一个目录,但没有设法过滤具有上述文件扩展名的文件。

到目前为止我的代码

#/bin/sh

SEARCH_FOLDER="/tmp/*"

for f in $SEARCH_FOLDER
do
    if [ -d "$f" ]
    then
        for ff in $f/*
        do      
            echo "Processing $ff"
        done
    else
        echo "Processing file $f"
    fi
done

我需要帮助来完成代码,因为我无处可去。

【问题讨论】:

我知道在不理解代码的情况下执行代码是不好的形式,但是很多人来这个站点学习 bash 脚本。我通过谷歌搜索“bash 脚本文件递归”到达这里,几乎 运行了这些答案之一(只是为了测试递归),却没有意识到它会删除文件。我知道rm 是 OP 代码的一部分,但它实际上与所提出的问题无关。我认为如果使用像echo 这样的无害命令来表达答案会更安全。 类似问题:***.com/questions/41799938/… @Keith 也有类似经历,完全同意并改了标题 警告像我这样的菜鸟,浪费时间:在大多数答案中,您需要更改“/tmp/”目录的位置,例如:“/home/my 文件夹"。 【参考方案1】:

做事

find . -name '*.pdf'|xargs rm

【讨论】:

不,不要这样做。如果您的文件名带有空格或其他有趣的符号,则会中断。【参考方案2】:

这并不能直接回答您的问题,但您可以用一条线解决您的问题:

find /tmp \( -name "*.pdf" -o -name "*.doc" \) -type f -exec rm  +

某些版本的 find(GNU、BSD)有一个 -delete 操作,您可以使用它来代替调用 rm

find /tmp \( -name "*.pdf" -o -name "*.doc" \) -type f -delete

【讨论】:

【参考方案3】:

find 就是为此而生的。

find /tmp -name '*.pdf' -or -name '*.doc' | xargs rm

【讨论】:

或者找到-delete选项。 应该始终使用find ... -print0 | xargs -0 ...,而不是原始查找 | xargs 以避免包含换行符的文件名出现问题。 在没有选项的情况下使用xargs 几乎总是不好的建议,这也不例外。请改用find … -exec @Gilles'SO-stopbeingevil':为什么这是个坏建议? @CarlWinbäck 因为xargs 的输入语法不是find(或任何其他常用命令)打印的语法。 xargs 需要一种特殊类型的引号分隔输入。【参考方案4】:

如果你想递归地做某事,我建议你使用递归(是的,你可以使用堆栈等等,但是嘿)。

recursiverm() 
  for d in *; do
    if [ -d "$d" ]; then
      (cd -- "$d" && recursiverm)
    fi
    rm -f *.pdf
    rm -f *.doc
  done


(cd /tmp; recursiverm)

也就是说,find 可能是已经建议的更好选择。

【讨论】:

【参考方案5】:

作为 mouviciel 回答的后续行动,您也可以将其作为 for 循环执行,而不是使用 xargs。我经常发现 xargs 很麻烦,尤其是当我需要在每次迭代中做一些更复杂的事情时。

for f in $(find /tmp -name '*.pdf' -or -name '*.doc'); do rm $f; done

正如许多人评论的那样,如果文件名中有空格,这将失败。您可以通过临时将 IFS(内部字段分隔符)设置为换行符来解决此问题。如果文件名中有通配符\[?*,这也会失败。您可以通过暂时禁用通配符扩展(通配符)来解决此问题。

IFS=$'\n'; set -f
for f in $(find /tmp -name '*.pdf' -or -name '*.doc'); do rm "$f"; done
unset IFS; set +f

如果你的文件名中有换行符,那也行不通。您最好使用基于 xargs 的解决方案:

find /tmp \( -name '*.pdf' -or -name '*.doc' \) -print0 | xargs -0 rm

(此处需要转义括号以使 -print0 适用于两个 or 子句。)

GNU 和 *BSD find 也有一个 -delete 操作,看起来像这样:

find /tmp \( -name '*.pdf' -or -name '*.doc' \) -delete

【讨论】:

如果文件名中有空格(for 循环将 find 的结果拆分为空格),这将无法按预期工作。 如何避免在空格上分割?我正在尝试类似的事情,并且我有很多带有空格的目录会破坏这个循环。 因为这是一个非常有用的答案? @Christian 使用如下引号修复空格分割:“$(find...)”。我已经编辑了詹姆斯的答案以显示。 @Matthew 您的编辑根本没有解决任何问题:它实际上使命令只有在找到唯一的文件时才起作用。如果文件名中没有空格、制表符等,至少这个版本有效。我回滚到旧版本。注意明智可以真正解决for f in $(find ...)只是不要使用这种方法。【参考方案6】:

这个方法可以很好地处理空格。

files="$(find -L "$dir" -type f)"
echo "Count: $(echo -n "$files" | wc -l)"
echo "$files" | while read file; do
  echo "$file"
done

编辑,逐个修复

function count() 
    files="$(find -L "$1" -type f)";
    if [[ "$files" == "" ]]; then
        echo "No files";
        return 0;
    fi
    file_count=$(echo "$files" | wc -l)
    echo "Count: $file_count"
    echo "$files" | while read file; do
        echo "$file"
    done

【讨论】:

我认为不需要回显后的“-n”标志。只需自己测试一下:使用“-n”,您的脚本会给出错误数量的文件。对于目录中的一个文件,它会输出 "Count: 0" 这不适用于所有文件名:名称末尾有空格、文件名包含换行符和某些文件名包含反斜杠会失败。这些缺陷可以修复,但整个方法过于复杂,因此不值得费心。【参考方案7】:

没有find

for f in /tmp/* tmp/**/* ; do
  ...
done;

/tmp/* 是目录中的文件,/tmp/**/* 是子文件夹中的文件。您可能必须启用 globstar 选项 (shopt -s globstar)。 所以对于这个问题,代码应该是这样的:

shopt -s globstar
for f in /tmp/*.pdf /tmp/*.doc tmp/**/*.pdf tmp/**/*.doc ; do
  rm "$f"
done

请注意,这需要 bash ≥4.0(或不带 shopt -s globstar 的 zsh,或带 set -o globstar 而不是 shopt -s globstar 的 ksh)。此外,在 bash

【讨论】:

这个方法对我有用,即使文件名在 OSX 上包含空格 值得注意的是,globstar 仅在 Bash 4.0 或更高版本中可用。这不是许多机器上的默认版本。 我认为您不需要指定第一个参数。 (至少从今天开始)for f in /tmp/** 就足够了。包含来自 /tmp 目录的文件。 这样不是更好吗? for f in /tmp/*.pdf,doc tmp/**/*.,pdf,doc ; do ** 是一个不错的扩展,但不能移植到 POSIX sh。 (这个问题被标记为 bash 但很高兴指出,与这里的几个解决方案不同,这确实是 Bash-only。或者,它也适用于其他几个扩展 shell。)【参考方案8】:

以下将递归遍历给定目录并列出所有内容:

for d in /home/ubuntu/*; 
do 
    echo "listing contents of dir: $d"; 
    ls -l $d/; 
done

【讨论】:

不,这个函数不会递归地遍历任何东西。它只列出子目录的内容。这只是ls -l /home/ubuntu/*/ 周围的绒毛,所以它毫无用处。【参考方案9】:

这是一个使用 shell (bash) 的示例:

#!/bin/bash

# loop & print a folder recusively,
print_folder_recurse() 
    for i in "$1"/*;do
        if [ -d "$i" ];then
            echo "dir: $i"
            print_folder_recurse "$i"
        elif [ -f "$i" ]; then
            echo "file: $i"
        fi
    done



# try get path from param
path=""
if [ -d "$1" ]; then
    path=$1;
else
    path="/tmp"
fi

echo "base path: $path"
print_folder_recurse $path

【讨论】:

【参考方案10】:

对于 bash(自 4.0 版起):

shopt -s globstar nullglob dotglob
echo **/*".ext"

就是这样。 尾随扩展名“.ext”用于选择具有该扩展名的文件(或目录)。

选项 globstar 激活 **(递归搜索)。 选项 nullglob 在不匹配任何文件/目录时删除 *。 选项 dotglob 包括以点开头的文件(隐藏文件)。

请注意,在 bash 4.3 之前,**/ 还会遍历到不可取的目录的符号链接。

【讨论】:

【参考方案11】:

以下函数将递归遍历\home\ubuntu 目录中的所有目录(ubuntu 下的整个目录结构),并在else 块中应用必要的检查。

function check 
        for file in $1/*      
        do
        if [ -d "$file" ]
        then
                check $file                          
        else
               ##check for the file
               if [ $(head -c 4 "$file") = "%PDF" ]; then
                         rm -r $file
               fi
        fi
        done     

domain=/home/ubuntu
check $domain

【讨论】:

【参考方案12】:

没有理由将find 的输出通过管道传输到另一个实用程序。 find 内置了 -delete 标志。

find /tmp -name '*.pdf' -or -name '*.doc' -delete

【讨论】:

【参考方案13】:

提供的其他答案不包括以 .以下对我有用:

#/bin/sh
getAll()

  local fl1="$1"/*;
  local fl2="$1"/.[!.]*; 
  local fl3="$1"/..?*;
  for inpath in "$1"/* "$1"/.[!.]* "$1"/..?*; do
    if [ "$inpath" != "$fl1" -a "$inpath" != "$fl2" -a "$inpath" != "$fl3" ]; then 
      stat --printf="%F\0%n\0\n" -- "$inpath";
      if [ -d "$inpath" ]; then
        getAll "$inpath"
      #elif [ -f $inpath ]; then
      fi;
    fi;
  done;

【讨论】:

【参考方案14】:

如果您可以更改用于运行命令的 shell,则可以使用 ZSH 来完成这项工作。

#!/usr/bin/zsh

for file in /tmp/**/*
do
    echo $file
done

这将递归遍历所有文件/文件夹。

【讨论】:

【参考方案15】:

这是我知道的最简单的方法: rm **/@(*.doc|*.pdf)

** 以递归方式完成这项工作

@(*.doc|*.pdf) 查找以 pdf OR doc 结尾的文件

通过将rm 替换为ls 可以轻松安全地进行测试

【讨论】:

【参考方案16】:

我认为最直接的解决方案是使用递归,在下面的示例中,我打印了目录及其子目录中的所有文件名。

您可以根据自己的需要进行修改。

#!/bin/bash    
printAll() 
    for i in "$1"/*;do # for all in the root 
        if [ -f "$i" ]; then # if a file exists
            echo "$i" # print the file name
        elif [ -d "$i" ];then # if a directroy exists
            printAll "$i" # call printAll inside it (recursion)
        fi
    done 

printAll $1 # e.g.: ./printAll.sh .

输出:

> ./printAll.sh .
./demoDir/4
./demoDir/mo st/1
./demoDir/m2/1557/5
./demoDir/Me/nna/7
./TEST

它也适用于空格!

Note: 您可以使用echo $(basename "$i") # print the file name 打印不带路径的文件名。

OR:使用运行速度极快的echo $i%/##*/; # print the file name,无需调用外部basename

【讨论】:

以上是关于如何递归遍历目录以删除具有某些扩展名的文件的主要内容,如果未能解决你的问题,请参考以下文章

递归遍历tar文件,提取指定扩展名的所有文件

python 递归遍历目录下的文件,以处理和映射到相同的目录结构。递归遍历文件,获取相同目录结构的目标路径。

java 遍历一个目录,统计目录及其子目录中各种类型文件的数目,统计完成后,打印出各种扩展名及其数目。急

用C语言编出遍历出某个目录以及其子目录下所有以TXT为扩展名的文本文件

java 如何递归遍历多重目录下的指定格式文件复制到目标目录并改格式

如何递归遍历目录,通过 node.js 中的套接字发送所有文件名?