Comment boucler sur des répertoires sous Linux?

168

J'écris un script en bash sur Linux et j'ai besoin de parcourir tous les noms de sous-répertoires dans un répertoire donné. Comment puis-je parcourir ces répertoires (et ignorer les fichiers normaux)?

Par exemple:
le répertoire donné /tmp/
contient les sous-répertoires suivants:/tmp/A, /tmp/B, /tmp/C

Je veux récupérer A, B, C.

Erik Sapir
la source

Réponses:

129
cd /tmp
find . -maxdepth 1 -mindepth 1 -type d -printf '%f\n'

Une brève explication:

  • find trouve des fichiers (bien évidemment)

  • .est le répertoire actuel, qui après le cdest /tmp(à mon humble avis, c'est plus flexible que d'avoir /tmpdirectement dans la findcommande. Vous n'avez qu'un seul endroit, le cd, à changer, si vous voulez que plus d'actions aient lieu dans ce dossier)

  • -maxdepth 1et -mindepth 1assurez-vous que findne regarde que dans le répertoire courant et ne s'inclut pas .dans le résultat

  • -type d recherche uniquement les répertoires

  • -printf '%f\n imprime uniquement le nom du dossier trouvé (plus une nouvelle ligne) pour chaque appel.

Et voilà!

Boldewyn
la source
Au fait: notez que toutes les réponses trouveront également des dossiers cachés (c'est-à-dire des dossiers commençant par un point)! Si vous ne le souhaitez pas, ajoutez `-regex '\ ./ [^ \.]. *' 'Avant les options printf ou exec.
Boldewyn
1
Utile - si vous avez besoin d'exécuter une seule commande pour chaque hit ;-) - Seulement j'ai plusieurs commandes à exécuter :-(.
Martin
Pas de problème: adaptez cette solution: transnum.blogspot.de/2008/11/… Dans la while..doneboucle, vous pouvez devenir fou.
Boldewyn
1
C'est une très bonne méthode pour certains cas d'utilisation; findL' -execoption de vous permet d'exécuter n'importe quelle commande pour chaque fichier / répertoire.
jtpereyda le
451

Toutes les réponses utilisées jusqu'à présent find, alors en voici une avec juste le shell. Pas besoin d'outils externes dans votre cas:

for dir in /tmp/*/     # list directories in the form "/tmp/dirname/"
do
    dir=${dir%*/}      # remove the trailing "/"
    echo ${dir##*/}    # print everything after the final "/"
done
ghostdog74
la source
18
+1 - Pourquoi s'embêter findquand vous pouvez ajouter une barre oblique à un joker
Tobias Kienzler
19
il en for dir in */; do echo $dir; doneva de même pour les répertoires du répertoire courant.
Ehtesh Choudhury
41
Ce serait bien s'il pouvait contenir une explication de ce que dir=${dir%*/}et echo ${dir##*/}fait.
Jeremy S.
11
C'est parce que la variable dir inclura la barre oblique inverse à la fin. Il le supprime simplement pour avoir un nom propre. % supprimera le / à la fin. ## supprimera le / au début. Ce sont des opérateurs pour gérer les sous-chaînes.
sfratini
8
S'il n'y a pas de correspondance, la boucle sera déclenchée avec /tmp/*/, il serait sage d'inclure une vérification pour voir si le répertoire existe réellement.
Jrs42
41

Vous pouvez parcourir tous les répertoires, y compris les répertoires cachés (commençant par un point) avec:

for file in */ .*/ ; do echo "$file is a directory"; done

remarque: l' utilisation de la liste ne */ .*/fonctionne dans zsh que s'il existe au moins un répertoire caché dans le dossier. En bash, il montrera aussi .et..


Une autre possibilité pour bash d'inclure des répertoires cachés serait d'utiliser:

shopt -s dotglob;
for file in */ ; do echo "$file is a directory"; done

Si vous souhaitez exclure les liens symboliques:

for file in */ ; do 
  if [[ -d "$file" && ! -L "$file" ]]; then
    echo "$file is a directory"; 
  fi; 
done

Pour afficher uniquement le nom du répertoire de fin (A, B, C comme interrogé) dans chaque solution, utilisez ceci dans les boucles:

file="${file%/}"     # strip trailing slash
file="${file##*/}"   # strip path and leading slash
echo "$file is the directoryname without slashes"

Exemple (cela fonctionne également avec les répertoires contenant des espaces):

mkdir /tmp/A /tmp/B /tmp/C "/tmp/ dir with spaces"
for file in /tmp/*/ ; do file="${file%/}"; echo "${file##*/}"; done
rubo77
la source
16

Fonctionne avec les répertoires contenant des espaces

Inspiré par Sorpigal

while IFS= read -d $'\0' -r file ; do 
    echo $file; ls $file ; 
done < <(find /path/to/dir/ -mindepth 1 -maxdepth 1 -type d -print0)

Message d'origine (ne fonctionne pas avec les espaces)

Inspiré de Boldewyn : Exemple de boucle avec findcommande.

for D in $(find /path/to/dir/ -mindepth 1 -maxdepth 1 -type d) ; do
    echo $D ;
done
zpon
la source
9
find . -mindepth 1 -maxdepth 1 -type d -printf "%P\n"
Ignacio Vazquez-Abrams
la source
C'est bien aussi et élimine le besoin de basename. Je préférerais cela à ma réponse.
Boldewyn
6

La technique que j'utilise le plus souvent est find | xargs. Par exemple, si vous souhaitez rendre tous les fichiers de ce répertoire et de tous ses sous-répertoires lisibles par le monde, vous pouvez faire:

find . -type f -print0 | xargs -0 chmod go+r
find . -type d -print0 | xargs -0 chmod go+rx

L' -print0option se termine par un caractère NULL au lieu d'un espace. L' -0option divise son entrée de la même manière. C'est donc la combinaison à utiliser sur les fichiers avec des espaces.

Vous pouvez imaginer cette chaîne de commandes comme prenant chaque sortie de ligne findet la collant à la fin d'une chmodcommande.

Si la commande que vous souhaitez exécuter en tant qu'argument au milieu plutôt qu'à la fin, vous devez être un peu créatif. Par exemple, j'avais besoin de changer dans chaque sous-répertoire et d'exécuter la commande latemk -c. J'ai donc utilisé (de Wikipedia ):

find . -type d -depth 1 -print0 | \
    xargs -0 sh -c 'for dir; do pushd "$dir" && latexmk -c && popd; done' fnord

Cela a pour effet de for dir $(subdirs); do stuff; done, mais est sans danger pour les répertoires avec des espaces dans leurs noms. De plus, les appels séparés à stuffsont effectués dans le même shell, c'est pourquoi dans ma commande nous devons revenir au répertoire courant avec popd.

Matthew Leingang
la source
3

une boucle de bash minimale sur laquelle vous pouvez construire (basée sur la réponse de ghostdog74)

for dir in directory/*                                                     
do                                                                                 
  echo ${dir}                                                                  
done

pour compresser tout un tas de fichiers par répertoire

for dir in directory/*
do
  zip -r ${dir##*/} ${dir}
done                                               
Harry Moreno
la source
Cela répertorie tous les fichiers de directory, pas seulement les sous-répertoires.
dexteritas
2

find . -type d -maxdepth 1

Anonyme
la source
2
cette réponse est bonne, sauf que je reçois les chemins complets, alors que je ne veux que les noms des répertoires.
Erik Sapir le
2

Si vous souhaitez exécuter plusieurs commandes dans une boucle for, vous pouvez enregistrer le résultat de findwith mapfile(bash> = 4) en tant que variable et parcourir le tableau avec ${dirlist[@]}. Il fonctionne également avec des répertoires contenant des espaces.

La findcommande est basée sur la réponse de Boldewyn. Vous trouverez de plus amples informations sur la findcommande ici.

IFS=""
mapfile -t dirlist < <( find . -maxdepth 1 -mindepth 1 -type d -printf '%f\n' )
for dir in ${dirlist[@]}; do
    echo ">${dir}<"
    # more commands can go here ...
done
dexteritas
la source