204 votes

Comment parcourir en boucle un répertoire de manière récursive pour supprimer les fichiers ayant certaines extensions

J'ai besoin de faire une boucle dans un répertoire de manière récursive et de supprimer tous les fichiers avec une extension .pdf y .doc . Je parviens à boucler un répertoire de manière récursive mais je ne parviens pas à filtrer les fichiers avec les extensions de fichiers mentionnées ci-dessus.

Mon code jusqu'à présent

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

J'ai besoin d'aide pour compléter le code, car je n'arrive à rien.

97 votes

Je sais que ce n'est pas bien d'exécuter du code sans le comprendre, mais beaucoup de gens viennent sur ce site pour apprendre le scripting bash. Je suis arrivé ici en googlant "bash scripting files recursively", et presque J'ai exécuté une de ces réponses (juste pour tester la récursivité) sans réaliser qu'elle supprimerait des fichiers. Je sais rm fait partie du code de l'OP, mais il n'est pas vraiment pertinent pour la question posée. Je pense qu'il serait plus sûr que les réponses soient formulées en utilisant une commande inoffensive telle que echo .

0 votes

Question similaire ici : stackoverflow.com/questions/41799938/

1 votes

@Keith a eu une expérience similaire, complètement d'accord et a changé le titre.

261voto

James Scriven Points 2027

Pour faire suite à la réponse de mouviciel, vous pourriez aussi faire cela comme une boucle for, au lieu d'utiliser xargs. Je trouve souvent xargs encombrant, surtout si je dois faire quelque chose de plus compliqué à chaque itération.

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

Comme un certain nombre de personnes l'ont fait remarquer, cela échouera s'il y a des espaces dans les noms de fichiers. Vous pouvez contourner ce problème en définissant temporairement l'IFS (séparateur de champ interne) sur le caractère de nouvelle ligne. L'opération échoue également en présence de caractères génériques. \[?* dans les noms de fichiers. Vous pouvez contourner ce problème en désactivant temporairement l'expansion des caractères génériques (globbing).

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

Si vous avez des retours à la ligne dans vos noms de fichiers, cela ne fonctionnera pas non plus. Il est préférable d'utiliser une solution basée sur xargs :

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

(Les parenthèses échappées sont nécessaires ici pour que l'option -print0 s'appliquent à la fois or clauses).

GNU et *BSD find possède également une fonction -delete qui ressemblerait à ceci :

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

29 votes

Cela ne fonctionne pas comme prévu s'il y a un espace dans le nom du fichier (la boucle for divise les résultats de find sur les espaces).

4 votes

Comment éviter le fractionnement sur les espaces blancs ? J'essaie un truc similaire et j'ai beaucoup de répertoires avec des espaces qui font foirer la boucle.

3 votes

Parce que c'est une réponse très utile ?

173voto

mouviciel Points 36624

find est fait pour ça.

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

24 votes

Ou trouver -delete option.

33 votes

Il faut toujours utiliser find ... -print0 | xargs -0 ... pas raw find | xargs pour éviter les problèmes avec les noms de fichiers contenant des nouvelles lignes.

9 votes

Utilisation de xargs sans options est presque toujours un mauvais conseil et ceci ne fait pas exception. Utilisez find … -exec à la place.

98voto

Tomek Points 103

Sans find :

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

/tmp/* sont des fichiers dans le répertoire et /tmp/**/* sont des fichiers dans des sous-dossiers. Il est possible que vous deviez activer l'option globstar ( shopt -s globstar ). Donc, pour la question, le code devrait ressembler à ceci :

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

Notez que cela nécessite bash 4.0 (ou zsh sans shopt -s globstar ou ksh avec set -o globstar au lieu de shopt -s globstar ). De plus, dans bash <4.3, cela traverse les liens symboliques vers les répertoires ainsi que les répertoires, ce qui n'est généralement pas souhaitable.

1 votes

Cette méthode a fonctionné pour moi, même avec des noms de fichiers contenant des espaces sous OSX.

2 votes

Il convient de noter que globstar n'est disponible que dans Bash 4.0 ou plus récent, ce qui n'est pas la version par défaut sur de nombreuses machines.

1 votes

Je ne pense pas que vous ayez besoin de spécifier le premier argument. (Du moins à ce jour,) for f in /tmp/** sera suffisant. Inclut les fichiers du répertoire /tmp.

35voto

falstro Points 16545

Si vous voulez faire quelque chose de manière récursive, je vous suggère d'utiliser la récursion (oui, vous pouvez le faire en utilisant des piles et ainsi de suite, mais bon).

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

(cd /tmp; recursiverm)

Cela dit, find est probablement un meilleur choix, comme cela a déjà été suggéré.

8voto

TJR Points 1034

Cette méthode gère bien les espaces.

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

Edit, corrige le 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
}

0 votes

Je pense que le drapeau "-n" après echo n'est pas nécessaire. Testez-le vous-même : avec "-n" votre script donne un nombre erroné de fichiers. Pour exactement un fichier dans le répertoire, il affiche "Count : 0"

1 votes

Cela ne fonctionne pas avec tous les noms de fichiers : cela échoue avec les espaces à la fin du nom, avec les noms de fichiers contenant des nouvelles lignes et avec certains noms de fichiers contenant des backslashes. Ces défauts pourraient être corrigés, mais l'approche globale est inutilement complexe et cela ne vaut pas la peine de s'en préoccuper.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X