Otra sobre expansión de argumentos en BASH
Hace ya algún tiempo comentaba diferencias entre SH (pdksh) y BASH, y hoy me he encontrado algo habitual cuando manejas directorios con muchos ficheros, o como me ha pasado a mi: liberando espacio de /tmp/
.
Tenía gran cantidad de ficheros temporales, y otras cosas que no quería borrar, bueno... para eso están los patrones:
# rm /tmp/loquesea.* -bash: /bin/rm: La lista de argumentos es demasiado larga
¡Anda! Y, ¿ahora qué hago? Bueno, lo más fácil es find(1)
al rescate:
# find /tmp/ -name "loquesea.*" -exec rm {} \;
Esta solución hace que se ejecute el rm
sobre cualquier fichero encontrado en /tmp/
o subdirectorios que cumpla el patrón indicado. Ojo que el patrón está entre comillas (no lo interpreta el shell
, sino find
), y veamos porqué la orden original tenía problemas.
Cuando indicamos en el shell
un patrón de sustitución, BASH
en este caso, reemplaza el patrón por una lista de ficheros que se ajustan a la descripción.
Eso quiere decir que si tenemos:
$ ls fichero1 fichero2 fichero3 ficha
Al hace un rm fichero*
, lo que realmente se ejecuta es: rm fichero1 fichero2 fichero3
.
El comando rm(1)
puede procesar listas de elementos a eliminar, pero el tamañano de la lista es limitado, y con los ficheros temporales que tenía esta mañana... he superado el límite.
Apuesto a que hay otras formas de solucionar la papeleta, si alguien se anima, que use los comentarios ;).
Hay 10 comentarios
Aunque no sé si es positivo que se implementen cosas quizás innecesarias para darle en toda la boca al troll de turno :D
Por cierto que el primer enlace que apuntas tiene una alternativa a mi -exec que emplea xargs (aunque siendo puntillosos es menos eficiente, crea un proceso más por fichero).
De hecho siento contradecirte pero no, tu versión es bastante menos eficiente.
find . -name "*.whatever" -exec rm {} \;
eso lanza un rm por cada fichero que cumple el patrón.
find . -name "*.whatever" | xargs rm
eso lanza un find, un xargs y un solo rm para todos los ficheros (bueno, tantos como hagan falta para no exceder el tamaño de la línea de comandos)
En versiones actuales de find (al menos desde Etch para adelante) es posible sustituir el ; terminador de argumentos a -exec en find por un +, que tiene un efecto similar al de redirigir a xargs, es decir, concatena la salida y la pasa como argumentos al comando.
De todas formas, cuando se usa xargs es conveniente usar -print0 en find y -0 en xargs, para no tener problemas con los nombres de ficheros que tengan espacios.
por plácido, en 2007-09-14 22:28:57 ∞
A simple vista he pensado que xargs hacía un fork por cada linea que recogía de la entrada estándard (salida de find en este caso), pero realiza una sola ejecución pasando todo como argumentos (o varias ejecuciones, si son demasiados argumentos).
Al agrupar argumentos, es más eficiente que mi propuesta. Ya decía yo lo de puntilloso, creía que suponía un proceso más al menos (es decir, uno por rm más el del xargs).
Pero vamos, otra vez: a simple vista. A ver si alguien se anima a intentar mejorarlo (pero recurrir a la lista del kernel está feo :D).
Holas!
He visto tu entrada en http://www.planetabsd.es, y venía a comentarte lo mismo que ya veo te ha dicho Plácido; usando "-exec" no te libras del límite de ficheros máximo que puede manejar el shell. Para ello debes usar el comando "xargs" (y también por experiencia, pero con más de 500.000 ficheros que me las he tenido que ver).
Saludos y felicidades por tu weblog.
Have a nice day ;-)
TooManySecrets
por TooManySecrets, en 2007-09-15 19:00:00 ∞
ls -1 /tmp/loqusea.* | xargs -L 100 rm
xargs ejecutara el comando rm pasandole como parametro 100 nombres de fichero.
--
SegFault
por SegFault, en 2007-09-18 12:43:22 ∞
TooManySecrets:
con "find -exec" sí que te libras de la expansión de la shell, lo que se discutía era si era más eficiente que usar "xargs" en este caso concreto. Es decir, la línea que comenta Juanjo funciona perfectamente, simplemente no es eficiente.
SegFault:
si "rm /tmp/loquesea.*" falla porque la lista de argumentos es mayor que el tamaño máximo, entonces "ls /tmp/loquesea.*" también fallará ¿no? Simplemente te has llevado el problema a otro comando ;-)
por placido, en 2007-09-18 22:14:33 ∞
En el próximo kernel 2.6.23 este asunto quedará solucionado gracias la lista variable de argumentos.
Podeís verlo en el Changelog de kernelnewbies:
http://kernelnewbies.org/Linux_2_6_23
por funky, en 2007-09-23 02:00:50 ∞
Has cometido el mismo error que SegFault, trasladando el problema a otro comando (ls).
Lo explico otra vez: el shell reemplaza los patrones (un * en este caso) por los ficheros que encajan, expandiendo la linea de comandos con tantos parámetros como ocurrencias.
Una vez se tiene la linea de ejecución definitiva, se pasa a la llamada al sistema para que ejecute y... falla, porque hay más argumentos que el tope soportado por esa llamada.
Las soluciones válidas (por ahora :D) pasan por usar find y que éste ejecute n veces rm o (como apunta Plácido), usar xargs para que pase el número máximo de parámetros permitidos por ejecución de rm, minimizando así el número de procesos necesarios.
Los comentarios están cerrados: los comentarios se cierran automáticamente una vez pasados 30 días. Si quieres comentar algo acerca de la anotación, puedes hacerlo por e-mail.
por plácido, en 2007-09-13 23:30:52 ∞