Come scorrere in modo ricorsivo una directory per eliminare i file con determinate estensioni


157

Ho bisogno di scorrere in modo ricorsivo una directory e rimuovere tutti i file con estensione .pdfe .doc. Riesco a ricorrere in modo ricorsivo a una directory ma non riesco a filtrare i file con le estensioni di file sopra menzionate.

Il mio codice finora

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

Ho bisogno di aiuto per completare il codice, poiché non arrivo da nessuna parte.


68
So che è una cattiva forma eseguire il codice senza capirlo, ma molte persone vengono su questo sito per imparare lo script bash. Sono arrivato qui cercando su google "file di scripting bash in modo ricorsivo" e ho quasi eseguito una di queste risposte (solo per testare la ricorsione) senza rendermi conto che avrebbe eliminato i file. So che rmfa parte del codice OP, ma in realtà non è pertinente alla domanda posta. Penso che sarebbe più sicuro se le risposte fossero formulate usando un comando innocuo come echo.
Keith


1
@Keith ha avuto un'esperienza simile, è completamente d'accordo e ha cambiato il titolo
idclev 463035818

Risposte:


146

find è fatto proprio per quello.

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

19
O trova l' -deleteopzione.
Matthew Flaschen,

28
Si dovrebbe sempre usare find ... -print0 | xargs -0 ..., non trovare grezzo | xargs per evitare problemi con i nomi di file contenenti newline.
Grumbel,

7
L'uso xargssenza opzioni è quasi sempre un cattivo consiglio e questa non fa eccezione. Usa find … -execinvece.
Gilles 'SO- smetti di essere malvagio' il

211

Come seguito alla risposta di mouviciel, puoi anche farlo come un ciclo for, invece di usare xargs. Trovo spesso xarg ingombranti, soprattutto se devo fare qualcosa di più complicato in ogni iterazione.

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

Come un certo numero di persone ha commentato, ciò fallirà se ci sono spazi nei nomi dei file. È possibile aggirare questo problema impostando temporaneamente l'IFS (separatore di campo interno) sul carattere di nuova riga. Ciò fallisce anche se ci sono caratteri jolly \[?*nei nomi dei file. Puoi aggirare questo disabilitando temporaneamente l'espansione jolly (globbing).

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

Se hai nuove righe nei nomi dei file, anche quello non funzionerà. Stai meglio con una soluzione basata su xargs:

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

(Le parentesi di escape sono richieste qui per avere l' -print0applicazione per entrambe le orclausole.)

GNU e * BSD find hanno anche -deleteun'azione, che sarebbe simile a questa:

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

27
Questo non funziona come previsto se c'è uno spazio nel nome del file (il ciclo for divide i risultati di find su whitespace).
trev,

3
Come evitare la divisione su spazi bianchi? Sto provando una cosa simile e ho molte directory con spazi bianchi che rovinano questo loop.
Christian,

3
perché è una risposta molto utile?
zenperttu,

1
@Christian Correggi la divisione degli spazi bianchi usando virgolette come questa: "$ (trova ...)". Ho modificato la risposta di James per mostrare.
Matteo,

2
@Matthew la tua modifica non ha risolto nulla: in realtà ha fatto funzionare il comando solo se c'è un file trovato univoco . Almeno questa versione funziona se non ci sono spazi, tab, ecc. Nei nomi dei file. Sono tornato alla vecchia versione. Notare che sensato può davvero risolvere un problema for f in $(find ...). Basta non usare questo metodo.
gniourf_gniourf

67

Senza find:

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

/tmp/*sono file in dir e /tmp/**/*sono file in sottocartelle. È possibile che tu debba abilitare l'opzione globstar ( shopt -s globstar). Quindi per la domanda il codice dovrebbe apparire così:

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

Nota che questo richiede bash ≥4.0 (o zsh senza shopt -s globstar, o ksh con set -o globstarinvece di shopt -s globstar). Inoltre, in bash <4.3, questo attraversa i collegamenti simbolici alle directory e alle directory, che di solito non è desiderabile.


1
Questo metodo ha funzionato per me, anche con nomi di file contenenti spazi su OSX
ideasasylum,

2
Vale la pena notare che globstar è disponibile solo in Bash 4.0 o versioni successive .. che non è la versione predefinita su molte macchine.
Troy Howard

1
Non credo sia necessario specificare il primo argomento. (Almeno ad oggi) for f in /tmp/**sarà sufficiente. Include i file dalla directory / tmp.
phil294,

1
Non sarebbe meglio così? for f in /tmp/*.{pdf,doc} tmp/**/*.{,pdf,doc} ; do
Ice-Blaze,

1
**è una bella estensione ma non portatile per POSIX sh. (Questa domanda è taggata bash ma sarebbe bello sottolineare che a differenza di molte delle soluzioni qui, questa è davvero solo Bash. O, bene, funziona anche in molte altre shell estese.)
tripleee

27

Se vuoi fare qualcosa in modo ricorsivo, ti suggerisco di usare la ricorsione (sì, puoi farlo usando pile e così via, ma ehi).

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

(cd /tmp; recursiverm)

Detto questo, findè probabilmente una scelta migliore come è già stato suggerito.


15

Ecco un esempio usando 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

Questo non risponde direttamente alla tua domanda, ma puoi risolvere il tuo problema con una sola riga:

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

Alcune versioni di find (GNU, BSD) hanno -deleteun'azione che puoi usare invece di chiamare rm:

find /tmp \( -name "*.pdf" -o -name "*.doc" \) -type f -delete

7

Questo metodo gestisce bene gli spazi.

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

Modifica, correzioni off-by-one

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
}

