Tengo algunos miles de archivos en el formato nombrearchivo.12345.end. Solo quiero mantener cada 12º archivo, así que file,00012.end, file,00024.end ... file.99996.end y borra todo lo demás.
Los archivos también pueden tener números anteriores en su nombre de archivo, y normalmente tienen la forma: file.00064.name.99999.end
Uso Bash shell y no puedo encontrar la manera de recorrer los archivos y luego obtener el número y verificar si está number%%12=0
borrando el archivo, si no. ¿Alguien puede ayudarme?
Gracias Dorina
Respuestas:
Aquí hay una solución Perl. Esto debería ser mucho más rápido para miles de archivos:
Que se puede condensar aún más en:
Si tiene demasiados archivos y no puede usar el simple
*
, puede hacer algo como:En cuanto a la velocidad, aquí hay una comparación de este enfoque y el shell proporcionado en una de las otras respuestas:
Como puede ver, la diferencia es enorme, como se esperaba .
Explicación
-e
es simplemente decirleperl
a ejecutar la secuencia dada en la línea de comandos.@ARGV
es una variable especial que contiene todos los argumentos dados al script. Como lo estamos dando*
, contendrá todos los archivos (y directorios) en el directorio actual.El
grep
va a buscar a través de la lista de nombres de archivos y buscar los que coincidan con una cadena de números, un punto yend
(/(\d+)\.end/)
.Como los números (
\d
) están en un grupo de captura (paréntesis), se guardan como$1
. Entoncesgrep
verificará si ese número es un múltiplo de 12 y, si no lo es, se devolverá el nombre del archivo. En otras palabras, la matriz@bad
contiene la lista de archivos que se eliminarán.Luego se pasa la lista a la
unlink()
que se eliminan los archivos (pero no los directorios).fuente
Dado que sus nombres de archivo están en el formato
file.00064.name.99999.end
, primero necesitamos recortar todo excepto nuestro número. Usaremos unfor
bucle para hacer esto.También debemos decirle al shell de Bash que use la base 10, porque la aritmética de Bash los tratará con números que comienzan con un 0 como base 8, lo que nos complicará las cosas.
Como secuencia de comandos, que se iniciará cuando esté en el directorio que contiene archivos, use:
O puede usar este comando feo muy largo para hacer lo mismo:
Para explicar todas las partes:
for f in ./*
significa para todo en el directorio actual, hacer .... Esto establece cada archivo o directorio encontrado como la variable $ f.if [[ -f "$f" ]]
comprueba si el elemento encontrado es un archivo; de lo contrario, saltamos a laecho "$f is not...
parte, lo que significa que no comenzamos a eliminar directorios accidentalmente.file="${f%.*}"
establece la variable $ file como el recorte del nombre de archivo de lo que viene después del último.
.if [[ $((10#${file##*.} % 12)) -eq 0 ]]
es donde entra en acción la aritmética principal.${file##*.}
Recorta todo antes del último.
en nuestro nombre de archivo sin extensión.$(( $num % $num2 ))
es la sintaxis para que la aritmética de Bash use la operación de módulo,10#
al principio le dice a Bash que use la base 10, para lidiar con esos molestos ceros iniciales.$((10#${file##*.} % 12))
luego nos deja el resto de nuestro número de nombres de archivo dividido por 12.-ne 0
comprueba si el resto es "no igual" a cero.rm
comando, es posible que desee reemplazarrm
conecho
cuando se ejecuta por primera vez este, para comprobar que usted obtenga los archivos que se espera que desea eliminar.Esta solución no es recursiva, lo que significa que solo procesará archivos en el directorio actual, no entrará en ningún subdirectorio.
La
if
declaración con elecho
comando para advertir sobre los directorios no es realmente necesaria ya querm
por sí sola se quejará de los directorios y no los eliminará, así que:O
Funcionará correctamente también.
fuente
rm
unos miles de veces puede ser bastante lento. Sugiero queecho
el nombre de archivo en lugar y canalizar la salida del bucle dexargs rm
(add opciones según sea necesario):for f in *; do if ... ; then echo "$f"; fi; done | xargs -rd '\n' -- rm --
.xargs
versión tomó 5 minutos y 1 segundo. ¿Podría esto ser debido a una sobrecarga enecho
@DavidFoerster?time { for f in *; do echo "$f"; done | xargs rm; }
vs. 1m11.450s / 0m10.695s / 0m16.800s contime { for f in *; do rm "$f"; done; }
tmpfs. Bash es v4.3.11, Kernel es v4.4.19.Puede usar la expansión de soporte de Bash para generar nombres que contengan cada número 12. Creemos algunos datos de prueba
Entonces podemos usar lo siguiente
Sin embargo, funciona irremediablemente lento para una gran cantidad de archivos: se necesita tiempo y memoria para generar miles de nombres, por lo que es más un truco que una solución eficiente real.
fuente
Un poco largo, pero es lo que me vino a la mente.
Explicación: Eliminar cada 12 archivos once veces.
fuente
Con toda humildad, creo que esta solución es mucho mejor que la otra respuesta:
Una pequeña explicación: primero generamos una lista de archivos con
find
. Obtenemos todos los archivos cuyo nombre termina.end
y que tienen una profundidad de 1 (es decir, están directamente en el directorio de trabajo y no en ninguna subcarpeta. Puede omitirlo si no hay subcarpetas). La lista de salida se ordenará alfabéticamente.Luego canalizamos esa lista
awk
, donde usamos la variable especialNR
que es el número de línea. Dejamos cada 12 archivos imprimiendo los archivos dondeNR%12 != 0
. Elawk
comando se puede acortar aawk 'NR%12'
, porque el resultado del operador de módulo se interpreta como un valor booleano y de{print}
todos modos se hace implícitamente.Así que ahora tenemos una lista de archivos que deben eliminarse, lo que podemos hacer con xargs y rm.
xargs
ejecuta el comando dado (rm
) con la entrada estándar como argumentos.Si tiene muchos archivos, recibirá un error que dice algo así como 'lista de argumentos demasiado larga' (en mi máquina ese límite es 256 kB, y el mínimo requerido por POSIX es 4096 bytes). La
-n 100
bandera puede evitar esto , que divide los argumentos cada 100 palabras (no líneas, algo a tener en cuenta si los nombres de los archivos tienen espacios) y ejecuta unrm
comando separado , cada uno con solo 100 argumentos.fuente
-depth
debe ser anterior-name
; ii) esto fallará si alguno de los nombres de archivo contiene espacios en blanco; iii) está asumiendo que los archivos se enumerarán en orden numérico ascendente (eso es lo queawk
está probando), pero esto seguramente no será el caso. Por lo tanto, esto eliminará un conjunto aleatorio de archivos.-depth
. Aún así, ese fue el menor de los problemas aquí, el más importante es que está eliminando un conjunto aleatorio de archivos y no los que quiere el OP.-depth
no tiene un valor y hace lo contrario de lo que crees que hace. Consulteman find
: "-profundidad Procese el contenido de cada directorio antes que el directorio mismo". Así que esto realmente descenderá a subdirectorios y causará estragos en todo el lugar.-depth n
y-maxdepth n
existen. El primero requiere que la profundidad sea exactamente n, y con el segundo puede ser <= n. II) Sí, eso es malo, pero para este ejemplo en particular no es una preocupación. Puede solucionarlo utilizandofind ... -print0 | awk 'BEGIN {RS="\0"}; NR%12 != 0' | xargs -0 -n100 rm
, que utiliza el byte nulo como separador de registros (que no está permitido en los nombres de archivo). III) Una vez más, en este caso la suposición es razonable. De lo contrario, puede insertar unsort -n
mediofind
yawk
, o redirigirfind
a un archivo y ordenarlo como desee.find
. Una vez más, sin embargo, el problema principal es que está asumiendo quefind
devuelve una lista ordenada. No lo hace.Para usar solo bash, mi primer enfoque sería: 1. mover todos los archivos que desea mantener a otro directorio (es decir, todos aquellos cuyo número en el nombre de archivo es un múltiplo de 12) y luego 2. eliminar todos los archivos restantes en el directorio, luego 3. coloque los múltiples archivos de 12 que guardó donde estaban. Entonces algo como esto podría funcionar:
fuente
filename
pieza si no es consistente?