Question

Existe-t-il un moyen simple, dans un environnement UNIX assez standard avec bash, d'exécuter une commande pour supprimer d'un répertoire tous les fichiers X sauf les plus récents ?

Pour donner un exemple un peu plus concret, imaginez une tâche cron écrivant un fichier (par exemple, un fichier journal ou une sauvegarde tarée) dans un répertoire toutes les heures.J'aimerais trouver un moyen d'exécuter une autre tâche cron qui supprimerait les fichiers les plus anciens de ce répertoire jusqu'à ce qu'il y en ait moins de, disons, 5.

Et juste pour être clair, il n'y a qu'un seul fichier présent, il ne doit jamais être supprimé.

Était-ce utile?

La solution

Les problèmes avec les réponses existantes :

  • incapacité à gérer les noms de fichiers avec des espaces intégrés ou des nouvelles lignes.
    • dans le cas de solutions qui invoquent rm directement sur une substitution de commande sans guillemets (rm `...`), il existe un risque supplémentaire de globalisation involontaire.
  • incapacité à faire la distinction entre les fichiers et les répertoires (c'est-à-dire si répertoires se trouvait parmi les 5 éléments du système de fichiers les plus récemment modifiés, vous conserveriez effectivement moins plus de 5 fichiers et en appliquant rm les répertoires échoueront).

réponse de Wnoise résout ces problèmes, mais la solution est GNOU-spécifique (et assez complexe).

Voici un pragmatique, Solution compatible POSIX qui vient avec seulement une mise en garde:il ne peut pas gérer les noms de fichiers avec des éléments intégrés nouvelles lignes - mais je ne considère pas que cela soit une préoccupation réelle pour la plupart des gens.

Pour mémoire, voici l'explication pourquoi ce n'est généralement pas une bonne idée d'analyser ls sortir: http://mywiki.wooledge.org/ParsingLs

ls -tp | grep -v '/$' | tail -n +6 | xargs -I {} rm -- {}

Ce qui précède est inefficace, parce que xargs doit invoquer rm une fois pour chaque nom de fichier.
Celle de votre plateforme xargs peut vous permettre de résoudre ce problème :

Si tu as GNOU xargs, utiliser -d '\n', ce qui rend xargs considérer chaque ligne d'entrée comme un argument distinct, mais transmettre autant d'arguments que peut en contenir une ligne de commande immediatement:

ls -tp | grep -v '/$' | tail -n +6 | xargs -d '\n' -r rm --

-r (--no-run-if-empty) s'assure que rm n'est pas invoqué s'il n'y a pas d'entrée.

Si tu as BSD xargs (y compris sur OS X), vous pouvez utiliser -0 gérer NUL-entrée séparée, après avoir d'abord traduit les nouvelles lignes en NUL (0x0) chars., qui transmet également (généralement) tous les noms de fichiers immediatement (fonctionnera également avec GNU xargs):

ls -tp | grep -v '/$' | tail -n +6 | tr '\n' '\0' | xargs -0 rm --

Explication:

  • ls -tp imprime les noms des éléments du système de fichiers triés selon leur date de modification récente, par ordre décroissant (les éléments les plus récemment modifiés en premier) (-t), avec des répertoires imprimés avec une fin / pour les marquer comme tels (-p).
  • grep -v '/$' puis élimine les répertoires de la liste résultante, en omettant (-v) les lignes qui ont une fin / (/$).
    • Mise en garde:Depuis un lien symbolique qui pointe vers un répertoire n'est techniquement pas lui-même un répertoire, de tels liens symboliques pas être exclu.
  • tail -n +6 saute le premier 5 entrées dans la liste, renvoyant en fait toutes mais les 5 fichiers les plus récemment modifiés, le cas échéant.
    Notez que pour exclure N des dossiers, N+1 doit être transmis à tail -n +.
  • xargs -I {} rm -- {} (et ses variantes) invoque alors sur rm sur tous ces dossiers ;s'il n'y a aucune correspondance, xargs ne fera rien.
    • xargs -I {} rm -- {} définit un espace réservé {} qui représente chaque ligne d'entrée dans son ensemble, donc rm est ensuite invoqué une fois pour chaque ligne d'entrée, mais avec les noms de fichiers avec des espaces incorporés gérés correctement.
    • -- dans tous les cas, garantit que tous les noms de fichiers commençant par - ne sont pas confondus avec choix par rm.

UN variation sur le problème initial, au cas où les fichiers correspondants devraient être traités individuellement ou collecté dans un tableau de shell:

# One by one, in a shell loop (POSIX-compliant):
ls -tp | grep -v '/$' | tail -n +6 | while IFS= read -r f; do echo "$f"; done

# One by one, but using a Bash process substitution (<(...), 
# so that the variables inside the `while` loop remain in scope:
while IFS= read -r f; do echo "$f"; done < <(ls -tp | grep -v '/$' | tail -n +6)

# Collecting the matches in a Bash *array*:
IFS=$'\n' read -d '' -ra files  < <(ls -tp | grep -v '/$' | tail -n +6)
printf '%s\n' "${files[@]}" # print array elements

Autres conseils

Supprimez tous les fichiers les plus récents d'un répertoire sauf 5 (ou quel que soit le nombre).

rm `ls -t | awk 'NR>5'`
(ls -t|head -n 5;ls)|sort|uniq -u|xargs rm

Cette version prend en charge les noms avec des espaces :

(ls -t|head -n 5;ls)|sort|uniq -u|sed -e 's,.*,"&",g'|xargs rm

Variante plus simple de la réponse de thelsdj :

ls -tr | head -n -5 | xargs --no-run-if-empty rm 

ls -tr affiche tous les fichiers, le plus ancien en premier (-t le plus récent en premier, -r reverse).

head -n -5 affiche toutes les lignes sauf les 5 dernières (c'est-à-dire les 5 fichiers les plus récents).

xargs rm appelle rm pour chaque fichier sélectionné.

find . -maxdepth 1 -type f -printf '%T@ %p\0' | sort -r -z -n | awk 'BEGIN { RS="\0"; ORS="\0"; FS="" } NR > 5 { sub("^[0-9]*(.[0-9]*)? ", ""); print }' | xargs -0 rm -f

Nécessite GNU find pour -printf et GNU sort pour -z, et GNU awk pour "\0", et GNU xargs pour -0, mais gère les fichiers avec des nouvelles lignes ou des espaces intégrés.

Toutes ces réponses échouent lorsqu'il y a des répertoires dans le répertoire courant.Voici quelque chose qui fonctionne :

find . -maxdepth 1 -type f | xargs -x ls -t | awk 'NR>5' | xargs -L1 rm

Ce:

  1. fonctionne lorsqu'il y a des répertoires dans le répertoire courant

  2. essaie de supprimer chaque fichier même si le précédent n'a pas pu être supprimé (en raison des autorisations, etc.)

  3. échoue lorsque le nombre de fichiers dans le répertoire actuel est excessif et xargs te baiserait normalement (le -x)

  4. ne prend pas en compte les espaces dans les noms de fichiers (peut-être utilisez-vous le mauvais système d'exploitation ?)

ls -tQ | tail -n+4 | xargs rm

Répertoriez les noms de fichiers par heure de modification, en citant chaque nom de fichier.Exclure les 3 premiers (les 3 plus récents).Retirez le reste.

EDIT après commentaire utile de mklement0 (merci !) :argument -n+3 corrigé, et notez que cela ne fonctionnera pas comme prévu si les noms de fichiers contiennent des nouvelles lignes et/ou si le répertoire contient des sous-répertoires.

Ignorer les nouvelles lignes, c'est ignorer la sécurité et un bon codage.wnoise avait la seule bonne réponse.Voici une variante de celui-ci qui place les noms de fichiers dans un tableau $x

while IFS= read -rd ''; do 
    x+=("${REPLY#* }"); 
done < <(find . -maxdepth 1 -printf '%T@ %p\0' | sort -r -z -n )

Si les noms de fichiers ne contiennent pas d'espaces, cela fonctionnera :

ls -C1 -t| awk 'NR>5'|xargs rm

Si les noms de fichiers contiennent des espaces, quelque chose comme

ls -C1 -t | awk 'NR>5' | sed -e "s/^/rm '/" -e "s/$/'/" | sh

Logique de base :

  • obtenir une liste des fichiers par ordre chronologique, une colonne
  • obtenez tout sauf les 5 premiers (n=5 pour cet exemple)
  • première version:envoie-les à rm
  • deuxième version :générer un script qui les supprimera correctement

Avec zsh

En supposant que vous ne vous souciez pas des répertoires présents et que vous n'aurez pas plus de 999 fichiers (choisissez un plus grand nombre si vous le souhaitez, ou créez une boucle while).

[ 6 -le `ls *(.)|wc -l` ] && rm *(.om[6,999])

Dans *(.om[6,999]), le . signifie des fichiers, le o signifie trier l'ordre, le m signifie par date de modification (mettre a pour le temps d'accès ou c pour le changement d'inode), le [6,999] choisit une plage de fichiers, donc ne rm le 5 en premier.

Je me rends compte que c'est un vieux fil de discussion, mais peut-être que quelqu'un en bénéficiera.Cette commande trouvera les fichiers dans le répertoire courant :

for F in $(find . -maxdepth 1 -type f -name "*_srv_logs_*.tar.gz" -printf '%T@ %p\n' | sort -r -z -n | tail -n+5 | awk '{ print $2; }'); do rm $F; done

C'est un peu plus robuste que certaines des réponses précédentes car cela permet de limiter votre domaine de recherche aux fichiers correspondant aux expressions.Tout d’abord, recherchez les fichiers correspondant aux conditions souhaitées.Imprimez ces fichiers avec les horodatages à côté d'eux.

find . -maxdepth 1 -type f -name "*_srv_logs_*.tar.gz" -printf '%T@ %p\n'

Ensuite, triez-les par horodatage :

sort -r -z -n

Ensuite, supprimez les 4 fichiers les plus récents de la liste :

tail -n+5

Saisissez la 2ème colonne (le nom du fichier, pas l'horodatage) :

awk '{ print $2; }'

Et puis résumez tout cela dans une instruction for :

for F in $(); do rm $F; done

C'est peut-être une commande plus détaillée, mais j'ai eu beaucoup plus de chance de pouvoir cibler des fichiers conditionnels et d'exécuter des commandes plus complexes sur eux.

trouvé un cmd intéressant dans Sed-Onliners - Supprimez les 3 dernières lignes - trouvez-le parfait pour une autre façon d'écorcher le chat (d'accord non) mais idée :

 #!/bin/bash
 # sed cmd chng #2 to value file wish to retain

 cd /opt/depot 

 ls -1 MyMintFiles*.zip > BigList
 sed -n -e :a -e '1,2!{P;N;D;};N;ba' BigList > DeList

 for i in `cat DeList` 
 do 
 echo "Deleted $i" 
 rm -f $i  
 #echo "File(s) gonzo " 
 #read junk 
 done 
 exit 0

Supprime tous les fichiers sauf les 10 derniers (les plus récents)

ls -t1 | head -n $(echo $(ls -1 | wc -l) - 10 | bc) | xargs rm

Si moins de 10 fichiers aucun fichier n'est supprimé et vous aurez :tête d'erreur :nombre de lignes illégales -- 0

Pour compter les fichiers avec bash

J'avais besoin d'une solution élégante pour la busybox (routeur), toutes les solutions xargs ou array m'étaient inutiles - aucune commande de ce type n'est disponible là-bas.find et mtime ne sont pas la bonne réponse car nous parlons de 10 éléments et pas nécessairement de 10 jours.La réponse d'Espo fut la plus courte, la plus claire et probablement la plus contradictoire.

Les erreurs avec les espaces et lorsqu'aucun fichier ne doit être supprimé sont toutes deux simplement résolues de la manière standard :

rm "$(ls -td *.tar | awk 'NR>7')" 2>&-

Version un peu plus pédagogique :Nous pouvons tout faire si nous utilisons awk différemment.Normalement, j'utilise cette méthode pour transmettre (retourner) des variables du awk au sh.Comme nous lisons tout le temps que cela ne peut pas être fait, je ne suis pas d’accord :voici la méthode.

Exemple pour les fichiers .tar sans problème concernant les espaces dans le nom du fichier.Pour tester, remplacez "rm" par "ls".

eval $(ls -td *.tar | awk 'NR>7 { print "rm \"" $0 "\""}')

Explication:

ls -td *.tar répertorie tous les fichiers .tar triés par heure.Pour appliquer à tous les fichiers du dossier actuel, supprimez la partie "d *.tar"

awk 'NR>7... saute les 7 premières lignes

print "rm \"" $0 "\"" construit une ligne :rm "nom du fichier"

eval l'exécute

Puisque nous utilisons rm, je n'utiliserais pas la commande ci-dessus dans un script !Une utilisation plus judicieuse est :

(cd /FolderToDeleteWithin && eval $(ls -td *.tar | awk 'NR>7 { print "rm \"" $0 "\""}'))

Dans le cas de l'utilisation ls -t la commande ne fera aucun mal sur des exemples aussi stupides que : touch 'foo " bar' et touch 'hello * world'.Non pas que nous créions jamais des fichiers avec de tels noms dans la vraie vie !

Note latérale.Si nous voulions passer une variable au sh de cette façon, nous modifierions simplement le print (forme simple, aucun espace toléré) :

print "VarName="$1

pour définir la variable VarName à la valeur de $1.Plusieurs variables peuvent être créées en une seule fois.Ce VarName devient une variable sh normale et peut être normalement utilisée par la suite dans un script ou un shell.Donc, pour créer des variables avec awk et les restituer au shell :

eval $(ls -td *.tar | awk 'NR>7 { print "VarName=\""$1"\""  }'); echo "$VarName"
leaveCount=5
fileCount=$(ls -1 *.log | wc -l)
tailCount=$((fileCount - leaveCount))

# avoid negative tail argument
[[ $tailCount < 0 ]] && tailCount=0

ls -t *.log | tail -$tailCount | xargs rm -f

J'en ai fait un script shell bash.Usage: keep NUM DIR où NUM est le nombre de fichiers à conserver et DIR est le répertoire à nettoyer.

#!/bin/bash
# Keep last N files by date.
# Usage: keep NUMBER DIRECTORY
echo ""
if [ $# -lt 2 ]; then
    echo "Usage: $0 NUMFILES DIR"
    echo "Keep last N newest files."
    exit 1
fi
if [ ! -e $2 ]; then
    echo "ERROR: directory '$1' does not exist"
    exit 1
fi
if [ ! -d $2 ]; then
    echo "ERROR: '$1' is not a directory"
    exit 1
fi
pushd $2 > /dev/null
ls -tp | grep -v '/' | tail -n +"$1" | xargs -I {} rm -- {}
popd > /dev/null
echo "Done. Kept $1 most recent files in $2."
ls $2|wc -l

Fonctionnant sur Debian (supposons que c'est la même chose sur les autres distributions que je reçois :RM :impossible de supprimer le répertoire `..'

ce qui est assez énervant..

Quoi qu'il en soit, j'ai modifié ce qui précède et ajouté grep à la commande.Dans mon cas, j'ai 6 fichiers de sauvegarde dans un répertoire, par ex.file1.tar file2.tar file3.tar etc et je souhaite supprimer uniquement le fichier le plus ancien (supprimer le premier fichier dans mon cas)

Le script que j'ai exécuté pour supprimer le fichier le plus ancien était :

LS -C1 -T | fichier grep | awk 'nr> 5' | xargs rm

Ceci (comme ci-dessus) supprime le premier de mes fichiers, par ex.file1.tar cela laisse également être avec file2 file3 file4 file5 et file6

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top