Як циклічно прокручувати каталог, щоб рекурсивно видаляти файли з певними розширеннями


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
Я знаю, що це погана форма виконання коду, не розуміючи його, але багато людей приходять на цей сайт, щоб вивчити баш сценарії. Я потрапив сюди, гуглюючи "файли скриптових сценаріїв рекурсивно", і ледь не побіг одного з цих відповідей (просто для перевірки рекурсії), не розуміючи, що це видалить файли. Я знаю, що rmє частиною коду ОП, але він насправді не має відношення до поставленого питання. Я думаю, було б безпечніше, якби відповіді були сформульовані за допомогою нешкідливої ​​команди типу echo.
Кіт

Схожий питання тут: stackoverflow.com/questions/41799938 / ...
codeforester

1
@Keith мав подібний досвід, повністю погодився і змінив назву
idclev 463035818

Відповіді:


146

find створено саме для цього.

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

19
Або знайдіть -deleteваріант.
Метью Флашен

28
Завжди слід використовувати find ... -print0 | xargs -0 ..., а не сирі знахідки | xargs, щоб уникнути проблем з іменами файлів, що містять нові рядки.
Grumbel

7
Використання xargsбез варіантів майже завжди є поганою порадою, і це не виняток. Використовуйте find … -execзамість цього.
Жил 'ТАК - перестань бути злим'

211

Як подальший відповідь на mouviciel, ви можете також зробити це як цикл, замість того, щоб використовувати 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 також має -deleteдію, яка виглядатиме так:

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

27
Це не працює, як очікувалося, якщо в імені файлу є пробіл (цикл for для циклу розбиває результати пошуку на пробіл).
трев

3
Як ви маєте на увазі розщеплення на пробіли? Я намагаюся подібну річ, і у мене є багато каталогів з пробілами, які накручують цю петлю.
Крістіан

3
адже це дуже корисна відповідь?
zenperttu

1
@Christian Виправте розділення пробілів, використовуючи такі лапки: "$ (find ...)". Я редагував відповідь Джеймса, щоб показати.
Метью

2
@Matthew ваша редакція взагалі нічого не виправила: вона фактично змусила команду працювати лише за наявності унікального знайденого файлу . Принаймні ця версія працює, якщо у файлах файлів немає пробілів, вкладок тощо. Я повернувся до старої версії. Помітивши розумне дійсно можна виправити for f in $(find ...). Просто не використовуйте цей метод.
gniourf_gniourf

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
Ideasylum

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. Або, ну, він працює і в кількох інших розширених оболонках.)
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 {} +

Деякі версії знаходження (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" ваш сценарій дає неправильну кількість файлів. Точно для одного файлу в каталозі він виводить "Кількість: 0"
Лопа

1
Це не працює з усіма іменами файлів: він не працює з пробілами в кінці імені, з іменами файлів, що містять нові рядки, і з деякими іменами файлів, що містять зворотні риски. Ці дефекти можна виправити, але весь підхід є зайвим складним, тому не варто турбуватися.
Жил "ТАК - перестань бути злим"

3

Для bash (починаючи з версії 4.0):

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

Це все.
Додаткове розширення ".ext" є для вибору файлів (або dirs) із цим розширенням.

Опція globstar активує ** (пошук рекурсивно).
Опція nullglob видаляє *, коли він не відповідає файлу / dir.
Опція dotglob включає файли, які починаються з точки (приховані файли).

Пам’ятайте, що перед ударом 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

Інші надані відповіді не включатимуть файли чи каталоги, які починаються з a. наступне працювало для мене:

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

-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

Це буде рекурсивно прокручувати всі файли / папки.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.