J'ai quelques milliers de fichiers au format filename.12345.end. Je veux seulement conserver chaque 12ème fichier, donc file.00012.end, file.00024.end ... file.99996.end et supprimer 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 le shell Bash et ne peux pas comprendre comment boucler sur les fichiers, puis sortir le numéro et vérifier s'il number%%12=0
supprime le fichier sinon. Quelqu'un peut-il m'aider?
Merci, Dorina
Réponses:
Voici une solution Perl. Cela devrait être beaucoup plus rapide pour des milliers de fichiers:
Qui peut être encore condensé en:
Si vous avez trop de fichiers et ne pouvez pas utiliser le simple
*
, vous pouvez faire quelque chose comme:En ce qui concerne la vitesse, voici une comparaison de cette approche et celle du shell fournie dans l'une des autres réponses:
Comme vous pouvez le voir, la différence est énorme, comme prévu .
Explication
-e
dit simplementperl
d'exécuter le script donné sur la ligne de commande.@ARGV
est une variable spéciale contenant tous les arguments donnés au script. Puisque nous le donnons*
, il contiendra tous les fichiers (et répertoires) du répertoire courant.Le
grep
recherche dans la liste des noms de fichiers et recherche ceux qui correspondent à une chaîne de chiffres, un point etend
(/(\d+)\.end/)
.Étant donné que les nombres (
\d
) sont dans un groupe de capture (parenthèses), ils sont enregistrés sous$1
. Legrep
vérifie alors si ce nombre est un multiple de 12 et, si ce n'est pas le cas, le nom du fichier sera retourné. En d'autres termes, le tableau@bad
contient la liste des fichiers à supprimer.La liste est ensuite passée à
unlink()
laquelle supprime les fichiers (mais pas les répertoires).la source
Étant donné que vos noms de fichiers sont au format
file.00064.name.99999.end
, nous devons d'abord supprimer tout sauf notre numéro. Nous allons utiliser unefor
boucle pour ce faire.Nous devons également dire au shell Bash d'utiliser la base 10, car l'arithmétique Bash traitera les nombres commençant par un 0 comme base 8, ce qui gâchera les choses pour nous.
En tant que script, pour être lancé dans le répertoire contenant les fichiers, utilisez:
Ou vous pouvez utiliser cette très longue commande laide pour faire la même chose:
Pour expliquer toutes les parties:
for f in ./*
signifie pour tout dans le répertoire courant, faire .... Ceci définit chaque fichier ou répertoire trouvé comme variable $ f.if [[ -f "$f" ]]
vérifie si l'élément trouvé est un fichier, sinon nous sautons à laecho "$f is not...
partie, ce qui signifie que nous ne commençons pas à supprimer les répertoires accidentellement.file="${f%.*}"
définit la variable $ file comme nom de fichier, supprimant tout ce qui vient après le dernier.
.if [[ $((10#${file##*.} % 12)) -eq 0 ]]
C'est là que l'arithmétique principale entre en jeu. Le${file##*.}
coupe tout avant le dernier.
dans notre nom de fichier sans extension.$(( $num % $num2 ))
est la syntaxe pour que l'arithmétique Bash utilise l'opération modulo, la commande10#
indique au début à Bash d'utiliser la base 10 pour gérer les 0 en tête embêtants.$((10#${file##*.} % 12))
nous laisse ensuite le reste de notre nombre de noms de fichiers divisé par 12.-ne 0
vérifie si le reste n'est "pas égal" à zéro.rm
commande, vous voudrez peut-être le remplacerrm
parecho
lors de la première exécution, pour vérifier que vous obtenez les fichiers attendus à supprimer.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'
if
instruction avec laecho
commande pour avertir des répertoires n'est pas vraiment nécessaire carrm
elle se plaindra des répertoires et ne les supprimera pas, donc:Ou
Fonctionnera correctement aussi.
la source
rm
quelques milliers de fois peut être assez lent. Je suggèreecho
le nom du fichier à la place et redirigez la sortie de la bouclexargs rm
(options d'ajouter au besoin):for f in *; do if ... ; then echo "$f"; fi; done | xargs -rd '\n' -- rm --
.xargs
version a pris 5 minutes 1 seconde. Cela pourrait-il être dû à des frais généraux surecho
@DavidFoerster?time { for f in *; do echo "$f"; done | xargs rm; }
vs 1m11.450s / 0m10.695s / 0m16.800s avectime { for f in *; do rm "$f"; done; }
un tmpfs. Bash est v4.3.11, le noyau est v4.4.19.Vous pouvez utiliser l'expansion du support Bash pour générer des noms contenant tous les 12 numéros. Créons des données de test
Ensuite, nous pouvons utiliser ce qui suit
Fonctionne désespérément lentement pour une grande quantité de fichiers - cela prend du temps et de la mémoire pour générer des milliers de noms - c'est donc plus une astuce qu'une solution efficace.
la source
Un peu long, mais c'est ce qui m'est venu à l'esprit.
Explication: supprimez tous les 12 fichiers onze fois.
la source
En toute humilité, je pense que cette solution est beaucoup plus agréable que l'autre réponse:
Une petite explication: nous générons d'abord une liste de fichiers avec
find
. Nous obtenons tous les fichiers dont le nom se termine par.end
et qui sont à une profondeur de 1 (c'est-à-dire qu'ils sont directement dans le répertoire de travail et non dans aucun sous-dossier. Vous pouvez laisser cela de côté s'il n'y a pas de sous-dossier). La liste de sortie sera triée par ordre alphabétique.Ensuite, nous dirigeons cette liste vers
awk
, où nous utilisons la variable spécialeNR
qui est le numéro de ligne. Nous omettons tous les 12 fichiers en imprimant les fichiers oùNR%12 != 0
. Laawk
commande peut être raccourcieawk 'NR%12'
, car le résultat de l'opérateur modulo est interprété comme une valeur booléenne et{print}
est implicitement fait de toute façon.Alors maintenant, nous avons une liste de fichiers qui doivent être supprimés, ce que nous pouvons faire avec xargs et rm.
xargs
exécute la commande donnée (rm
) avec l'entrée standard comme arguments.Si vous avez beaucoup de fichiers, vous obtiendrez une erreur disant quelque chose comme «liste d'arguments trop longue» (sur ma machine, cette limite est de 256 Ko et le minimum requis par POSIX est de 4096 octets). Cela peut être évité par l'
-n 100
indicateur, qui divise les arguments tous les 100 mots (pas les lignes, quelque chose à surveiller si vos noms de fichier ont des espaces) et exécute unerm
commande distincte , chacun avec seulement 100 arguments.la source
-depth
doit être avant-name
; ii) cela échouera si l'un des noms de fichiers contient des espaces; iii) vous supposez que les fichiers seront répertoriés par ordre numérique croissant (c'est ce que vousawk
testez), mais ce ne sera certainement pas le cas. Par conséquent, cela supprimera un ensemble aléatoire de fichiers.-depth
. Pourtant, c'était le moindre des problèmes ici, le plus important est que vous supprimez un ensemble aléatoire de fichiers et non pas ceux que l'OP veut.-depth
cela ne prend pas de valeur et cela fait le contraire de ce que vous pensez qu'il fait. Voirman find
: "-depth Traite le contenu de chaque répertoire avant le répertoire lui-même.". Donc, cela va en fait descendre dans des sous-répertoires et faire des ravages partout.-depth n
et-maxdepth n
existent. La première nécessite que la profondeur soit exactement n, et avec la seconde, elle peut être <= n. II). Oui, c'est mauvais mais pour cet exemple particulier ce n'est pas un problème. Vous pouvez le corriger en utilisantfind ... -print0 | awk 'BEGIN {RS="\0"}; NR%12 != 0' | xargs -0 -n100 rm
, qui utilise l'octet nul comme séparateur d'enregistrement (ce qui n'est pas autorisé dans les noms de fichiers). III) Encore une fois, dans ce cas, l'hypothèse est raisonnable. Sinon, vous pouvez insérer unsort -n
entrefind
etawk
, ou redirigerfind
vers un fichier et le trier comme vous le souhaitez.find
. Encore une fois, cependant, le principal problème est que vous supposez quefind
renvoie une liste triée. Ce n'est pas le cas.Pour utiliser uniquement bash, ma première approche serait de: 1. déplacer tous les fichiers que vous souhaitez conserver dans un autre répertoire (c'est-à-dire tous ceux dont le nombre dans le nom de fichier est un multiple de 12) puis 2. supprimer tous les fichiers restants dans le répertoire, puis 3. remettez les fichiers multiples de 12 que vous avez conservés là où ils étaient. Donc, quelque chose comme ça pourrait fonctionner:
la source
filename
pièce si elle n'est pas cohérente?