2011-12-15 12 views
5

Estoy intentando eliminar el archivo más antiguo de un árbol con un script en Debian.xargs y find, rm quejándose de n (newline) en el nombre de archivo

find /home/backups -type f \(-name \*.tgz -o -name \*.gz \) -print0 | xargs -0 ls -t | tail -1 | xargs -0 rm 

pero estoy consiguiendo un error: (¿O es que hay una manera mejor/más fácil)

rm: cannot remove `/home/backups/tree/structure/file.2011-12-08_03-01-01.sql.gz\n': No such file or directory 

Alguna idea de lo que estoy haciendo mal, he tratado de RTFM, pero he perdido.

+1

lo que sucede si se omite el '-0' en el último' xargs'? – sjngm

Respuesta

11

El ls añade una nueva línea y los últimos xargs -0 dice que la nueva línea es parte del nombre del archivo. Ejecute los últimos xargs con -d '\n' en lugar de -0.

Por cierto, debido a la forma en que funciona xargs, toda la tubería es un error que está por ocurrir. Considere una lista de nombres de archivo realmente larga producida por el find, de modo que xargs -0 ls ejecute ls varias veces con subconjuntos de los nombres de archivo. Solo la más antigua de las última invocación ls pasará el tail -1. Si el archivo más antiguo es, digamos, el primer nombre de archivo que sale por find, está borrando un archivo más reciente.

+1

Eso no es suficiente. Sin '-d '\ n'',' xargs' se divide en cada espacio en blanco, que probablemente no es lo que quieres. – glglgl

+0

perfecto, gracias. Por cierto, ¿qué se considera una "lista de nombres de archivo realmente larga" por favor? – user1076412

+0

@ user1076412: "long" en este caso significa "mayor que ARG_MAX", que es algo que su sistema operativo establecerá. Si todos los nombres de archivo devueltos por 'find' juntos son más largos que ARG_MAX, xargs lo invocará más de una vez. – Sorpigal

1

ls emite nuevas líneas como separadores, por lo que debe reemplazar la segunda xargs -0 con xargs -d '\n'. Se rompe, sin embargo, si el archivo más antiguo tiene una nueva línea en su nombre.

0

Editar Me perdí el punto de ls -t allí.

Podría sugerir que sea mucho más simple, por ejemplo,

find /home/backups \ 
    -type f -iregex '.*\.t?gz$' \ 
    -mtime +60 -exec rm {} \; 

lo que eliminará cualquier archivo coincidente mayores de una edad específica (60 días, en el ejemplo)


Utilizó tail, pero no han dicho que para buscar null- delimitadores

En cualquier caso, aquí es un util que se puede utilizar para devolver el último elemento 0 delimitada:

#include <string> 
#include <iostream> 
#include <cstdio> 

int main(int argc, const char *argv[]) 
{ 
    std::cin.unsetf(std::ios::skipws); 
    if (! (freopen(NULL, "wb", stdout) && freopen(NULL, "rb", stdin))) 
    { 
     perror("Cannot open stdout/in in binary mode"); 
     return 255; 
    } 

    std::string previous, element; 
    while (std::getline(std::cin, element, '\0')) 
    { 
     previous = element; 
     // if you have c++0x support, use this _instead_ for performance: 
     previous = std::move(element); 
    } 

    std::cout << previous << '\0' << std::flush; 
} 

utilizarlo como

find /home/backups -type f \(-name \*.tgz -o -name \*.gz \) -print0 | ./mytail | xargs -0 rm 
+0

Recuerde que el primer 'xargs' se traga los delimitadores nulos y el' ls' se delimita con líneas nuevas, por lo que la cola está bien. – thiton

+0

Buena idea, pero para esta pregunta falta un orden por tiempo (el 'ls -t' del OP). – thiton

+0

@thiton: se corrigió – sehe

0

ls -tr $(find /home/backups -name '*.gz' -o -name '*.tgz')|head -1|xargs rm -f

+1

Esto se rompe en cualquier espacio en blanco y en listas de archivos demasiado largas. – thiton

2

También puede encontrar utiliza para imprimir la fecha de modificación, clasificar, cortar y xargs a voluntad:

find /home/backups -printf "%[email protected] %p\n" | sort -n | head -1 | cut -d" " -f2- | xargs ls -al 
+0

Este es el mejor enfoque, pero me gustaría 'cortar' en un delimitador en lugar de depender de un desplazamiento fijo. – Sorpigal

+0

puedes valorar mi "estado de svn | corte -c8-" de mis manos frías y muertas! Pero sí, un delimitador sería mejor – Ken

+0

Su edición ha introducido un nuevo error: debería decir '-f2-' en caso de que el nombre del archivo tenga espacios. – Sorpigal

3

Cualquier solución que implica ls es absolutamente incorrecto.

La forma correcta de hacer esto es utilizar find a buscar el conjunto de archivos, sort ordenarlos cronológicamente, filtrar todos menos el primero, y luego rm eliminar. @Ken tenía la mayoría de las veces bien, solo faltan algunos detalles.

find /home/backups -type f \(-name \*.tgz -o -name \*.gz \) -printf '%[email protected] %p\0' |\ 
    sort -z -n | \ 
    { IFS= read -d '' file ; [ -n "$file" ] && echo rm -f "$(cut -d' ' -f2- <<<"$file")" ; } 

Retire la echo anterior para llevar a cabo realmente la eliminación.

El código anterior funciona incluso para archivos que tienen espacios, líneas nuevas u otros valores inusuales en los nombres de archivo. Tampoco hará nada dañino cuando no haya resultados.

Si no se preocupan por la fractura en los saltos de línea en los nombres de archivo este se hace un poco más fácil

find /home/backups -type f \(-name \*.tgz -o -name \*.gz \) -printf '%[email protected] %p\n' |\ 
    sort -n |\ 
    head -n 1 |\ 
    cut -d' ' -f2- |\ 
    xargs echo rm 

La diferencia es que podemos confiar en head y podemos utilizar cut en una tubería en lugar de hacer ninguna locura.

+1

+1 para la solución correcta sin 'ls'. – l0b0