Penso che la bandiera "-n" dopo l'eco non sia necessaria. Provalo tu stesso: con "-n" il tuo script fornisce un numero errato di file. Per esattamente un file nella directory, viene visualizzato "Count: 0"
Lopa

1
Questo non funziona con tutti i nomi di file: fallisce con spazi alla fine del nome, con nomi di file contenenti newline e con alcuni nomi di file contenenti barre rovesciate. Questi difetti potrebbero essere risolti, ma l'intero approccio è inutilmente complesso, quindi non vale la pena preoccuparsi.
Gilles 'SO- smetti di essere malvagio' il

3

Per bash (dalla versione 4.0):

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

È tutto.
L'estensione finale ".ext" lì per selezionare i file (o dirs) con tale estensione.

L'opzione globstar attiva ** (ricerca ricorsiva).
L'opzione nullglob rimuove un * quando non corrisponde a nessun file / dir.
L'opzione dotglob include i file che iniziano con un punto (file nascosti).

Fai attenzione che prima di bash 4.3 **/attraversa anche collegamenti simbolici a directory che non sono desiderabili.


1

La seguente funzione ripeterebbe in modo ricorsivo tutte le directory nella \home\ubuntudirectory (intera struttura di directory in Ubuntu) e applicherebbe i elseblocchi necessari in blocco.

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

Questo è il modo più semplice che conosco per fare questo: rm **/@(*.doc|*.pdf)

** rende questo lavoro ricorsivo

@(*.doc|*.pdf) cerca un file che termina in pdf O doc

Facile da testare in sicurezza sostituendolo rmconls


0

Non c'è motivo di convogliare l'output di findin un'altra utility. findha una -deletebandiera incorporata.

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

0

Le altre risposte fornite non includeranno file o directory che iniziano con a. per me ha funzionato:

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

Basta fare

find . -name '*.pdf'|xargs rm

4
No, non farlo. Questo si interrompe se hai nomi di file con spazi o altri simboli divertenti.
gniourf_gniourf

-1

Quanto segue scorrerà in modo ricorsivo la directory indicata ed elencherà tutti i contenuti:

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


No, questa funzione non attraversa nulla in modo ricorsivo. Elenca solo il contenuto delle sottodirectory. È solo lanugine in giro ls -l /home/ubuntu/*/, quindi è abbastanza inutile.
Gilles 'SO- smetti di essere malvagio' il

-1

Se è possibile modificare la shell utilizzata per eseguire il comando, è possibile utilizzare ZSH per eseguire il lavoro.

#!/usr/bin/zsh

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

Questo ripeterà ciclicamente tutti i file / cartelle.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.