Jak cyklicznie przeglądać katalog w celu usunięcia plików z określonymi rozszerzeniami

157

Muszę cyklicznie przeglądać katalog i usunąć wszystkie pliki z rozszerzeniami .pdfi .doc. Udaje mi się cyklicznie przeglądać katalog, ale nie udaje mi się filtrować plików z wyżej wymienionymi rozszerzeniami plików.

Jak dotąd mój kod

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

Potrzebuję pomocy, aby ukończyć kod, ponieważ nigdzie nie dojdę.

Elitmiar
źródło
68
Wiem, że wykonywanie kodu bez jego zrozumienia jest złą formą, ale wiele osób przychodzi na tę stronę, aby nauczyć się skryptów bash. Dostałem się tutaj, wpisując w Google „rekursywne pliki skryptów bash” i prawie uruchomiłem jedną z tych odpowiedzi (tylko w celu przetestowania rekurencji), nie zdając sobie sprawy, że spowoduje to usunięcie plików. Wiem, że rmto część kodu OP, ale tak naprawdę nie ma związku z zadanym pytaniem. Myślę, że byłoby bezpieczniej, gdyby odpowiedzi zostały sformułowane za pomocą nieszkodliwego polecenia, takiego jak echo.
Keith,
Podobne pytanie tutaj: stackoverflow.com/questions/41799938/…
codeforester
1
@Keith miał podobne doświadczenie, całkowicie się zgadzam i zmienił tytuł
idclev 463035818

Odpowiedzi:

146

find jest po prostu do tego stworzony.

find /tmp -name '*.pdf' -or -name '*.doc' | xargs rm
mouviciel
źródło
19
Lub znajdź -deleteopcję.
Matthew Flaschen,
28
Należy zawsze używać find ... -print0 | xargs -0 ..., a nie surowego find | xargs, aby uniknąć problemów z nazwami plików zawierającymi znaki nowej linii.
Grumbel
7
Używanie xargsbez opcji jest prawie zawsze złą radą i nie jest to wyjątek. Użyj find … -execzamiast tego.
SO- Gilles 'SO- przestań być zły'
211

Jako uzupełnienie odpowiedzi mouviciel, możesz również zrobić to jako pętlę for, zamiast używać xargs. Często uważam, że xargs są uciążliwe, zwłaszcza jeśli w każdej iteracji muszę zrobić coś bardziej skomplikowanego.

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

