Les problèmes avec les réponses existantes:
- incapacité à gérer les noms de fichiers avec des espaces incorporés ou des retours à la ligne.
- dans le cas de solutions qui invoquent
rm
directement sur une commande substitution ( rm `...`
) sans guillemets , il y a un risque supplémentaire de globbing involontaire.
- l'incapacité de faire la distinction entre les fichiers et les répertoires (par exemple, si les répertoires se trouvaient parmi les 5 éléments du système de fichiers les plus récemment modifiés, vous conserveriez effectivement moins de 5 fichiers et l'application
rm
aux répertoires échouera).
La réponse de wnoise aborde ces problèmes, mais la solution est spécifique à GNU (et assez complexe).
Voici une solution pragmatique et conforme à POSIX qui ne comporte qu'une seule mise en garde : elle ne peut pas gérer les noms de fichiers avec des sauts de ligne intégrés - mais je ne considère pas cela comme une préoccupation du monde réel pour la plupart des gens.
Pour mémoire, voici l'explication des raisons pour lesquelles ce n'est généralement pas une bonne idée d'analyser la ls
sortie: http://mywiki.wooledge.org/ParsingLs
ls -tp | grep -v '/$' | tail -n +6 | xargs -I {} rm -- {}
Ce qui précède est inefficace , car xargs
doit être invoqué rm
une fois pour chaque nom de fichier.
Votre plateforme xargs
peut vous permettre de résoudre ce problème:
Si vous avez GNU xargs
, utilisez -d '\n'
, ce qui fait de xargs
chaque ligne d'entrée un argument distinct, tout en transmettant autant d'arguments que possible sur une ligne de commande à la fois :
ls -tp | grep -v '/$' | tail -n +6 | xargs -d '\n' -r rm --
-r
( --no-run-if-empty
) garantit que ce rm
n'est pas appelé s'il n'y a pas d'entrée.
Si vous avez BSD xargs
(y compris sur macOS ), vous pouvez utiliser -0
pour gérer les NUL
entrées séparées, après avoir d'abord traduit les sauts de ligne en NUL
( 0x0
) chars., Qui transmet également (généralement) tous les noms de fichiers à la fois (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, par ordre décroissant (les éléments les plus récemment modifiés en premier) ( -t
), avec les 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 un fin /
( /$
).
- Attention : comme un lien symbolique qui pointe vers un répertoire n'est techniquement pas lui-même un répertoire, de tels liens symboliques ne seront pas exclus.
tail -n +6
ignore les 5 premières entrées de la liste, renvoyant en fait tous les fichiers modifiés sauf les 5 derniers, le cas échéant.
Notez que pour exclure des N
fichiers, N+1
doit être passé à tail -n +
.
xargs -I {} rm -- {}
(et ses variantes) invoque alors rm
sur tous ces fichiers; 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 , il rm
est donc appelé 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 les noms de fichiers commençant par -
ne sont pas confondus avec les options de rm
.
Une variante du problème d'origine, au cas où les fichiers correspondants doivent être traités individuellement ou collectés dans un tableau 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
ls
pas dans le répertoire courant, les chemins d'accès aux fichiers contiendront «/», ce qui signifie quegrep -v '/'
cela ne correspondra à rien. Je crois quegrep -v '/$'
c'est ce que vous voulez exclure uniquement les répertoires.grep
commande plus claire sur le plan conceptuel. Notez, cependant, que le problème que vous décrivez n'aurait pas surgi avec un seul chemin de répertoire; par exemple,ls -p /private/var
n'imprimerait que de simples noms de fichiers. Ce n'est que si vous passez plusieurs arguments de fichier (généralement via un glob) que vous verrez les chemins réels dans la sortie; par exemple,ls -p /private/var/*
(et vous verriez également le contenu des sous-répertoires correspondants, sauf si vous l'avez également inclus-d
).Supprimez tous les fichiers les plus récents d'un répertoire, sauf 5 (ou n'importe quel nombre).
la source
ls -t
pourls -td *.bz2
ls -t | awk 'NR>1'
(je ne voulais que le plus récent). Merci!ls -t | awk 'NR>5' | xargs rm -f
si vous préférez les tubes et que vous devez supprimer l'erreur s'il n'y a rien à supprimer.touch 'hello * world'
, cela supprimerait absolument tout dans le répertoire actuel .Cette version prend en charge les noms avec des espaces:
la source
(ls -t|head -n 5;ls)
est un groupe de commandes . Il imprime deux fois les 5 fichiers les plus récents.sort
met ensemble des lignes identiques.uniq -u
supprime les doublons, de sorte que tous les fichiers sauf les 5 plus récents restent.xargs rm
appellerm
chacun d'eux.--no-run-if-empty
àxargs
comme(ls -t|head -n 5;ls)|sort|uniq -u|xargs --no-run-if-empty rm
s'il vous plaît mettre à jour la réponse.touch 'foo " bar'
supprimera tout le reste de la commande.xargs -d $'\n'
que d'injecter des guillemets dans votre contenu, bien que la délimitation NUL du flux d'entrée (qui nécessite d'utiliser autre chose quels
de vraiment bien faire) est l'option idéale.Variante plus simple de la réponse de thelsdj:
ls -tr affiche tous les fichiers, le plus ancien en premier (-t le plus récent en premier, -r inverse).
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é.
la source
-1
est la valeur par défaut lorsque la sortie est vers un pipeline, donc ce n'est pas obligatoire ici. Cela pose des problèmes beaucoup plus importants, liés au comportement par défautxargs
lors de l'analyse des noms avec des espaces, des guillemets, etc.--no-run-if-empty
n'est pas reconnu dans ma coquille. J'utilise Cmder sur Windows.-0
option si les noms de fichiers peuvent contenir des espaces. Je ne l'ai pas encore testé. sourceRequiert 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 retours à la ligne ou des espaces intégrés.
la source
awk
logique. Est-ce que je manque certaines exigences dans la question du PO qui le rendent nécessaire?while read -r -d ' '; IFS= -r -d ''; do ...
boucle shell - la première lecture se termine sur l'espace, tandis que la seconde passe au NUL.sed -z -e 's/[^ ]* //; 1,5d'
est le plus clair. (ou peut-êtresed -n -z -e 's/[^ ]* //; 6,$p'
.Toutes ces réponses échouent lorsqu'il y a des répertoires dans le répertoire courant. Voici quelque chose qui fonctionne:
Ce:
fonctionne quand il y a des répertoires dans le répertoire courant
essaie de supprimer chaque fichier même si le précédent n'a pas pu être supprimé (en raison des autorisations, etc.)
échoue en toute sécurité lorsque le nombre de fichiers dans le répertoire actuel est excessif et
xargs
vous foutrait normalement (le-x
)ne prend pas en charge les espaces dans les noms de fichiers (peut-être que vous utilisez le mauvais système d'exploitation?)
la source
find
renvoie plus de noms de fichiers que ce qui peut être passé sur une seule ligne de commandels -t
? (Astuce: vous obtenez plusieurs exécutions dels -t
, dont chacune n'est triée qu'individuellement, plutôt que d'avoir un ordre de tri globalement correct; ainsi, cette réponse est gravement cassée lors de l'exécution avec des répertoires suffisamment grands).Répertoriez les noms de fichiers par heure de modification, en citant chaque nom de fichier. Excluez les 3 premiers (3 les plus récents). Retirez le reste.
EDIT après un commentaire utile de mklement0 (merci!): Argument corrigé -n + 3, et notez que cela ne fonctionnera pas comme prévu si les noms de fichiers contiennent des nouvelles lignes et / ou le répertoire contient des sous-répertoires.
la source
-Q
option ne semble pas exister sur ma machine.-Q
. Oui,-Q
c'est une extension GNU (voici la spécification POSIXls
). Une petite mise en garde (rarement un problème en pratique):-Q
encode les nouvelles lignes intégrées dans les noms de fichiers comme littéral\n
, cerm
qui ne reconnaîtra pas. Pour exclure les 3 premiers , l'xargs
argument doit+4
. Enfin, une mise en garde qui s'applique à la plupart des autres réponses également: votre commande ne fonctionnera comme prévu que s'il n'y a pas de sous-répertoires dans le répertoire actuel.--no-run-if-empty
option:ls -tQ | tail -n+4 | xargs --no-run-if-empty rm
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 son qui met les noms de fichiers dans un tableau $ x
la source
IFS
- sinon vous risqueriez de perdre les espaces blancs de fin des noms de fichiers. Peut étendre cela à la commande de lecture:while IFS= read -rd ''; do
"${REPLY#* }"
?Si les noms de fichiers n'ont pas d'espaces, cela fonctionnera:
Si les noms de fichiers ont des espaces, quelque chose comme
Logique de base:
la source
while read
astuce pour gérer les espaces:ls -C1 -t | awk 'NR>5' | while read d ; do rm -rvf "$d" ; done
while IFS= read -r d
serait un peu mieux - le-r
empêche les littéraux de barre oblique inverse d'être consommés parread
, etIFS=
empêche le rognage automatique des espaces de fin.touch $'hello \'$(rm -rf ~)\' world'
; les guillemets littéraux à l'intérieur du nom de fichier contreraient les guillemets littéraux avec lesquels vous ajoutezsed
, ce qui entraînerait l'exécution du code dans le nom de fichier.| sh
formulaire, qui est celui avec la vulnérabilité d'injection de shell).Avec zsh
En supposant que vous ne vous souciez pas des répertoires actuels 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).
Dans
*(.om[6,999])
, les.
fichiers moyens, l'o
ordre de tri des moyens vers le haut, lesm
moyens par date de modification (misa
pour le temps d'accès ouc
pour le changement d'inode), le[6,999]
choisit une plage de fichiers, donc ne rm le 5 en premier.la source
om
) (tout tri que j'ai essayé n'a montré aucun effet - ni sur OSX 10.11.2 (essayé avec zsh 5.0.8 et 5.1.1) , ni sur Ubuntu 14.04 (zsh 5.0.2)) - que me manque-t-il?. En ce qui concerne le point final de la plage: pas besoin de coder en dur il, utilisez simplement de-1
se référer à la dernière entrée et inclut donc tous les fichiers restants:[6,-1]
.Je me rends compte que c'est un vieux fil, mais peut-être que quelqu'un en profitera. Cette commande trouvera les fichiers dans le répertoire courant:
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, trouvez les fichiers correspondant aux conditions souhaitées. Imprimez ces fichiers avec les horodatages à côté d'eux.
Ensuite, triez-les par horodatage:
Ensuite, supprimez les 4 fichiers les plus récents de la liste:
Prenez la 2ème colonne (le nom du fichier, pas l'horodatage):
Et puis enveloppez tout cela dans une déclaration for:
Cela peut être une commande plus verbeuse, mais j'ai eu beaucoup plus de chance de pouvoir cibler des fichiers conditionnels et d'exécuter des commandes plus complexes contre eux.
la source
trouvé 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 pas) mais idée:
la source
Supprime tous les fichiers sauf les 10 derniers (les plus récents)
Si moins de 10 fichiers aucun fichier n'est supprimé et vous aurez: tête d'erreur: nombre de lignes illégal - 0
Pour compter les fichiers avec bash
la source
J'avais besoin d'une solution élégante pour le busybox (routeur), tous les xargs ou solutions de baie étaient inutiles pour moi - aucune commande de ce type n'est disponible là-bas. find and mtime n'est pas la bonne réponse car nous parlons de 10 éléments et pas nécessairement de 10 jours. La réponse d'Espo était la plus courte et la plus propre et probablement la plus universelle.
Les erreurs avec les espaces et lorsqu'aucun fichier ne doit être supprimé sont simplement résolus de la manière standard:
Version un peu plus éducative: nous pouvons tout faire si nous utilisons awk différemment. Normalement, j'utilise cette méthode pour passer (renvoyer) des variables de awk au sh. Comme nous lisons tout le temps ce qui ne peut être fait, je prie de différer: voici la méthode.
Exemple pour les fichiers .tar sans problème concernant les espaces dans le nom de fichier. Pour tester, remplacez "rm" par "ls".
Explication:
ls -td *.tar
répertorie tous les fichiers .tar triés par heure. Pour appliquer à tous les fichiers du dossier courant, supprimez la partie "d * .tar"awk 'NR>7...
saute les 7 premières lignesprint "rm \"" $0 "\""
construit une ligne: rm "nom de fichier"eval
l'exécutePuisque nous utilisons
rm
, je n'utiliserais pas la commande ci-dessus dans un script! Une utilisation plus sage est:Dans le cas de l'utilisation de la
ls -t
commande ne fera aucun mal sur des exemples aussi stupides que:touch 'foo " bar'
ettouch 'hello * world'
. Non pas que nous ayons jamais créé des fichiers avec de tels noms dans la vraie vie!Sidenote. Si nous voulions passer une variable au sh de cette façon, nous modifierions simplement l'impression (forme simple, aucun espace toléré):
pour définir la variable
VarName
sur la valeur de$1
. Plusieurs variables peuvent être créées en une seule fois. CelaVarName
devient une variable sh normale et peut être normalement utilisée dans un script ou un shell par la suite. Donc, pour créer des variables avec awk et les rendre au shell:la source
la source
xargs
sans-0
ou au strict minimum-d $'\n'
n'est pas fiable; observez comment cela se comporte avec un fichier avec des espaces ou des guillemets dans son nom.J'en ai fait un script shell bash. Utilisation:
keep NUM DIR
où NUM est le nombre de fichiers à conserver et DIR est le répertoire à nettoyer.la source