Realizar una acción en cada subdirectorio usando Bash


194

Estoy trabajando en un script que necesita realizar una acción en cada subdirectorio de una carpeta específica.

¿Cuál es la forma más eficiente de escribir eso?


1
Considere la posibilidad de volver y evaluar las respuestas para determinar si son correctas: tiene una respuesta aceptada que obtiene muchas vistas a pesar de los errores más importantes (f / e, ejecutarla en un directorio donde alguien ejecutó anteriormente mkdir 'foo * bar'causará fooy se repetirá barincluso si no existen, y *serán reemplazados por una lista de todos los nombres de archivos, incluso los que no son de directorio).
Charles Duffy,

1
... aún peor es si alguien ejecutó mkdir -p '/tmp/ /etc/passwd /': si alguien ejecuta un script siguiendo esta práctica /tmppara, por ejemplo, buscar directorios para eliminar, podría terminar eliminándolo /etc/passwd.
Charles Duffy,

Respuestas:


177
for D in `find . -type d`
do
    //Do whatever you need with D
done

81
esto se romperá en espacios en blanco
ghostdog74

2
Además, tenga en cuenta que necesita ajustar los findparámetros si desea un comportamiento recursivo o no recursivo.
Chris Tonkinson

32
La respuesta anterior también me dio el directorio propio, por lo que lo siguiente funcionó un poco mejor para mí: find . -mindepth 1 -type d
jzheaux

1
@JoshC, ambas variantes dividirán el directorio creado por mkdir 'directory name with spaces'en cuatro palabras separadas.
Charles Duffy,

44
Necesitaba agregar -mindepth 1 -maxdepth 1o fue demasiado profundo.
ScrappyDev

282

Una versión que evita crear un subproceso:

for D in *; do
    if [ -d "${D}" ]; then
        echo "${D}"   # your processing here
    fi
done

O, si su acción es un solo comando, esto es más conciso:

for D in *; do [ -d "${D}" ] && my_command; done

O una versión aún más concisa ( gracias @enzotib ). Tenga en cuenta que en esta versión cada valor de Dtendrá una barra inclinada final:

for D in */; do my_command; done

