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?

Mikewilliamson
fuente
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
Mike Clark
fuente
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
canaca
fuente
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
  • ...
d0x
fuente
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

kenorb
fuente
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
Sriram Murali
fuente
10

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

Paul Tomblin
fuente
¿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.

Pausado hasta nuevo aviso.
fuente
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 .)

Henry Dobson
fuente
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 
ghostdog74
fuente
¿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,