Как рекурсивно перебрать каталог, чтобы удалить файлы с определенными расширениями

Мне нужно рекурсивно пройти по каталогу и удалить все файлы с расширениями.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

Мне нужна помощь для завершения кода, так как я никуда не денусь.

17 ответов

Решение

find только для этого.

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

В качестве продолжения ответа 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

Без 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 (или zsh без shopt -s globstarили кш с set -o globstar вместо shopt -s globstar). Кроме того, в bash <4.3 это перебирает символические ссылки на каталоги, а также на каталоги, что обычно нежелательно.

Если вы хотите сделать что-то рекурсивно, я предлагаю вам использовать рекурсию (да, вы можете сделать это с помощью стеков и так далее, но эй).

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

(cd /tmp; recursiverm)

Это сказало, find вероятно, лучший выбор, как уже было предложено.

Вот пример использования оболочки (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

Это не отвечает на ваш вопрос напрямую, но вы можете решить вашу проблему с помощью одной строки:

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

Для bash (начиная с версии 4.0):

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

Это все.
Конечное расширение ".ext" там, чтобы выбрать файлы (или каталоги) с этим расширением.

Опция globstar активирует ** (поиск рекурсивно).
Опция nullglob удаляет *, если не соответствует ни файлу / директории.
Опция dotglob включает в себя файлы, которые начинаются с точки (скрытые файлы).

Остерегайтесь, что перед bash 4.3, **/ также проходит символические ссылки на каталоги, что нежелательно.

Этот метод хорошо обрабатывает пробелы.

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
}

Это самый простой из известных мне способов:rm **/@(*.doc|*.pdf)

** делает эту работу рекурсивно

@(*.doc|*.pdf) ищет файл, заканчивающийся на pdf ИЛИ doc

Легко безопасно протестировать, заменив rm с ls

Следующая функция рекурсивно перебирает все каталоги в \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

Там нет причин, чтобы передать вывод find в другую утилиту. find имеет -delete флаг встроен в него.

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

Здесь много ответов, но я был удивлен, что не смог найти этот очень простой:

      rm -v **/*.pdf **/*.doc

Или добавьте-iвариант иrmпредложит вам для каждого файла.

Протестировано на рыбе , хотя должно работать и с большинством других оболочек.

Обновление : Также протестировано в zsh 5.9 .

Другие предоставленные ответы не будут включать файлы или каталоги, начинающиеся с. у меня сработало следующее:

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

Я думаю, что наиболее простым решением является использование рекурсии. В следующем примере я напечатал все имена файлов в каталоге и его подкаталогах.

Вы можете изменить его в соответствии с вашими потребностями.

      #!/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

Он отлично работает и с пробелами!

Примечание: вы можете использовать echo $(basename "$i") # print the file nameчтобы напечатать имя файла без пути.

ИЛИ: Использовать echo ${i%/##*/}; # print the file nameкоторый работает очень быстро, без необходимости вызывать внешний basename.

Следующее будет рекурсивно перебирать данный каталог и перечислять все содержимое:

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

Если вы можете изменить оболочку, используемую для запуска команды, вы можете использовать ZSH для выполнения этой работы.

#!/usr/bin/zsh

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

Это будет рекурсивно перебирать все файлы / папки.

Просто делать

find . -name '*.pdf'|xargs rm
Другие вопросы по тегам