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?

rubo77
la source

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
choroba
la source
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
78

Vous pouvez tester avec -d:

for f in *; do
    if [ -d "$f" ]; then
        # $f is a directory
    fi
done

C'est l'un des opérateurs de test de fichier .

boucle d'or
la source
8
Notez qu'il inclura des liens symboliques vers des répertoires.
Stéphane Chazelas
21
if [[ -d "$f" && ! -L "$f" ]]exclura les liens symboliques
rubo77
Va casser sur le répertoire vide. if [[ "$f" = "*" ]]; then continue; fi
Piskvor le
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é.

Emagdne
la source
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)
rubo77
la source
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)

se ruer
la source
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.

AsymLabs
la source
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
rubo77
la source
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
rubo77
la source
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

rubo77
la source
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
pabloa98
la source
-1
ls -d */ | while read d
do
        echo $d
done
dcat
la source
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

utilisateur250676
la source
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