Jak komentowało wiele osób, zakończy się to niepowodzeniem, jeśli w nazwach plików są spacje. Możesz obejść ten problem, tymczasowo ustawiając IFS (wewnętrzny separator pól) na znak nowej linii. To również kończy się niepowodzeniem, jeśli \[?*w nazwach plików znajdują się symbole wieloznaczne . Możesz to obejść, tymczasowo wyłączając ekspansję wieloznaczną (globbing).

IFS=$'\n'; set -f
for f in $(find /tmp -name '*.pdf' -or -name '*.doc'); do rm "$f"; done
unset IFS; set +f

Jeśli w nazwach plików masz znaki nowej linii, to też nie zadziała. Lepiej Ci będzie z rozwiązaniem opartym na Xargs:

find /tmp \( -name '*.pdf' -or -name '*.doc' \) -print0 | xargs -0 rm

(Nawiasy ze znakami ucieczki są tutaj wymagane, aby mieć -print0zastosowanie do obu orklauzul).

Znajdowanie GNU i * BSD ma również -deleteakcję, która wyglądałaby następująco:

find /tmp \( -name '*.pdf' -or -name '*.doc' \) -delete
James Scriven
źródło
27
Nie działa to zgodnie z oczekiwaniami, jeśli w nazwie pliku jest spacja (pętla for dzieli wyniki wyszukiwania na białe znaki).
trev
3
Jak uniknąć podziału na białe znaki? Próbuję czegoś podobnego i mam wiele katalogów z białymi znakami, które psują tę pętlę.
Christian
3
bo to bardzo pomocna odpowiedź?
zenperttu
1
@Christian Napraw podział na białe znaki, używając cudzysłowów w ten sposób: "$ (znajdź ...)". Zredagowałem odpowiedź Jamesa, żeby pokazać.
Matthew
2
@Matthew, twoja edycja w ogóle niczego nie naprawiła: faktycznie sprawiła, że ​​polecenie działało tylko wtedy, gdy istnieje unikalny znaleziony plik . Przynajmniej ta wersja działa, jeśli w nazwach plików nie ma spacji, tabulatorów itp. Wróciłem do starej wersji. Zauważenie rozsądku może naprawdę naprawić plik for f in $(find ...). Po prostu nie używaj tej metody.
gniourf_gniourf
67

Bez find:

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

/tmp/*są plikami w katalogu i /tmp/**/*są plikami w podfolderach. Możliwe, że musisz włączyć opcję globstar ( shopt -s globstar). Więc w przypadku pytania kod powinien wyglądać następująco:

shopt -s globstar
for f in /tmp/*.pdf /tmp/*.doc tmp/**/*.pdf tmp/**/*.doc ; do
  rm "$f"
done

Zauważ, że wymaga to bash ≥4.0 (lub zsh bez shopt -s globstarlub ksh z set -o globstarzamiast shopt -s globstar). Co więcej, w bash <4.3, przemierza symboliczne dowiązania do katalogów, jak również katalogów, co zwykle nie jest pożądane.

Tomek
źródło
1
Ta metoda działała dla mnie, nawet w przypadku nazw plików zawierających spacje na OSX
ideasasylum
2
Warto zauważyć, że globstar jest dostępny tylko w Bash 4.0 lub nowszym .. co nie jest domyślną wersją na wielu komputerach.
Troy Howard
1
Nie sądzę, żebyś musiał określać pierwszy argument. (Przynajmniej na dzień dzisiejszy) for f in /tmp/**wystarczy. Zawiera pliki z katalogu / tmp dir.
phil294
1
Czy nie byłoby lepiej w ten sposób? for f in /tmp/*.{pdf,doc} tmp/**/*.{,pdf,doc} ; do
Ice-Blaze,
1
**jest ładnym rozszerzeniem, ale nie przenośnym do POSIX sh. (To pytanie jest otagowane jako bash, ale fajnie byłoby wskazać, że w przeciwieństwie do kilku tutaj rozwiązań, to naprawdę jest tylko Bash. Lub, cóż, działa również w kilku innych rozszerzonych powłokach.)
tripleee
27

Jeśli chcesz coś zrobić rekurencyjnie, sugeruję użycie rekurencji (tak, możesz to zrobić używając stosów i tak dalej, ale hej).

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

(cd /tmp; recursiverm)

To powiedziawszy, findjest prawdopodobnie lepszym wyborem, jak już sugerowano.

falstro
źródło
15

Oto przykład użycia powłoki ( 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
Eric Wang
źródło
15

To nie jest bezpośrednią odpowiedzią na twoje pytanie, ale możesz rozwiązać swój problem za pomocą jednej linijki:

find /tmp \( -name "*.pdf" -o -name "*.doc" \) -type f -exec rm {} +

Niektóre wersje find (GNU, BSD) mają -deleteakcję, której możesz użyć zamiast wywoływania rm:

find /tmp \( -name "*.pdf" -o -name "*.doc" \) -type f -delete
Oliver Charlesworth
źródło
7

Ta metoda dobrze radzi sobie ze spacjami.

files="$(find -L "$dir" -type f)"
echo "Count: $(echo -n "$files" | wc -l)"
echo "$files" | while read file; do
  echo "$file"
done

Edytuj, naprawia pojedynczo

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
źródło
Myślę, że flaga "-n" po echu nie jest potrzebna. Po prostu przetestuj to sam: z "-n" twój skrypt podaje nieprawidłową liczbę plików. Dla dokładnie jednego pliku w katalogu wypisuje "Count: 0"
Lopa
1
Nie działa to ze wszystkimi nazwami plików: kończy się niepowodzeniem ze spacjami na końcu nazwy, nazwami plików zawierającymi znaki nowej linii i niektórymi nazwami plików zawierającymi ukośniki odwrotne. Te wady można naprawić, ale całe podejście jest niepotrzebnie skomplikowane, więc nie warto się tym przejmować.
SO- Gilles 'SO- przestań być zły'
3

Dla basha (od wersji 4.0):

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

To wszystko.
Końcowe rozszerzenie „.ext” służy do wybierania plików (lub katalogów) z tym rozszerzeniem.

Opcja globstar aktywuje ** (wyszukiwanie rekurencyjne).
Opcja nullglob usuwa *, gdy nie pasuje do żadnego pliku / katalogu.
Opcja dotglob zawiera pliki zaczynające się kropką (pliki ukryte).

Uważaj, zanim bash 4.3, **/również przemierza dowiązania symboliczne do katalogów, co nie jest pożądane.

Gilles 'SO- przestań być zły'
źródło
1

Następująca funkcja rekurencyjnie iteruje przez wszystkie katalogi w \home\ubuntukatalogu (cała struktura katalogów pod ubuntu) i stosuje niezbędne sprawdzenia w elsebloku.

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
źródło
1

Oto najprostszy sposób, jaki znam: rm **/@(*.doc|*.pdf)

** sprawia, że ​​działa to rekurencyjnie

@(*.doc|*.pdf) szuka pliku kończącego się na pdf LUB doc

Łatwy bezpiecznie przetestować zastępując rmzls

ecotechie
źródło
0

Nie ma powodu, aby potokować dane wyjściowe finddo innego narzędzia. findma -deletewbudowaną flagę.

find /tmp -name '*.pdf' -or -name '*.doc' -delete
Zak
źródło
0

Pozostałe udzielone odpowiedzi nie będą obejmowały plików ani katalogów zaczynających się od. dla mnie zadziałało:

#/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
źródło
-1

Po prostu zrób

find . -name '*.pdf'|xargs rm
Navi
źródło
4
Nie, nie rób tego. To się psuje, jeśli masz nazwy plików ze spacjami lub innymi zabawnymi symbolami.
gniourf_gniourf
-1

Poniższe polecenie zapętli podany katalog rekurencyjnie i wyświetli całą zawartość:

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

SK Venkat
źródło
Nie, ta funkcja nie przemierza niczego rekurencyjnie. Wymienia tylko zawartość podkatalogów. To tylko puch ls -l /home/ubuntu/*/, więc jest całkiem bezużyteczny.
SO- Gilles 'SO- przestań być zły'
-1

Jeśli możesz zmienić powłokę używaną do uruchomienia polecenia, możesz użyć ZSH do wykonania zadania.

#!/usr/bin/zsh

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

Spowoduje to rekurencyjne zapętlenie wszystkich plików / folderów.

Amin NAIRI
źródło