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

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

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

Elitmiar
источник
68
Я знаю, что плохо выполнять код, не понимая его, но многие люди приходят на этот сайт, чтобы изучить скрипты bash. Я попал сюда, прибегая к помощи «рекурсивных файлов сценариев bash», и почти запустил один из этих ответов (просто для проверки рекурсии), даже не подозревая, что он удалит файлы. Я знаю, rmчто это часть кода OP, но на самом деле это не имеет отношения к заданному вопросу. Я думаю, что было бы безопаснее, если бы ответы были сформулированы с использованием такой безобидной команды, как echo.
Кит
Подобный вопрос здесь: stackoverflow.com/questions/41799938/…
codeforester
1
@Keith имел подобный опыт, полностью согласен и изменил название
idclev 463035818

Ответы:

146

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

find /tmp -name '*.pdf' -or -name '*.doc' | xargs rm
mouviciel
источник
19
Или найти -deleteвариант.
Мэтью Флэшен
28
Нужно всегда использовать find ... -print0 | xargs -0 ..., а не сырье найти | xargs, чтобы избежать проблем с именами файлов, содержащими символы новой строки.
Грамбель
7
Использование xargsбез опций - почти всегда плохой совет, и это не исключение. Используйте find … -execвместо этого.
Жиль "ТАК - перестань быть злым"
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 разбивает результаты поиска на пробельные символы).
Трев
3
Как избежать расщепления пробелов? Я пытаюсь сделать то же самое, и у меня есть много каталогов с пробелами, которые запутывают этот цикл.
Кристиан
3
потому что это очень полезный ответ?
zenperttu
1
@Christian Исправьте разделение пробелов с помощью таких кавычек: "$ (find ...)". Я отредактировал ответ Джеймса, чтобы показать.
Мэтью
2
@ Математическое редактирование ничего не исправило: команда фактически работала только при наличии уникального найденного файла . По крайней мере, эта версия работает, если в именах файлов нет пробелов, табуляции и т. Д. Я откатился на старую версию. Отмечая разумное, можно починить for f in $(find ...). Просто не используйте этот метод.
gniourf_gniourf
67

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

Томек
источник
1
Этот метод работал для меня, даже с именами файлов, содержащими пробелы в OSX
ideasasylum
2
Стоит отметить, что globstar доступен только в Bash 4.0 или новее. Это не версия по умолчанию на многих машинах.
Трой Ховард
1
Я не думаю, что вам нужно указать первый аргумент. (По крайней мере, на сегодня) for f in /tmp/**будет достаточно. Включает файлы из / tmp dir.
phil294
1
Не было бы лучше, как это? for f in /tmp/*.{pdf,doc} tmp/**/*.{,pdf,doc} ; do
Ice-Blaze
1
**хорошее расширение, но не переносимое в POSIX sh. (Этот вопрос помечен как bash, но было бы неплохо отметить, что в отличие от нескольких решений здесь, это действительно только Bash. Или, ну, это работает и в некоторых других расширенных оболочках.)
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вероятно, это лучший выбор, как уже было предложено.

falstro
источник
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
}
TJR
источник
Я думаю, что флаг "-n" после эха не нужен. Просто проверьте сами: с помощью «-n» ваш скрипт выдает неверное количество файлов. Для ровно один файл в каталоге он выводит «Count: 0»
LOPA
1
Это не работает со всеми именами файлов: оно завершается с пробелами в конце имени, с именами файлов, содержащими символы новой строки, и с некоторыми именами файлов, содержащими обратную косую черту. Эти дефекты можно исправить, но весь подход излишне сложен, поэтому его не стоит беспокоить.
Жиль "ТАК - перестань быть злым"
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
K_3
источник
1

Это самый простой способ, которым я знаю, чтобы сделать это: rm **/@(*.doc|*.pdf)

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

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

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

ecotechie
источник
0

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

find /tmp -name '*.pdf' -or -name '*.doc' -delete
Zak
источник
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;
}
TrevTheDev
источник
-1

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

find . -name '*.pdf'|xargs rm
Navi
источник
4
Нет, не делай этого. Это ломается, если у вас есть имена файлов с пробелами или другими забавными символами.
gniourf_gniourf
-1

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

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

СК Венкат
источник
Нет, эта функция не проходит ничего рекурсивно. Это только перечисляет содержание подкаталогов. Это просто ls -l /home/ubuntu/*/бесполезно, поэтому бесполезно.
Жиль "ТАК - перестань быть злым"
-1

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

#!/usr/bin/zsh

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

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

Амин НАИРИ
источник