Comment puis-je parcourir uniquement les répertoires de bash?


251

J'ai un dossier avec des répertoires et des fichiers (certains sont cachés, commençant par un point).

for d in *; do
 echo $d
done

passera en boucle dans tous les fichiers, mais je veux uniquement en boucle dans les répertoires. Comment je fais ça?

Réponses:


334

Vous pouvez spécifier une barre oblique à la fin pour ne faire correspondre que les répertoires:

for d in */ ; do
    echo "$d"
done

7
Notez qu'il inclut également des liens symboliques vers des répertoires.
Stéphane Chazelas

1
Alors, comment voulez-vous exclure les liens symboliques alors?
rubo77

3
@ rubo77: Vous pouvez tester [[ -L $d ]]si $ d est un lien symbolique.
Choroba

1
@ AsymLabs C'est incorrect, cela set -Pn'affecte que les commandes qui changent de répertoire. sprunge.us/TNac
Chris Down

4
@choroba: [[ -L "$f" ]]n'exclura pas les liens symboliques dans ce cas avec */, vous devez [[ -L "${f%/}" ]]
effacer


30

Attention, la solution de choroba, bien qu'élégante, peut provoquer un comportement inattendu si aucun répertoire n'est disponible dans le répertoire actuel. Dans cet état, plutôt que de sauter la forboucle, bash exécutera la boucle exactement une fois où dest égal à */:

#!/usr/bin/env bash

for d in */; do
    # Will print */ if no directories are available
    echo $d
done

Je recommande d'utiliser les éléments suivants pour vous protéger contre ce cas:

#!/usr/bin/env bash

for f in *; do
    if [ -d ${f} ]; then
        # Will not run if no directories are available
        echo $f
    fi
done

Ce code parcourt tous les fichiers du répertoire en cours, vérifie s’il fs’agit d’un répertoire, puis indique fsi la condition renvoie true. Si fest égal à */, echo $fne sera pas exécuté.


3
Beaucoup plus facile à shopt -s nullglob.
Choroba

21

Si vous devez sélectionner des fichiers plus spécifiques que seuls les répertoires utilisent find, transmettez-les à while read:

shopt -s dotglob
find * -prune -type d | while IFS= read -r d; do 
    echo "$d"
done

Utilisez shopt -u dotglobpour exclure les répertoires cachés (ou setopt dotglob/ unsetopt dotglobdans zsh).

IFS=pour éviter de diviser les noms de fichiers contenant l’un des $IFS, par exemple:'a b'

voir la réponse AsymLabs ci-dessous pour plus d' findoptions


edit:
au cas où vous auriez besoin de créer une valeur de sortie à partir de la boucle while, vous pouvez contourner le sous-shell supplémentaire par cette astuce:

while IFS= read -r d; do 
    if [ "$d" == "something" ]; then exit 1; fi
done < <(find * -prune -type d)

2
J'ai trouvé cette solution sur: stackoverflow.com/a/8489394/1069083
rubo77

11

Vous pouvez utiliser pure bash pour cela, mais il vaut mieux utiliser find:

find . -maxdepth 1 -type d -exec echo {} \;

(trouver en outre comprendra des répertoires cachés)


8
Notez qu'il n'inclut pas les liens symboliques vers les répertoires. Vous pouvez utiliser shopt -s dotglobpour bashinclure des répertoires cachés. Le vôtre comprendra également .. Notez également que ce -maxdepthn'est pas une option standard ( -pruneest).
Stéphane Chazelas

2
L' dotgloboption est intéressante mais dotglobne concerne que l'utilisation de *. find .inclura toujours les répertoires cachés (ainsi que le répertoire courant)
rubo77

6

Ceci est fait pour trouver les répertoires visibles et cachés dans le répertoire de travail actuel, en excluant le répertoire racine:

pour simplement parcourir les répertoires:

 find -path './*' -prune -type d

inclure des liens symboliques dans le résultat:

find -L -path './*' -prune -type d

faire quelque chose dans chaque répertoire (à l'exclusion des liens symboliques):

find -path './*' -prune -type d -print0 | xargs -0 <cmds>

pour exclure les répertoires cachés:

find -path './[^.]*' -prune -type d

pour exécuter plusieurs commandes sur les valeurs retournées (un exemple très artificiel):

find -path './[^.]*' -prune -type d -print0 | xargs -0 -I '{}' sh -c \
"printf 'first: %-40s' '{}'; printf 'second: %s\n' '{}'"

au lieu de 'sh -c', vous pouvez également utiliser 'bash -c', etc.


Que se passe-t-il si echo '* /' in 'pour d in echo * /' contient, disons, 60 000 répertoires?
AsymLabs

Vous obtiendrez l'erreur "Trop de fichiers" mais cela peut être résolu: Comment contourner "Trop de fichiers ouverts" dans debian
rubo77

1
L'exemple xargs ne fonctionnerait que pour un sous-ensemble de noms de répertoires 9e.g. ceux avec des espaces ou des nouvelles lignes ne fonctionneraient pas). Mieux vaut utiliser ... -print0 | xargs -0 ...si vous ne connaissez pas les noms exacts.
Anthon

1
@ rubo77 a ajouté un moyen supplémentaire d'exclure des fichiers / répertoires cachés et l'exécution de plusieurs commandes - bien sûr, cela peut aussi être fait avec un script.
AsymLabs

1
J'ai ajouté un exemple dans ma réponse en bas, comment faire quelque chose dans chaque répertoire en utilisant une fonction avecfind * | while read file; do ...
rubo77

3

Vous pouvez parcourir tous les répertoires, y compris les répertoires cachés (commençant par un point) sur une ligne et plusieurs commandes avec:

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

Remarque: l' utilisation de la liste */ .*/fonctionne sous bash, mais affiche également les dossiers .et ..ne s'affiche pas dans zsh, mais génère une erreur s'il n'y a pas de fichier caché dans le dossier.

Une version plus propre qui inclura les répertoires cachés et exclura ../ sera avec l'option dotglob:

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

(ou setopt dotgloben zsh)

vous pouvez désinstaller DOTGLOB avec

shopt -u dotglob

2

Cela inclura le chemin complet dans chaque répertoire de la liste:

for i in $(find $PWD -maxdepth 1 -type d); do echo $i; done

1
les pauses lors de l'utilisation de dossiers avec des espaces
Alejandro Sazo

0

Utilisez findwith -execpour parcourir les répertoires et appeler une fonction dans l'option exec:

dosomething () {
  echo "doing something with $1"
}
export -f dosomething
find -path './*' -prune -type d -exec bash -c 'dosomething "$0"' {} \;

Utiliser shopt -s dotglobou shopt -u dotglobpour inclure / exclure des répertoires cachés


0

Ceci liste tous les répertoires avec le nombre de sous-répertoires dans un chemin donné:

for directory in */ ; do D=$(readlink -f "$directory") ; echo $D = $(find "$D" -mindepth 1 -type d | wc -l) ; done

-1
ls -d */ | while read d
do
        echo $d
done

This is one directory with spaces in the name- mais est analysé comme multiple.
Piskvor

-5
ls -l | grep ^d

ou:

ll | grep ^d

Vous pouvez le définir comme un alias


1
Malheureusement, je ne pense pas que cela réponde à la question «Je veux uniquement parcourir des répertoires» - ce qui est légèrement différent de la lsréponse à cette base, qui répertorie les répertoires.
Jeff Schaller

1
Ce n'est jamais une bonne idée d'analyser la sortie de ls: mywiki.wooledge.org/ParsingLs
codeforester
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.