¿Cómo ir a cada directorio y ejecutar un comando?

143

¿Cómo escribo un script bash que atraviesa cada directorio dentro de un directorio parent_directory y ejecuta un comando en cada directorio ?

La estructura del directorio es la siguiente:

parent_directory (el nombre puede ser cualquier cosa, no sigue un patrón)

  • 001 (los nombres de directorio siguen este patrón)
    • 0001.txt (los nombres de archivo siguen este patrón)
    • 0002.txt
    • 0003.txt
  • 002
    • 0001.txt
    • 0002.txt
    • 0003.txt
    • 0004.txt
  • 003
    • 0001.txt

El número de directorios es desconocido.

Van de Graff
fuente

Respuestas:

108

Puede hacer lo siguiente, cuando su directorio actual es parent_directory:

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

El (y )crear una subshell, por lo que el directorio actual no cambia en el script principal.

Mark Longair
fuente
55
No importa aquí porque el comodín no coincide con nada más que números, pero en el caso general, básicamente siempre desea poner el nombre del directorio entre comillas dobles dentro del bucle. cd "$d"sería mejor ya que se transfiere a situaciones en las que el comodín coincide con los archivos cuyos nombres contienen espacios en blanco y / o metacaracteres de shell.
tripleee
1
(Todas las respuestas aquí parecen tener el mismo defecto, pero es lo más importante en la respuesta más votada).
tripleee
1
Además, a la gran mayoría de los comandos no les importa en qué directorio los ejecutan. for f in foo bar; do cat "$f"/*.txt; done >outputes funcionalmente equivalente a cat foo/*.txt bar/*.txt >output. Sin embargo, lses un comando que produce resultados ligeramente diferentes dependiendo de cómo le pase los argumentos; Del mismo modo, un grepo wcque genera un nombre de archivo relativo será diferente si lo ejecuta desde un subdirectorio (pero a menudo, desea evitar entrar en un subdirectorio precisamente por esa razón).
tripleee
158

Esta respuesta publicada por Todd me ayudó.

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

El \( ! -name . \) evita ejecutar el comando en el directorio actual.

Christian Vielma
fuente
44
Y si solo desea ejecutar el comando en ciertas carpetas:find FOLDER* -maxdepth 0 -type d \( ! -name . \) -exec bash -c "cd '{}' && pwd" \;
Martin K
3
Tuve que hacerlo -exec bash -c 'cd "$0" && pwd' {} \;ya que algunos directorios contenían comillas simples y algunas comillas dobles.
Charlie Gorichanaz
12
También puede omitir el directorio actual agregando-mindepth 1
mdziob
Cómo cambiar esto a una función aceptará un comando como "Git remoto conseguir-url origen controlado en lugar de pwddirectamente?
roachsinai
disd(){find . -maxdepth 1 -type d \( ! -name . \) -exec bash -c 'cd "{}" && "$@"' \;}no trabajo.
roachsinai
48

Si está utilizando GNU find, puede probar el -execdirparámetro, por ejemplo:

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

o (según el comentario de @gniourf_gniourf ):

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

Nota: Puede usar en ${0#./}lugar de $0arreglar ./en el frente.

o un ejemplo más práctico:

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

Si desea incluir el directorio actual, es aún más simple usando -exec:

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

o usando xargs:

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

O ejemplo similar sugerido por @gniourf_gniourf :

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

Los ejemplos anteriores admiten directorios con espacios en su nombre.


O asignando a bash array:

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

Cambie .a su nombre de carpeta específico. Si no necesita ejecutar recursivamente, puede usar: en su dirs=(*)lugar. El ejemplo anterior no admite directorios con espacios en el nombre.

Entonces, como sugirió @gniourf_gniourf , la única forma adecuada de colocar la salida de find en una matriz sin usar un bucle explícito estará disponible en Bash 4.4 con:

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

O no es una forma recomendada (que implica el análisis dels ):

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

El ejemplo anterior ignoraría el directorio actual (según lo solicitado por OP), pero se romperá en los nombres con los espacios.

Ver también:

kenorb
fuente
1
La única forma adecuada de poner la salida de finden una matriz sin usar un bucle explícito estará disponible en Bash 4.4 con mapfile -t -d '' dirs < <(find . -type d -print0). Hasta entonces, su única opción es usar un bucle (o diferir la operación find).
gniourf_gniourf
1
De hecho, realmente no necesita la dirsmatriz; usted podría bucle de findsalida 's (con seguridad), así: find . -type d -print0 | while IFS= read -r -d '' file; do ...; done.
gniourf_gniourf
@gniourf_gniourf Soy visual y siempre trato de encontrar / hacer las cosas que parecen simples. Por ejemplo, tengo este script y usar bash array es fácil y legible para mí (separando la lógica en partes más pequeñas, en lugar de usar tuberías). La introducción de tubos adicionales, IFS, read, da la impresión de una complejidad adicional. Tendré que pensarlo, tal vez haya una forma más fácil de hacerlo.
kenorb
Si por más fácil te refieres a roto, entonces sí, hay formas más fáciles de hacerlo. Ahora no te preocupes, todo esto IFSy read(como dices) se convierte en una segunda naturaleza a medida que te acostumbras ... son parte de las formas canónicas e idiomáticas de escribir guiones.
gniourf_gniourf
Por cierto, su primer comando find . -type d -execdir echo $(pwd)/{} ';'no hace lo que quiere (el $(pwd)se expande antes findincluso se ejecuta) ...
gniourf_gniourf
29

Puede lograr esto canalizando y luego utilizando xargs. El problema es que necesita utilizar el -Iindicador que sustituirá a la subcadena en su comando de fiesta con la subcadena aprobada por cada uno de los xargs.

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

Es posible que desee reemplazar pwdcon cualquier comando que desee ejecutar en cada directorio.

Piyush Singh
fuente
2
No sé por qué esto no ha sido votado, pero es el script más simple que encontré hasta ahora. Gracias
Linvi
20

Si se conoce la carpeta de nivel superior, puede escribir algo como esto:

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

En el $ (JUEGA TANTO COMO QUIERAS); puedes poner tanto código como quieras.

Tenga en cuenta que no "cd" en ningún directorio.

Salud,

gforcada
fuente
3
Aquí hay un par de instancias de "Uso inútil de ls" ; en su lugar, podría hacerlo for dir in $YOUR_TOP_LEVEL_FOLDER/*.
Mark Longair
1
Bueno, tal vez sea inútil en un sentido general, pero permite filtrar directamente en el ls (es decir, todos los directorios terminaron con .tmp). Es por eso que usé la ls $direxpresión
gforcada
13
for dir in PARENT/*
do
  test -d "$dir" || continue
  # Do something with $dir...
done
Ideal
fuente
¡Y esto es muy fácil de comprender!
Rbjz
6

Si bien uno de los revestimientos es bueno para un uso rápido y sucio, prefiero a continuación una versión más detallada para escribir scripts. Esta es la plantilla que uso, que se encarga de muchos casos extremos y le permite escribir código más complejo para ejecutar en una carpeta. Puede escribir su código bash en la función dir_command. A continuación, dir_coomand implementa el etiquetado de cada repositorio en git como ejemplo. El resto del script llama a dir_command para cada carpeta en el directorio. También se incluye el ejemplo de iterar solo a través de un conjunto de carpetas determinado.

#!/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"
Shital Shah
fuente
5

No entiendo el formato del archivo, ya que solo quieres recorrer las carpetas ... ¿Estás buscando algo como esto?

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

puedes usar

find .

para buscar todos los archivos / directorios en el directorio actual recurrente

De lo que puede canalizar la salida, el comando xargs así

find . | xargs 'command here'
Dan Bizdadea
fuente
3
Esto no es lo que se le pidió.
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
Fedir RYKHTIK
fuente
Tendría que volver a hacer una copia de seguridad del directorio, o hacerlo cden una subshell.
Mark Longair
Voto negativo: la edición se perdió, cdpor lo que este código en realidad no hace nada útil.
tripleee