J'ai quelques milliers de fichiers au format filename.12345.end. Je veux seulement garder tous les 12 fichiers, donc file.00012.end, fichier.00024.end ... fichier.99996.end et supprime tout le reste.
Les fichiers peuvent également avoir des numéros plus tôt dans leur nom de fichier et sont normalement de la forme: file.00064.name.99999.end
J'utilise Bash Shell et je n'arrive pas à comprendre comment boucler les fichiers, puis extraire le numéro et vérifier si c'est le number%%12=0
qui supprime le fichier sinon. Quelqu'un peut-il m'aider?
Merci, Dorina
Voici une solution Perl. Cela devrait être beaucoup plus rapide pour des milliers de fichiers:
Perl -e '@bad=grep{/(\d+)\.end/ && $1 % 12 != 0}@ARGV; unlink @bad' *
Qui peut être davantage condensé dans:
Perl -e 'unlink grep{/(\d+)\.end/ && $1 % 12 != 0}@ARGV;' *
Si vous avez trop de fichiers et que vous ne pouvez pas utiliser le simple *
, vous pouvez procéder comme suit:
Perl -e 'opendir($d,"."); unlink grep{/(\d+)\.end/ && $1 % 12 != 0} readdir($dir)'
En ce qui concerne la rapidité, voici une comparaison de cette approche et de celle de Shell fournie dans l’une des réponses suivantes:
$ touch file.{01..64}.name.{00001..01000}.end
$ ls | wc
64000 64000 1472000
$ time for f in ./* ; do file="${f%.*}"; if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then rm "$f"; fi; done
real 2m44.258s
user 0m9.183s
sys 1m7.647s
$ touch file.{01..64}.name.{00001..01000}.end
$ time Perl -e 'unlink grep{/(\d+)\.end/ && $1 % 12 != 0}@ARGV;' *
real 0m0.610s
user 0m0.317s
sys 0m0.290s
Comme vous pouvez le constater, la différence est énorme, comme prév .
-e
indique simplement Perl
pour exécuter le script indiqué sur la ligne de commande.@ARGV
est une variable spéciale contenant tous les arguments donnés au script. Puisque nous lui donnons *
, il contiendra tous les fichiers (et répertoires) du répertoire en cours.grep
va parcourir la liste des noms de fichiers et rechercher ceux qui correspondent à une chaîne de nombres, un point et end
(/(\d+)\.end/)
.
Les numéros (\d
) étant dans un groupe de capture (parenthèses), ils sont enregistrés sous le nom $1
. Donc, le grep
vérifiera si ce nombre est un multiple de 12 et, dans le cas contraire, le nom du fichier sera renvoyé. En d'autres termes, le tableau @bad
contient la liste des fichiers à supprimer.
La liste est ensuite transmise à unlink()
qui supprime les fichiers (mais pas les répertoires).
Étant donné que vos noms de fichiers sont au format file.00064.name.99999.end
, nous devons d’abord tout supprimer, sauf notre numéro. Nous allons utiliser une boucle for
pour le faire.
Nous devons également indiquer au shell Bash d'utiliser la base 10, car l'arithmétique Bash les traitera avec un nombre commençant par 0, ce qui nous gâchera les choses.
En tant que script, à lancer lorsque dans le répertoire contenant les fichiers, utilisez:
#!/bin/bash
for f in ./*
do
if [[ -f "$f" ]]; then
file="${f%.*}"
if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then
rm "$f"
fi
else
echo "$f is not a file, skipping."
fi
done
Ou vous pouvez utiliser cette très longue commande moche pour faire la même chose:
for f in ./* ; do if [[ -f "$f" ]]; then file="${f%.*}"; if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then rm "$f"; fi; else echo "$f is not a file, skipping."; fi; done
Pour expliquer toutes les parties:
for f in ./*
signifie pour tout dans le répertoire en cours, do .... Ceci définit chaque fichier ou répertoire trouvé en tant que variable $ f.if [[ -f "$f" ]]
vérifie si l'élément trouvé est un fichier; sinon, nous passons à la partie echo "$f is not...
, ce qui signifie que nous ne commençons pas la suppression accidentelle de répertoires.file="${f%.*}"
définit la variable $ file comme nom de fichier coupant après tout le dernier .
.if [[ $((10#${file##*.} % 12)) -eq 0 ]]
est l'endroit où l'arithmétique principale entre en action. Le ${file##*.}
efface tout ce qui se trouve avant le dernier .
de notre nom de fichier sans extension. $(( $num % $num2 ))
est la syntaxe utilisée par l'arithmétique de Bash pour utiliser l'opération modulo, le 10#
au début indique à Bash d'utiliser la base 10 pour gérer ces 0 désagréables. $((10#${file##*.} % 12))
nous laisse ensuite le reste du nombre de nos noms de fichiers divisé par 12. -ne 0
vérifie si le reste n'est "pas égal" à zéro.rm
, vous souhaiterez peut-être remplacer rm
par echo
lors de la première exécution pour vérifier que les fichiers attendus sont supprimés.Cette solution est non-récursive, ce qui signifie qu’elle ne traitera que les fichiers du répertoire en cours, elle n’ira dans aucun sous-répertoire.
L'instruction if
avec la commande echo
pour avertir des répertoires n'est pas vraiment nécessaire, car rm
se plaindra de son propre chef au sujet des répertoires, et non pas pour les supprimer, ainsi:
#!/bin/bash
for f in ./*
do
file="${f%.*}"
if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then
rm "$f"
fi
done
Ou
for f in ./* ; do file="${f%.*}"; if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then rm "$f"; fi; done
Fonctionnera correctement aussi.
Vous pouvez utiliser le développement de parenthèse de Bash pour générer des noms contenant tous les 12 numéros. Créons des données de test
$ touch file.{0..9}{0..9}{0..9}{0..9}{0..9}.end # create test data
$ mv file.00024.end file.00024.end.name.99999.end # testing this form of filenames
Ensuite, nous pouvons utiliser le suivant
$ ls 'file.'{00012..100..12}* # print these with numbers less than 100
file.00012.end file.00036.end file.00060.end file.00084.end
file.00024.end.name.99999.end file.00048.end file.00072.end file.00096.end
$ rm 'file.'{00012..100000..12}* # do the job
Cela fonctionne désespérément lentement pour une grande quantité de fichiers - il faut du temps et de la mémoire pour générer des milliers de noms -, donc c'est plus une astuce qu'une solution efficace réelle.
Un peu long, mais c’est ce qui m’est venu à l’esprit.
for num in $(seq 1 1 11) ; do
for sequence in $(seq -f %05g $num 12 99999) ; do
rm file.$sequence.end.99999;
done
done
Explication: Supprimez 11 fois le fichier 12.
En toute humilité, je pense que cette solution est bien plus intéressante que l’autre réponse:
find . -name '*.end' -depth 1 | awk 'NR%12 != 0 {print}' | xargs -n100 rm
Une petite explication: Tout d’abord, nous générons une liste de fichiers avec find
. Nous obtenons tous les fichiers dont le nom se termine par .end
et qui se trouvent à une profondeur de 1 (c'est-à-dire qu'ils se trouvent directement dans le répertoire de travail et non dans aucun sous-dossier. Vous pouvez l'omettre s'il n'y en a pas). La liste de sortie sera triée par ordre alphabétique.
Ensuite, nous canalisons cette liste dans awk
, où nous utilisons la variable spéciale NR
qui correspond au numéro de ligne. Nous omettons tous les 12 fichiers en imprimant les fichiers où NR%12 != 0
. La commande awk
peut être réduite à awk 'NR%12'
, car le résultat de l'opérateur modulo est interprété comme une valeur booléenne et le {print}
est implicitement appliqué.
Nous avons maintenant une liste de fichiers à supprimer, ce que nous pouvons faire avec xargs et rm. xargs
exécute la commande donnée (rm
) avec l'entrée standard en tant qu'argument.
Si vous avez plusieurs fichiers, vous obtiendrez une erreur disant "Liste des arguments trop longue" (sur ma machine, la limite est de 256 ko et le minimum requis par POSIX est de 4096 octets). Ceci peut être évité grâce à l'indicateur -n 100
, qui divise les arguments tous les 100 mots (pas de lignes, il convient de surveiller si les noms de fichiers ont des espaces) et exécute une commande distincte rm
, chacune avec seulement 100 arguments.
Pour utiliser uniquement bash, ma première approche consisterait à: 1. déplacer tous les fichiers que vous souhaitez conserver dans un autre répertoire (c'est-à-dire tous ceux dont le numéro dans le nom du fichier est un multiple de 12), puis 2. supprimer tous les fichiers restants du répertoire, puis 3. rangez les multiples fichiers sur 12 que vous avez conservés là où ils se trouvaient. Donc, quelque chose comme ça pourrait marcher:
cd dir_containing_files
mkdir keep_these_files
n=0
while [ "${n}" -lt 99999 ]; do
padded_n="`echo -n "00000${n}" | tail -c 5`"
mv "filename${padded_n}.end" keep_these_files/
n=$[n+12]
done
rm filename*.end
mv keep_these_files/* .
rmdir keep_these_files