Comment accéder à chaque répertoire et exécuter une commande?


143

Comment écrire un script bash qui parcourt chaque répertoire dans un parent_directory et exécute une commande dans chaque répertoire .

La structure des répertoires est la suivante:

parent_directory (le nom peut être n'importe quoi - ne suit pas un modèle)

  • 001 (les noms de répertoire suivent ce modèle)
    • 0001.txt (les noms de fichiers suivent ce modèle)
    • 0002.txt
    • 0003.txt
  • 002
    • 0001.txt
    • 0002.txt
    • 0003.txt
    • 0004.txt
  • 003
    • 0001.txt

le nombre de répertoires est inconnu.

Réponses:


108

Vous pouvez effectuer les opérations suivantes lorsque votre répertoire actuel est parent_directory:

for d in [0-9][0-9][0-9]
do
    ( cd "$d" && your-command-here )
done

Le (et )créer un sous - shell, de sorte que le répertoire courant ne change pas dans le script principal.


5
Cela n'a pas d'importance ici car le caractère générique ne correspond à rien d'autre que des nombres, mais dans le cas général, vous voulez toujours mettre le nom du répertoire entre guillemets doubles dans la boucle. cd "$d"serait mieux en ce sens qu'il transfère vers des situations où le caractère générique correspond aux fichiers dont les noms contiennent des espaces et / ou des métacaractères shell.
tripleee

1
(Toutes les réponses ici semblent avoir le même défaut, mais c'est ce qui compte le plus dans la réponse la plus votée.)
tripleee

1
De plus, la grande majorité des commandes ne se soucient pas vraiment du répertoire dans lequel vous les exécutez. for f in foo bar; do cat "$f"/*.txt; done >outputest fonctionnellement équivalent à cat foo/*.txt bar/*.txt >output. Cependant, lsest une commande qui produit une sortie légèrement différente selon la façon dont vous lui passez les arguments; de même, un grepou wcqui génère un nom de fichier relatif sera différent si vous l'exécutez à partir d'un sous-répertoire (mais souvent, vous voulez éviter d'entrer dans un sous-répertoire précisément pour cette raison).
tripleee

158

Cette réponse publiée par Todd m'a aidé.

find . -maxdepth 1 -type d \( ! -name . \) -exec bash -c "cd '{}' && pwd" \;

Le \( ! -name . \) évite d'exécuter la commande dans le répertoire courant.


4
Et si vous ne souhaitez exécuter la commande que dans certains dossiers:find FOLDER* -maxdepth 0 -type d \( ! -name . \) -exec bash -c "cd '{}' && pwd" \;
Martin K

3
J'ai dû le faire -exec bash -c 'cd "$0" && pwd' {} \;car certains répertoires contenaient des guillemets simples et des guillemets doubles.
Charlie Gorichanaz

12
Vous pouvez également omettre le répertoire actuel en ajoutant-mindepth 1
mdziob

Comment changer cela en une fonction accepter une commande comme "git remote get-url origin` au lieu de pwddirectement?
roachsinai

disd(){find . -maxdepth 1 -type d \( ! -name . \) -exec bash -c 'cd "{}" && "$@"' \;}ne fonctionne pas.
roachsinai

48

Si vous utilisez GNU find, vous pouvez essayer des -execdirparamètres, par exemple:

find . -type d -execdir realpath "{}" ';'

ou (selon le commentaire @gniourf_gniourf ):

find . -type d -execdir sh -c 'printf "%s/%s\n" "$PWD" "$0"' {} \;

Remarque: vous pouvez utiliser ${0#./}au lieu de $0pour fixer ./à l'avant.

ou un exemple plus pratique:

find . -name .git -type d -execdir git pull -v ';'

Si vous souhaitez inclure le répertoire courant, c'est encore plus simple en utilisant -exec:

find . -type d -exec sh -c 'cd -P -- "{}" && pwd -P' \;

ou en utilisant xargs:

find . -type d -print0 | xargs -0 -L1 sh -c 'cd "$0" && pwd && echo Do stuff'

Ou un exemple similaire suggéré par @gniourf_gniourf :

find . -type d -print0 | while IFS= read -r -d '' file; do
# ...
done

Les exemples ci-dessus prennent en charge les répertoires avec des espaces dans leur nom.


Ou en affectant dans un tableau bash:

dirs=($(find . -type d))
for dir in "${dirs[@]}"; do
  cd "$dir"
  echo $PWD
done

Modifiez le nom .de votre dossier spécifique. Si vous n'avez pas besoin d'exécuter récursivement, vous pouvez utiliser: à la dirs=(*)place. L'exemple ci-dessus ne prend pas en charge les répertoires avec des espaces dans le nom.

Ainsi, comme @gniourf_gniourf l'a suggéré, la seule façon correcte de mettre la sortie de find dans un tableau sans utiliser de boucle explicite sera disponible dans Bash 4.4 avec:

mapfile -t -d '' dirs < <(find . -type d -print0)

Ou pas une méthode recommandée (qui implique l' analyse dels ):

ls -d */ | awk '{print $NF}' | xargs -n1 sh -c 'cd $0 && pwd && echo Do stuff'

L'exemple ci-dessus ignorerait le répertoire actuel (comme demandé par OP), mais il cassera sur les noms avec des espaces.

Voir également:


1
La seule manière correcte de mettre la sortie de finddans un tableau sans utiliser de boucle explicite sera disponible dans Bash 4.4 avec mapfile -t -d '' dirs < <(find . -type d -print0). Jusque-là, votre seule option est d'utiliser une boucle (ou de reporter l'opération à find).
gniourf_gniourf

1
En fait, vous n'avez pas vraiment besoin du dirstableau; vous en boucle pourrait sur findla sortie « ( en toute sécurité) comme ceci: find . -type d -print0 | while IFS= read -r -d '' file; do ...; done.
gniourf_gniourf

@gniourf_gniourf Je suis visuel et j'essaie toujours de trouver / rendre les choses simples. Par exemple, j'ai ce script et en utilisant le tableau bash, c'est facile et lisible pour moi (en séparant la logique en plus petits morceaux, au lieu d'utiliser des tuyaux). Présentation de tuyaux supplémentaires, IFS, read, il donne l'impression de complexité supplémentaire. Je vais devoir y réfléchir, il y a peut-être un moyen plus simple de le faire.
kenorb

