¿Cómo realizo una acción elegante en todos los archivos con una extensión específica en subcarpetas?

18

Mi mejor apuesta actual es:

for i in $(find . -name *.jpg); do echo $i; done

Problema: no maneja espacios en los nombres de archivo.

Nota: También me encantaría una forma gráfica de hacerlo, como el comando "árbol".

Vorac
fuente
¿No puedes simplemente poner citas $i? Hago eso y funciona bien. ¿Hay algo malo con ese enfoque?
Umar Farooq Khawaja

Respuestas:

26

La forma canónica es hacer

find . -name '*.jpg' -exec echo {} \;

(reemplazar \;con +para pasar más de un archivo a la echovez)

o (específico de GNU, aunque ahora algunos BSD también lo tienen):

find . -name '*.jpg' -print0 | xargs -r0 echo

zsh:

for i (**/*.jpg(D)) echo $i
enero
fuente
2
Si está utilizando xargs -0 , debe coincidir con find -0
itsbruce el
1
@ MichaelKjörling, sin comillas {} no hay diferencia. {} se expande por find, no por el shell. Una mejora sería no usar el echoque expande secuencias de escape como \b(al menos las Unix conforme echo).
Stéphane Chazelas
1
@sch ¿Estás seguro? La página de findmanual se muestra find . -type f -exec file '{}' \;en la EXAMPLESsección. Tal vez algunas conchas tratarán los frenos especialmente.
QuasarDonkey
1
Las cotizaciones no perjudican pero no hacen diferencia. Todos '{}', \{\}, {}, "{}", '{'}se expanden por el shell (lo Bourne-como la cáscara) para un argumento para encontrar que son los dos caracteres "{" y "}". Reemplace "buscar" con "buscar eco", o printf '<%s>\n' findsi desea verificar dos veces.
Stéphane Chazelas
@QuasarDonkey Ver unix.stackexchange.com/questions/8647/…
Gilles 'SO- deja de ser malvado'
2

Ya se han dado mejores respuestas.

Pero tenga en cuenta que los espacios no son el único problema en el código que proporcionó. los caracteres de tabulación, nueva línea y comodín también son un problema.

Con

IFS='
'
set -f
for i in $(find . -name '*.jpg'); do echo "$i"; done

Entonces solo los caracteres de nueva línea son un problema.

Stéphane Chazelas
fuente
1

Lo que ha mencionado es uno de los problemas básicos que enfrentan las personas cuando intentan leer los nombres de los archivos. A veces, las personas con conocimientos limitados y que tienen una idea errónea de la estructura de archivos y carpetas tienden a olvidar que " en UNIX Todo es un archivo ". Por lo tanto, no entienden que también necesitan manejar espacios, ya que el nombre del archivo puede consistir en 2 o más palabras con espacios.

Solución: Entonces, una de las formas más conocidas de hacer esto es hacer una lectura limpia .

Aquí leeremos todos los archivos presentes y los mantendremos en una variable, la próxima vez que realicemos el procesamiento deseado solo tendremos que mantener esa variable entre comillas, lo que preservará los nombres de los archivos con espacios. Esta es una de las formas básicas de hacerlo, sin embargo, las otras respuestas proporcionadas por las otras personas aquí funcionan igual de bien.

SCRIPT:

 find /path/to/files/ -iname "*jpg" | \
  while read I; do
   cp -v --parent "$I" /backup/dir/
  done

Aquí estoy leyendo los archivos dándoles la ruta y, lo que sea que esté leyendo, los mantengo dentro de una variable I, que citaré en un momento posterior mientras lo proceso para preservar los espacios para procesarlo correctamente.

Espero que esto te ayude de alguna manera.

El caballero oscuro
fuente
1
"todo es un archivo" se refiere a otra cosa. Su solución es específica de GUN y no hace frente a la barra invertida o los caracteres de nueva línea en los nombres de archivo. Los bucles "en lectura" en shells (IMO) son a menudo una indicación de una mala práctica de scripting de shell.
Stéphane Chazelas
@sch: eh, y solía pensar, está bien. Gracias por señalarlo. Probablemente necesito verlo más.
The Dark Knight
lo siento, quise decir "GNU", no "PISTOLA" arriba (es la primera vez que me doy cuenta de que son anagramas, por cierto ...)
Stéphane Chazelas
1

Simplemente con findy bash:

find . -name '*.jpg' -exec bash -c '
    echo "treating file $1 in bash, from path : ${1%/*}" 
' -- {} \;

De esta manera, usamos $1en bash, como en un script básico, que abre buenas perspectivas para realizar cualquier tarea avanzada (o no).

Una forma más eficiente (para evitar tener que iniciar un nuevo bash para cada archivo):

find . -name '*.jpg' -exec bash -c 'for i do
    echo "treating file $i in bash, from path : ${i%/*}" 
  done' -- {} +
Gilles Quenot
fuente
0

En la versión reciente de bash, puede usar la globstaropción:

shopt -s globstar
for file in **/*.jpg ; do
    echo "$file"
done

Para acciones simples, incluso puede omitir el bucle por completo:

echo **/*.jpg
choroba
fuente
Mientras que las obras en zsh (donde la característica se originó a partir) y con ksh93 (con set -G), que no se debe utilizar en bash como la versión de bash se divide fundamentalmente (como GNU grep -r) a medida que desciende en enlaces simbólicos a directorios (equivalente a find -Lo del zsh ***/*.jpg) También tenga en cuenta que, al contrario de encontrar, omitirá los archivos de puntos y no descenderá a los dotdirs (por defecto).
Stéphane Chazelas