Recurrir iterativamente a través de archivos en un directorio

15

La iteración recursiva a través de los archivos en un directorio se puede hacer fácilmente mediante:

find . -type f -exec bar {} \;

Sin embargo, lo anterior no funciona para cosas más complejas, donde es necesario realizar muchas ramas condicionales, bucles, etc. Solía ​​usar esto para lo anterior:

while read line; do [...]; done < <(find . -type f)

Sin embargo, parece que esto no funciona para archivos que contienen caracteres oscuros:

$ touch $'a\nb'
$ find . -type f
./a?b

¿Hay alguna alternativa que maneje bien a personajes tan oscuros?

usuario2064000
fuente
1
find ... -exec bash -c 'echo filename is in \$0: "$0"' {} \;Es una mejor manera de hacerlo.
jw013
Puede resolver esto y mantener su diseño original, cambiando su read linea IFS= read -r line. El único personaje que lo romperá es una nueva línea.
Patrick
1
@Patrick, pero los nombres de archivo pueden contener nuevas líneas. Por eso -d $'\0'es preferible.
godlygeek

Respuestas:

7

Otro uso más para la seguridadfind :

while IFS= read -r -d '' -u 9
do
    [Do something with "$REPLY"]
done 9< <( find . -type f -exec printf '%s\0' {} + )

(Esto funciona con cualquier POSIX find, pero la parte de shell requiere bash. Con * BSD y GNU find, puede usar en -print0lugar de -exec printf '%s\0' {} +, será un poco más rápido).

Esto hace posible el uso de entrada estándar dentro del bucle y funciona con cualquier ruta.

l0b0
fuente
1
Porque tuve que buscarlo: "leer ... Si no se proporcionan nombres, la lectura de línea se asigna a la variable RESPUESTA". Entoncesdo echo "Filename is '$REPLY'"
Andrew
9

Hacer esto es tan simple como:

find -exec sh -c 'inline script "$0"' {} \;

O...

find -exec executable_script {} \;
mikeserv
fuente
5

El enfoque más simple (pero seguro) es usar el bloqueo de shell:

$ for f in *; do printf ":%s:\n" "$f"; done 
:a b:
:c
d:
:-e:
:e  f:
h:

Para hacer que lo anterior se repita en subdirectorios (en bash), puede usar la globstaropción; también configurado dotglobpara coincidir con archivos cuyo nombre comienza con. :

$ shopt -s globstar dotglob
$ for f in **/*; do printf ":%s:\n" "$f"; done 
:a b:
:c
d:
:-e:
:e  f:
:foo:
:foo/file1:
:foo/file two:
h:

Tenga en cuenta que hasta bash 4.2, **/ repite en enlaces simbólicos a directorios. Desde bash 4.3, **/solo se repite en directorios, como find.

Otra solución común es usar find -print0conxargs -0 :

$ touch -- 'a b' $'c\nd' $'e\tf' $'g\rh' '-e'
$ find . -type f -print0 | xargs -0 -I{} printf ":%s:\n" {}
h:/g
:./e    f:
:./a b:
:./-e:
:./c
d:

Tenga en cuenta que el h:/ges realmente correcto ya que el nombre del archivo contiene un \r.

terdon
fuente
4

Es un poco difícil hacer tu ciclo de lectura de forma portátil, pero para bash en particular puedes probar algo como esto .

Porción relevante:

while IFS= read -d $'\0' -r file ; do
        printf 'File found: %s\n' "$file"
done < <(find . -iname 'foo*' -print0)

Eso indica findque imprima su salida delimitada por caracteres NUL (0x00), y readque busque líneas delimitadas por NUL ( -d $'\0') sin manejar barras invertidas como escapes para otros caracteres ( -r) y que no se divida ninguna palabra en las líneas ( IFS=). Dado que 0x00 es un byte que no puede aparecer en nombres de archivo o rutas en Unix, esto debería manejar todos sus problemas de nombres de archivo extraños.

Godlygeek
fuente
1
-d ''es equivalente a -d $'\0'.
l0b0