web-dev-qa-db-fra.com

Supprimer tout sauf un fichier 12

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

14
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 .

Explication

  • Le -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).

18
terdon

É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.
  • Si le reste n'est pas égal à 0, le fichier est supprimé à l'aide de la commande 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.

12
Arronical

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.

6
Nykakin

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.

1
Terrik

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.

0
user593851

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
0
delt