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