Si par plus facile vous entendez brisé, alors oui, il existe des moyens plus faciles de faire. Maintenant ne vous inquiétez pas, tout cela IFSet read(comme vous le dites) devient une seconde nature au fur et à mesure que vous vous y habituez ... ils font partie des manières canoniques et idiomatiques d'écrire des scripts.
gniourf_gniourf

Au fait, votre première commande find . -type d -execdir echo $(pwd)/{} ';'ne fait pas ce que vous voulez (elle $(pwd)est développée avant findmême d'être exécutée)…
gniourf_gniourf

29

Vous pouvez y parvenir en raccordant puis en utilisant xargs. Le hic, c'est que vous devez utiliser l' -Iindicateur qui remplacera la sous-chaîne de votre commande bash par la sous-chaîne passée par chacun des xargs.

ls -d */ | xargs -I {} bash -c "cd '{}' && pwd"

Vous voudrez peut-être remplacer pwdpar la commande que vous souhaitez exécuter dans chaque répertoire.


2
Je ne sais pas pourquoi cela n'a pas été voté, mais c'est le script le plus simple que j'ai trouvé jusqu'à présent. Merci
Linvi

20

Si le dossier de niveau supérieur est connu, vous pouvez simplement écrire quelque chose comme ceci:

for dir in `ls $YOUR_TOP_LEVEL_FOLDER`;
do
    for subdir in `ls $YOUR_TOP_LEVEL_FOLDER/$dir`;
    do
      $(PLAY AS MUCH AS YOU WANT);
    done
done

Sur le $ (JOUEZ AUSSI QUE VOUS VOULEZ); vous pouvez mettre autant de code que vous le souhaitez.

Notez que je n'ai pas "cd" sur aucun répertoire.

À votre santé,


3
Il y a quelques exemples de "Utilisation inutile de ls" ici - vous pouvez simplement le faire à la for dir in $YOUR_TOP_LEVEL_FOLDER/*place.
Mark Longair

1
Bon, c'est peut-être inutile dans un sens général mais cela permet de faire du filtrage directement dans les ls (c'est-à-dire tous les répertoires se terminant par .tmp). C'est pourquoi j'ai utilisé l' ls $direxpression
gforcada

13
for dir in PARENT/*
do
  test -d "$dir" || continue
  # Do something with $dir...
done

Et c'est très facile à comprendre!
Rbjz

6

Alors que les doublures sont bonnes pour une utilisation rapide et sale, je préfère une version plus détaillée pour l'écriture de scripts. C'est le modèle que j'utilise qui prend en charge de nombreux cas extrêmes et vous permet d'écrire du code plus complexe à exécuter sur un dossier. Vous pouvez écrire votre code bash dans la fonction dir_command. Ci-dessous, dir_coomand implémente le balisage de chaque référentiel dans git à titre d'exemple. Le reste du script appelle dir_command pour chaque dossier du répertoire. L'exemple d'itération à travers un ensemble de dossiers donné est également inclus.

#!/bin/bash

#Use set -x if you want to echo each command while getting executed
#set -x

#Save current directory so we can restore it later
cur=$PWD
#Save command line arguments so functions can access it
args=("$@")

#Put your code in this function
#To access command line arguments use syntax ${args[1]} etc
function dir_command {
    #This example command implements doing git status for folder
    cd $1
    echo "$(tput setaf 2)$1$(tput sgr 0)"
    git tag -a ${args[0]} -m "${args[1]}"
    git push --tags
    cd ..
}

#This loop will go to each immediate child and execute dir_command
find . -maxdepth 1 -type d \( ! -name . \) | while read dir; do
   dir_command "$dir/"
done

#This example loop only loops through give set of folders    
declare -a dirs=("dir1" "dir2" "dir3")
for dir in "${dirs[@]}"; do
    dir_command "$dir/"
done

#Restore the folder
cd "$cur"

5

Je ne comprends pas le formatage du fichier, car vous ne voulez parcourir que des dossiers ... Cherchez-vous quelque chose comme ça?

cd parent
find . -type d | while read d; do
   ls $d/
done

4

vous pouvez utiliser

find .

pour rechercher tous les fichiers / répertoires dans le répertoire courant de manière récurrente

Ensuite, vous pouvez diriger la sortie de la commande xargs comme ceci

find . | xargs 'command here'

3
Ce n'est pas ce qui a été demandé.
kenorb

0
for p in [0-9][0-9][0-9];do
    (
        cd $p
        for f in [0-9][0-9][0-9][0-9]*.txt;do
            ls $f; # Your operands
        done
    )
done

Vous auriez besoin de modifier à nouveau le répertoire ou de le faire cddans un sous-shell.
Mark Longair

Downvote: la modification a perdu le cddonc ce code ne fait rien d'utile.
tripleee
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.