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


157

我需要递归遍历目录,并删除所有扩展名为.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

由于我什么都没得到,我需要帮助来完成代码。


68
我知道在不了解代码的情况下执行代码是一种不好的形式,但是很多人来此站点学习bash脚本。我是通过谷歌搜索“递归bash脚本文件”到达这里的,几乎跑了这些答案之一(只是为了测试递归)而没有意识到它将删除文件。我知道这rm是OP代码的一部分,但实际上与所提出的问题无关。我认为,使用无害的命令(如)来表达答案会更安全echo
基思2016年


1
@Keith有类似的经历,完全同意并更改了标题
idclev 463035818

Answers:


146

find 就是为此而设计的。

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

19
或查找的-delete选项。
Matthew Flaschen

28
一个应该经常使用find ... -print0 | xargs -0 ...,而不是原始查找。xargs以避免包含换行符的文件名出现问题。
Grumbel

7
xargs没有选项的使用几乎总是不好的建议,这也不例外。使用find … -exec代替。
吉尔斯(Gilles)'所以

211

作为mouviciel答案的后续措施,您也可以将此操作作为for循环,而不是使用xargs。我经常觉得xargs很麻烦,特别是如果我需要在每次迭代中做一些更复杂的事情时。

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

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

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

27
如果文件名中有空格,这将无法正常工作(for循环将空格中的find结果分割开)。
trev

3
您如何在空白处进行有效分割?我正在尝试类似的事情,并且我有很多带有空格的目录,这些目录会弄乱这个循环。
基督教徒

3
因为这是一个非常有用的答案?
zenperttu 2014年

1
@Christian使用这样的引号来修复空格拆分:“ $(find ...)”。我已经编辑了詹姆斯的答案以显示。
马修

2
@Matthew您的编辑根本无法解决任何问题:实际上,只有在找到唯一文件的情况下,该命令才起作用。如果文件名中没有空格,制表符等,则此版本至少适用。我回滚到旧版本。注意明智的做法确实可以解决for f in $(find ...)只是不要使用这种方法。
gniourf_gniourf 2014年

67

没有find

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

/tmp/*是dir中的文件,并且/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(或zsh不带shopt -s globstar,或ksh带set -o globstar代替shopt -s globstar)。此外,在bash <4.3中,这会遍历目录和目录的符号链接,这通常是不希望的。


1
即使在OSX上的文件名包含空格的情况下,此方法也对我有效
ideaasylum 2015年

2
值得注意的是,globstar仅在Bash 4.0或更高版本中可用。这不是许多计算机上的默认版本。
Troy Howard'1

1
我认为您不需要指定第一个参数。(至少到今天为止)for f in /tmp/**就足够了。包括/ tmp目录中的文件。
phil294

1
这样会更好吗?for f in /tmp/*.{pdf,doc} tmp/**/*.{,pdf,doc} ; do
Ice-Blaze

1
**是一个不错的扩展,但不能移植到POSIX sh。(这个问题被标记为bash,但要指出的是,与此处的几种解决方案不同,它确实仅适用于Bash。或者,它也可以在其他几种扩展Shell中使用。)
Tripleee

27

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

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

(cd /tmp; recursiverm)

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


15

这是使用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

15

这不会直接回答您的问题,但是您可以使用单线解决问题:

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

7

此方法可以很好地处理空间。

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”
Lopa

1
这并不适用于所有文件名:文件名末尾带有空格,文件名包含换行符,某些文件名包含反斜杠,因此会失败。这些缺陷可以修复,但是整个方法不必要地复杂,因此不值得费心。
吉尔斯(Gillles)“所以-别再作恶了”

3

对于bash(从4.0版开始):

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

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

选项globstar激活**(递归搜索)。
当nullglob与文件/目录不匹配时,它会删除*。
选项dotglob包含以点开头的文件(隐藏文件)。

请注意,在bash 4.3之前,**/还需要遍历目录的符号链接,这是不希望的。


1

以下函数将递归遍历目录中的所有\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

1

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

** 使这项工作递归

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

替换rm为易于安全测试ls


0

没有理由将其输出传递find到另一个实用程序。find有一个-delete内置的标志。

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

0

提供的其他答案将不包括以开头的文件或目录。以下为我工作:

#/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;
}

-1

做就是了

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

4
不,不要这样做。如果文件名带有空格或其他有趣的符号,则此操作会中断。
gniourf_gniourf 2014年

-1

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

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


不,此函数不会递归遍历任何内容。它仅列出子目录的内容。它只是绒毛ls -l /home/ubuntu/*/,所以非常没用。
吉尔(Gilles)'所以

-1

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

#!/usr/bin/zsh

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

这将循环遍历所有文件/文件夹。

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.