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.

Elitmiar
la source
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
Question similaire ici: stackoverflow.com/questions/41799938/…
codeforester
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
mouviciel
la source
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
James Scriven
la source
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.

Tomek
la source
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é.

falstro
la source
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
Eric Wang
la source
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
Oliver Charlesworth
la source
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
}
TJR
la source
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.

Gilles 'SO- arrête d'être méchant'
la source
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
K_3
la source
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

écotechie
la source
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
Zak
la source
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;
}
TrevTheDev
la source
-1

Fais juste

find . -name '*.pdf'|xargs rm
Navi
la source
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

SK Venkat
la source
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.

Amin NAIRI
la source