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 ;).


Publicidad

Aviso: Los siguientes comentarios pertenecen a las personas que los han enviado.
El administrador de este sitio web no es responsable de los mismos.

[comentarios] Hay 10 comentarios:

Gravatar
13/09/2007 23:30:52
curioso
por plácido (IP: 88.11.123.*)
Comentario de plácido
Me resulta muy curiosa esta entrada en el blog porque hace muy poco tiempo hubo una discusión en la lista de correo del kernel de linux en el que un troll famoso ya por esos lugares comentaba este problema

http://lkml.org/lkml/2007/8/19/19

El encabronamiento general era que el tío en cuestión no es capaz de distinguir entre el sistema operativo y un elemento del mismo, como es la shell. Finalmente se implementó una extensión del gestor de memoria que permite tener listas de argumentos arbitrariamente largas (al menos hasta donde la memoria del sistema permita), que me figuro veremos en el kernel 2.6.23

http://lkml.org/lkml/2007/8/19/44
Gravatar
14/09/2007 08:10:12
Muy bueno :)
por Juanjo (IP: 192.168.0.*)
Comentario de Juanjo
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).
Gravatar
14/09/2007 22:28:57
oops, el nazi de código a la carga
por plácido (IP: 88.11.123.*)
Comentario de plácido
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.
Gravatar
15/09/2007 02:33:41
Cierto
por Juanjo (IP: 192.168.0.*)
Comentario de Juanjo
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).
Gravatar
15/09/2007 19:00:00
Contradiciendo...
por TooManySecrets (IP: 80.224.35.*)
Comentario de TooManySecrets
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
Gravatar
18/09/2007 12:43:22
xargs to the rescue
por SegFault (IP: 208.113.140.*@65.38.160.134)
Comentario de SegFault
ls -1 /tmp/loqusea.* | xargs -L 100 rm

xargs ejecutara el comando rm pasandole como parametro 100 nombres de fichero.

--
SegFault
Gravatar
18/09/2007 22:14:33
bueno...
por placido (IP: 83.43.75.*)
Comentario de placido
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 ;-)
Gravatar
23/09/2007 02:00:50
2.6.23
por funky (IP: 81.202.252.*)
Comentario de funky
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
Gravatar
24/09/2007 14:12:35
La facil :)
por lazaro (IP: 217.125.60.*)
Comentario de lazaro
Pues otra mas para el cuaderno de tips :

for i in `ls loquesea.*`; do rm $i; done :)

Cuando quieres eliminar mas de 4.000 ficheros no esta mal :P.

Saludos y felicitaciones por el blog :)
Gravatar
24/09/2007 15:44:50
Pues no :D
por Juanjo (IP: 192.168.0.*)
Comentario de Juanjo
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.

! Esta entrada no permite nuevos comentarios.

Los comentarios se bloquean automáticamente tras 15 días desde la publicación del artículo.

Si deseas comentar algo relacionado con el texto, puedes enviarme un e-mail.