Al recorrer los archivos hay dos formas:
usa un
for
bucle:for f in *; do echo "$f" done
uso
find
:find * -prune | while read f; do echo "$f" done
Suponiendo que estos dos bucles encontrarán la misma lista de archivos, ¿cuáles son las diferencias en esas dos opciones en rendimiento y manejo?
bash
shell-script
performance
rubo77
fuente
fuente
find
no abre los archivos que encuentra. Lo único que puedo ver mordiéndote aquí con respecto a una gran cantidad de archivos es ARG_MAX .read f
destrozará los nombres de los archivos a medida que los lee (por ejemplo, nombres con espacios en blanco iniciales). Tambiénfind * -prune
parece ser una forma muy complicada de decir simplementels -1
sí.find .
, nofind *
.ls -l
es una mala idea. Pero analizarls -1
(eso1
no es unl
) no es peor que analizarfind * -prune
. Ambos fallan en archivos con nuevas líneas en los nombres.Respuestas:
1)
El primero:
falla por archivos llamados
-n
,-e
y variantes como-nene
y con algunas implementaciones de bash, con los nombres de archivo que contiene las barras invertidas.El segundo:
falla para aún más casos (llamados archivos
!
,-H
,-name
,(
, nombres de archivo que comienzan o terminan con espacios en blanco o caracteres de nueva línea ...)Es el shell que se expande
*
,find
no hace más que imprimir los archivos que recibe como argumentos. También podría haber utilizado en suprintf '%s\n'
lugar lo queprintf
está incorporado también evitaría el error potencial de demasiados argumentos .2)
La expansión de
*
está ordenada, puede hacerlo un poco más rápido si no necesita la clasificación. Enzsh
:o simplemente:
bash
no tiene equivalente por lo que puedo decir, por lo que tendrías que recurrirfind
.3)
(arriba usando una
-print0
extensión no estándar GNU / BSD ).Eso todavía implica generar un comando de búsqueda y usar un
while read
bucle lento , por lo que probablemente será más lento que usar elfor
bucle a menos que la lista de archivos sea enorme.4)
Además, a diferencia de la expansión de comodín de shell,
find
hará unalstat
llamada al sistema en cada archivo, por lo que es poco probable que la no clasificación compense eso.Con GNU / BSD
find
, eso puede evitarse utilizando su-maxdepth
extensión que activará una optimización guardandolstat
:Debido a que
find
comienza a generar nombres de archivos tan pronto como los encuentra (excepto para el búfer de salida stdio), donde puede ser más rápido es si lo que hace en el bucle lleva mucho tiempo y la lista de nombres de archivo es más que un búfer stdio (4 / 8 kB). En ese caso, el procesamiento dentro del ciclo comenzará antes de quefind
haya terminado de encontrar todos los archivos. En los sistemas GNU y FreeBSD, puede usarstdbuf
para hacer que eso suceda antes (deshabilitando el almacenamiento en búfer stdio).5)
La forma POSIX / estándar / portátil de ejecutar comandos para cada archivo
find
es usar el-exec
predicado:Sin
echo
embargo, en el caso de que, sea menos eficiente que hacer el bucle en el shell, ya que el shell tendrá una versión incorporada deecho
whilefind
, tendrá que generar un nuevo proceso y ejecutarlo/bin/echo
para cada archivo.Si necesita ejecutar varios comandos, puede hacer:
Pero ten cuidado porque
cmd2
solo se ejecuta sicmd1
tiene éxito.6)
Una forma canónica de ejecutar comandos complejos para cada archivo es llamar a un shell con
-exec ... {} +
:Esa vez, volvemos a ser eficientes,
echo
ya que estamos usandosh
uno incorporado y la-exec +
versión genera la menor cantidadsh
posible.7)
En mis pruebas en un directorio con 200,000 archivos con nombres cortos en ext4, el
zsh
(párrafo 2) es, con mucho, el más rápido, seguido del primerfor i in *
bucle simple (aunque, como de costumbre,bash
es mucho más lento que otros shells para eso).fuente
!
comando find?!
es por negación.! -name . -prune more...
hará-prune
(ymore...
como-prune
siempre devuelve verdadero) para cada archivo pero.
. Por lo tanto, lo harámore...
en todos los archivos.
, pero excluirá.
y no descenderá a subdirectorios de.
. Entonces es el equivalente estándar de GNU-mindepth 1 -maxdepth 1
.Intenté esto en un directorio con 2259 entradas y utilicé el
time
comando.La salida de
time for f in *; do echo "$f"; done
(¡menos los archivos!) Es:La salida de
time find * -prune | while read f; do echo "$f"; done
(¡menos los archivos!) Es:Ejecuté cada comando varias veces, para eliminar errores de caché. Esto sugiere mantenerlo
bash
(para i in ...) es más rápido que usarfind
y canalizar la salida (abash
)Solo por completo, dejé caer la tubería
find
, ya que en su ejemplo, es totalmente redundante. La salida de justfind * -prune
es:Además,
time echo *
(la salida no está separada por una nueva línea, por desgracia):En este punto, sospecho que la razón
echo *
es más rápida es que no está generando tantas líneas nuevas, por lo que la salida no se desplaza tanto. Probemos ...rendimientos:
mientras que
time find * -prune > /dev/null
produce:y
time for f in *; do echo "$f"; done > /dev/null
rendimientos:y finalmente:
time echo * > /dev/null
rendimientos:Parte de la variación puede explicarse por factores aleatorios, pero parece claro:
for f in *; do ...
es más lento quefind * -prune
, por sí solo, pero para las construcciones anteriores que involucran tuberías, es más rápido.Además, como un aparte, ambos enfoques parecen manejar nombres con espacios muy bien.
EDITAR:
Horarios para
find . -maxdepth 1 > /dev/null
vs.find * -prune > /dev/null
:time find . -maxdepth 1 > /dev/null
:find * -prune > /dev/null
:Entonces, conclusión adicional:
find * -prune
es más lento quefind . -maxdepth 1
: en el primero, el shell procesa un globo y luego construye una línea de comando (grande) parafind
. NB:find . -prune
vuelve solo.
.Más pruebas
time find . -maxdepth 1 -exec echo {} \; >/dev/null
:Conclusión:
fuente
find * -prune | while read f; do echo "$f"; done
tiene la tubería redundante: todo lo que la tubería está haciendo es emitir exactamente lo quefind
produce por sí mismo. Sin una tubería, sería simplementefind * -prune
La tubería solo es redundante específicamente porque la cosa en el otro lado de la tubería simplemente copia stdin en stdout (en su mayor parte). Es un no-op costoso. Si desea hacer cosas con la salida de find, aparte de escupirlo nuevamente, eso es diferente.*
. Como BitsOfNix declaró: Todavía sugiero no usar*
y.
para sufind
lugar.find . -prune
es más rápido porquefind
leerá una entrada de directorio al pie de la letra, mientras que el shell hará lo mismo, posiblemente coincidiendo con el glob (podría optimizar para*
), luego construyendo la línea de comando grande parafind
.find . -prune
imprime solo.
en mi sistema. Casi no funciona en absoluto. No es lo mismofind * -prune
que muestra todos los nombres en el directorio actual. Un simpleread f
va a destrozar los nombres de archivos con espacios iniciales.Definitivamente iría con find, aunque cambiaría tu find por solo esto:
En cuanto al rendimiento,
find
es mucho más rápido dependiendo de sus necesidades, por supuesto. Lo que tiene actualmente confor
él solo mostrará los archivos / directorios en el directorio actual, pero no el contenido de los directorios. Si usa find, también mostrará el contenido de los subdirectorios.Digo hallazgo es mejor ya que con su
for
la*
tendrá que ser ampliado primera y tengo miedo de que si usted tiene un directorio con una enorme cantidad de archivos que puede dar el error de lista de argumentos demasiado tiempo . Lo mismo vale parafind *
Como ejemplo, en uno de los sistemas que uso actualmente hay un par de directorios con más de 2 millones de archivos (<100k cada uno):
fuente
-prune
para hacer los dos ejemplos más parecidos. y prefiero la tubería con while, por lo que es más fácil aplicar más comandos en el buclees un uso inútil de
find
- Lo que está diciendo es efectivamente "para cada archivo en el directorio (*
), no encuentre ningún archivo. Además, no es seguro por varias razones:-r
opción deread
. Esto no es un problema con elfor
bucle.for
bucle.Manejar cualquier nombre de archivo
find
es difícil , por lo que debe usar lafor
opción de bucle siempre que sea posible solo por esa razón. Además, ejecutar un programa externo comofind
en general será más lento que ejecutar un comando de bucle interno comofor
.fuente
find
'-print0
nixargs
'-0
son compatibles con POSIX, y no puede poner comandos arbitrariossh -c ' ... '
(las comillas simples no pueden escaparse entre comillas simples), por lo que no es tan simple.¡Pero somos tontos para las preguntas de rendimiento! Esta solicitud de experimento hace al menos dos suposiciones que la hacen poco válida.
A. Suponga que encuentran los mismos archivos ...
Bueno, ellos van a encontrar los mismos archivos en un primer momento, porque los dos iteración sobre el mismo pegote, a saber
*
. Perofind * -prune | while read f
adolece de varios defectos que hacen posible que no encuentre todos los archivos que espera:find
implementaciones lo hacen, pero aún así, no debe confiar en eso.find *
puede romperse cuando golpeasARG_MAX
.for f in *
no, porque seARG_MAX
aplica aexec
, no incorporados.while read f
puede romperse con nombres de archivos que comienzan y terminan con espacios en blanco, que se eliminarán. Podría superar esto conwhile read
su parámetro predeterminadoREPLY
, pero eso aún no lo ayudará cuando se trata de nombres de archivo con nuevas líneas en ellos.B.
echo
. Nadie va a hacer esto solo para hacer eco del nombre del archivo. Si quieres eso, solo haz uno de estos:La tubería al
while
bucle aquí crea una subshell implícita que se cierra cuando finaliza el bucle, lo que puede ser poco intuitivo para algunos.Para responder a la pregunta, aquí están los resultados en un directorio mío que tiene 184 archivos y directorios.
fuente
$ ps ax | grep bash 20784 pts/1 Ss 0:00 -bash 20811 pts/1 R+ 0:00 grep bash $ while true; do while true; do while true; do while true; do while true; do sleep 100; done; done; done; done; done ^Z [1]+ Stopped sleep 100 $ bg [1]+ sleep 100 & $ ps ax | grep bash 20784 pts/1 Ss 0:00 -bash 20924 pts/1 S+ 0:00 grep bash
find *
no funcionará correctamente si*
produce tokens que parecen predicados en lugar de rutas.No puede usar el
--
argumento habitual para arreglar esto porque--
indica el final de las opciones, y las opciones de find van antes que las rutas.Para solucionar este problema, puede usarlo
find ./*
en su lugar. Pero entonces no está produciendo exactamente las mismas cadenas quefor x in *
.Tenga en cuenta que en
find ./* -prune | while read f ..
realidad no utiliza la funcionalidad de escaneo defind
. Es la sintaxis global./*
que realmente atraviesa el directorio y genera nombres. Luego, elfind
programa tendrá que realizar al menos unastat
verificación en cada uno de esos nombres. Usted tiene la sobrecarga de iniciar el programa y tener acceso a estos archivos, y luego hacer E / S para leer su salida.Es difícil imaginar cómo podría ser cualquier cosa menos menos eficiente que
for x in ./* ...
.fuente
Bueno, para empezar,
for
es una palabra clave de shell, integrada en Bash, mientras quefind
es un ejecutable separado.El
for
bucle solo encontrará los archivos del personaje globstar cuando se expanda, no se repetirá en ningún directorio que encuentre.Find, por otro lado, también recibirá una lista expandida por globstar, pero buscará de forma recursiva todos los archivos y directorios debajo de esta lista expandida y canalizará cada uno hacia el
while
bucle.Ambos enfoques podrían considerarse peligrosos en el sentido de que no manejan rutas o nombres de archivos que contienen espacios.
Eso es todo lo que puedo pensar que vale la pena comentar sobre estos 2 enfoques.
fuente
Si todos los archivos devueltos por find pueden procesarse con un solo comando (obviamente no es aplicable a su ejemplo de eco anterior), puede usar xargs:
fuente
Durante años he estado usando esto: -
para buscar ciertos archivos (por ejemplo, * .txt) que contienen un patrón que grep puede buscar y canalizarlo más para que no se desplace de la pantalla. A veces uso el >> tubo para escribir los resultados en otro archivo que puedo ver más adelante.
Aquí hay una muestra del resultado: -
fuente