48
Puede evitar el ifo [con:for D in */; do
enzotib

2
+1 porque los nombres de directorio no comienzan con ./ en lugar de la respuesta aceptada
Hayri Uğur Koltuk

3
Este es correcto incluso hasta espacios en los nombres de directorio +1
Alex Reinking

55
Hay un problema con el último comando: si está en un directorio sin subdirectorios; devolverá "* /". Así que mejor use el segundo comando for D in *; do [ -d "${D}" ] && my_command; doneo una combinación de los dos últimos:for D in */; do [ -d $D ] && my_command; done
Chris Maes

55
Tenga en cuenta que esta respuesta ignora los directorios ocultos. Para incluir directorios ocultos, use for D in .* *; doen su lugar for D in *; do.
patryk.beza

105

La forma más simple y no recursiva es:

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

Al /final dice, usar directorios solamente.

No hay necesidad de

  • encontrar
  • awk
  • ...

11
Nota: esto no incluirá directorios de puntos (lo cual puede ser algo bueno, pero es importante saberlo).
wisbucky

Es útil tener en cuenta que puede usar shopt -s dotglobpara incluir archivos de puntos / dotdirs al expandir comodines. Ver también: gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html
Steen Schütt

Creo que quería decir /*en lugar de */con /que representa la ruta que desea utilizar.
Brōtsyorfuzthrāx

2
@Shule /*sería para ruta absoluta mientras */que incluiría los subdirectorios de la ubicación actual
Dan G

3
consejo útil: si necesita recortar el '/' final de $ d, use${d%/*}
Hafthor

18

Usa el findcomando.

En GNU find, puede usar el -execdirparámetro:

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

o usando el -execparámetro:

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

o con xargscomando:

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

O usando for loop:

for d in */; { echo "$d"; }

Para la recursividad, intente globbing extendido ( **/) en su lugar (habilitado por :) shopt -s extglob.


Para obtener más ejemplos, consulte: ¿Cómo ir a cada directorio y ejecutar un comando? en SO


1
-exec {} +está especificado por POSIX, -exec sh -c 'owd=$PWD; for arg; do cd -- "$arg" && pwd -P; cd -- "$owd"; done' _ {} +es otra opción legal e invoca menos shells que -exec sh -c '...' {} \;.
Charles Duffy

13

Prácticas frases sencillas

for D in *; do echo "$D"; done
for D in *; do find "$D" -type d; done ### Option A

find * -type d ### Option B

La opción A es correcta para carpetas con espacios intermedios. Además, generalmente más rápido ya que no imprime cada palabra en el nombre de una carpeta como una entidad separada.

# Option A
$ time for D in ./big_dir/*; do find "$D" -type d > /dev/null; done
real    0m0.327s
user    0m0.084s
sys     0m0.236s

# Option B
$ time for D in `find ./big_dir/* -type d`; do echo "$D" > /dev/null; done
real    0m0.787s
user    0m0.484s
sys     0m0.308s

10

find . -type d -print0 | xargs -0 -n 1 my_command


¿Puedo alimentar el valor de retorno de ese hallazgo en un bucle for? Esto es parte de un guión más grande ...
mikewilliamson

@ Mike, poco probable. PS probablemente obtendrá el estado del comando find o xargs, en lugar de my_command.
Paul Tomblin

7

Esto creará una subshell (lo que significa que los valores variables se perderán cuando whilesalga el bucle):

find . -type d | while read -r dir
do
    something
done

Esto no:

while read -r dir
do
    something
done < <(find . -type d)

Cualquiera de los dos funcionará si hay espacios en los nombres de directorio.


Para un manejo aún mejor de nombres de archivos extraños (incluidos los nombres que terminan con espacios en blanco y / o incluyen saltos de línea), use find ... -print0ywhile IFS="" read -r -d $'\000' dir
Gordon Davisson

@GordonDavisson, ... de hecho, incluso diría que -d ''es menos engañoso sobre la sintaxis y las capacidades de bash, ya que -d $'\000'implica (falsamente) que $'\000'de alguna manera es diferente de '', de hecho, uno podría inferir fácilmente (y nuevamente, falsamente) es que bash admite cadenas de estilo Pascal (longitud especificada, capaz de contener literales NUL) en lugar de cadenas C (delimitadas por NUL, incapaz de contener NUL).
Charles Duffy

5

Tu podrías intentar:

#!/bin/bash
### $1 == the first args to this script
### usage: script.sh /path/to/dir/

for f in `find . -maxdepth 1 -mindepth 1 -type d`; do
  cd "$f"
  <your job here>
done

o similar...

Explicación:

find . -maxdepth 1 -mindepth 1 -type d: Solo encuentre directorios con una profundidad recursiva máxima de 1 (solo los subdirectorios de $ 1) y una profundidad mínima de 1 (excluye la carpeta actual .)


Esto tiene errores: intente con un nombre de directorio con espacios. Ver BashPitfalls # 1 , y DontReadLinesWithFor .
Charles Duffy,

El nombre del directorio con espacios está entre comillas y, por lo tanto, funciona y OP no intenta leer líneas del archivo.
Henry Dobson

funciona en el cd "$f". No funciona cuando la salida de findestá dividida en cadenas, por lo que tendrá las partes separadas del nombre como valores separados $f, lo que hace que cumpla o no cite $fel argumento de expansión.
Charles Duffy

No he dicho que se trata de leer líneas de un fichero. findLa salida está orientada a la línea (un nombre a una línea, en teoría, pero ver más abajo) con la -printacción predeterminada .
Charles Duffy

La salida orientada a línea, como from, find -printno es una forma segura de pasar nombres de archivo arbitrarios, ya que uno puede ejecutar algo mkdir -p $'foo\n/etc/passwd\nbar'y obtener un directorio que tiene /etc/passwduna línea separada en su nombre. Manejar nombres de archivos /uploado /tmpdirectorios sin cuidado es una excelente manera de obtener ataques de escalada de privilegios.
Charles Duffy

4

la respuesta aceptada se dividirá en espacios en blanco si los nombres de directorio los tienen, y la sintaxis preferida es $()para bash / ksh. Use la GNU find -exec opción con, +;p. Ej.

find .... -exec mycommand +; #this is same as passing to xargs

o use un bucle while

find .... |  while read -r D
do
  ...
done 

¿Qué parámetro tendrá el nombre del directorio? por ejemplo, chmod + x $ DIR_NAME (sí, sé que hay una opción chmod para solo directorios)
Mike Graf

Hay una sutil diferencia entre find -execy pasar a xargs: findignorará el valor de salida del comando que se está ejecutando, mientras xargsque fallará en una salida distinta de cero. Cualquiera de los dos puede ser correcto, según sus necesidades.
Jason Wodicka

find ... -print0 | while IFS= read -r des más seguro: admite nombres que comienzan o terminan en espacios en blanco y nombres que contienen literales de nueva línea.
Charles Duffy,
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.