Comment parcourir un répertoire de manière récursive pour supprimer des fichiers avec certaines extensions


157

J'ai besoin de parcourir un répertoire de manière récursive et de supprimer tous les fichiers avec l'extension .pdfet .doc. Je parviens à parcourir 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 ne vais nulle part.


68
Je sais que c'est une mauvaise forme d'exécuter du code sans le comprendre, mais beaucoup de gens viennent sur ce site pour apprendre les scripts bash. Je suis arrivé ici en recherchant sur Google "les fichiers de script bash de manière récursive", et j'ai presque exécuté l'une de ces réponses (juste pour tester la récursivité) sans me rendre compte que cela supprimerait des fichiers. Je sais que cela rmfait partie du code d'OP, mais ce n'est pas vraiment pertinent pour la question posée. Je pense que ce serait plus sûr si les réponses étaient formulées en utilisant une commande inoffensive comme echo.
Keith


1
@Keith a eu une expérience similaire, tout à fait d'accord et a changé le titre
idclev 463035818

Réponses:


146

find est juste fait pour ça.

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

19
Ou trouvez l' -deleteoption.
Matthew Flaschen

28
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.
Grumbel

7
L'utilisation xargssans option est presque toujours un mauvais conseil et ce n'est pas une exception. Utilisez find … -execplutôt.
Gilles 'SO- arrête d'être diabolique'

211

Pour faire suite à la réponse de mouviciel, vous pouvez également le faire comme une boucle for, au lieu d'utiliser xargs. Je trouve souvent les xargs encombrants, 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 commenté, 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. Cela échoue également s'il y a des caractères génériques \[?*dans les noms de fichier. 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 nouvelles lignes dans vos noms de fichiers, cela ne fonctionnera pas non plus. Vous êtes mieux avec une solution basée sur xargs:

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

(Les crochets échappés sont obligatoires ici pour que les -print0deux orclauses s'appliquent .)

GNU et * BSD find ont également une -deleteaction, qui ressemblerait à ceci:

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

27
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 la recherche sur un espace).
trev

3
Comment éviter le fractionnement sur les espaces? J'essaye une chose similaire et j'ai beaucoup de répertoires avec des espaces qui bousillent cette boucle.
Christian

3
parce que c'est une réponse très utile?
zenperttu

1
@Christian Corrige la division des espaces en utilisant des guillemets comme ceci: "$ (find ...)". J'ai édité la réponse de James pour l'afficher.
Matthieu

2
@Matthew votre modification n'a rien corrigé du tout: elle a en fait fait fonctionner la commande uniquement s'il y a un fichier unique trouvé . Au moins cette version fonctionne s'il n'y a pas d'espaces, tabulations, etc. dans les noms de fichiers. Je suis revenu à l'ancienne version. Noter raisonnable peut vraiment réparer un for f in $(find ...). N'utilisez pas cette méthode.
gniourf_gniourf

67

Sans find:

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

/tmp/*sont des fichiers dans dir 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 globstarau lieu de shopt -s globstar). De plus, dans bash <4.3, cela traverse des liens symboliques vers des répertoires ainsi que des répertoires, ce qui n'est généralement pas souhaitable.


1
Cette méthode a fonctionné pour moi, même avec des noms de fichiers contenant des espaces sur OSX
Ideasasylum

2
A noter que globstar n'est disponible que dans Bash 4.0 ou plus récent .. qui n'est pas la version par défaut sur de nombreuses machines.
Troy Howard

1
Je ne pense pas que vous ayez besoin de spécifier le premier argument. (Au moins à partir d'aujourd'hui,) for f in /tmp/**suffira. Inclut les fichiers de / tmp dir.
phil294

1
Ne serait-ce pas mieux comme ça? for f in /tmp/*.{pdf,doc} tmp/**/*.{,pdf,doc} ; do
Ice-Blaze

1
**est une belle extension mais pas portable sur POSIX sh. (Cette question est étiquetée bash mais ce serait bien de souligner que contrairement à plusieurs des solutions ici, il s'agit vraiment uniquement de Bash. Ou bien, cela fonctionne également dans plusieurs autres shells étendus.)
tripleee

27

Si vous voulez faire quelque chose de manière récursive, je vous suggère d'utiliser la récursivité (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, findc'est probablement un meilleur choix comme cela a déjà été suggéré.


15

Voici un exemple utilisant 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

Cela ne répond pas directement à votre question, mais vous pouvez résoudre votre problème avec un one-liner:

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

Certaines versions de find (GNU, BSD) ont une -deleteaction que vous pouvez utiliser au lieu d'appeler rm:

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

7

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

Modifier, corrige un par un

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
}

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

1
Cela ne fonctionne pas avec tous les noms de fichiers: cela échoue avec des espaces à la fin du nom, avec des noms de fichiers contenant des retours à la ligne et avec certains noms de fichiers contenant des barres obliques inverses. Ces défauts pourraient être corrigés, mais toute l'approche est inutilement complexe, donc cela ne vaut pas la peine d'être dérangé.
Gilles 'SO- arrête d'être diabolique'

3

Pour bash (depuis la version 4.0):

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

C'est tout.
L'extension de fin ".ext" permet de sélectionner les fichiers (ou répertoires) avec cette extension.

L'option globstar active le ** (recherche récursive).
L'option nullglob supprime un * lorsqu'il ne correspond à aucun fichier / répertoire.
L'option dotglob inclut les fichiers commençant par un point (fichiers cachés).

Attention, avant bash 4.3, **/il traverse également des liens symboliques vers des répertoires, ce qui n'est pas souhaitable.


1

La fonction suivante itérerait récursivement dans tous les répertoires du \home\ubunturépertoire (structure de répertoires entière sous ubuntu) et appliquerait les vérifications nécessaires en elsebloc.

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

C'est la façon la plus simple que je connaisse de faire ceci: rm **/@(*.doc|*.pdf)

** rend ce travail récursif

@(*.doc|*.pdf) recherche un fichier se terminant par pdf OU doc

Facile à tester en toute sécurité en le remplaçant rmparls


0

Il n'y a aucune raison de diriger la sortie de findvers un autre utilitaire. finda un -deletedrapeau intégré.

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

0

Les autres réponses fournies n'incluront pas les fichiers ou répertoires commençant par un. ce qui suit a fonctionné pour moi:

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

Fais juste

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

4
Non, ne fais pas ça. Cela casse si vous avez des noms de fichiers avec des espaces ou d'autres symboles amusants.
gniourf_gniourf

-1

Ce qui suit va parcourir le répertoire donné de manière récursive et lister tout le contenu:

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


Non, cette fonction ne parcourt rien de manière récursive. Il répertorie uniquement le contenu des sous-répertoires. C'est juste duveteux ls -l /home/ubuntu/*/, donc c'est assez inutile.
Gilles 'SO- arrête d'être diabolique'

-1

Si vous pouvez modifier le shell utilisé pour exécuter la commande, vous pouvez utiliser ZSH pour effectuer le travail.

#!/usr/bin/zsh

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

Cela fera une boucle récursive dans tous les fichiers / dossiers.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.