Un moyen rapide de supprimer des fichiers avec moins de x lignes

10

Qu'est-ce qu'un moyen rapide et pas trop compliqué de supprimer tous les fichiers d'un répertoire de moins de x lignes, en bash?

durrrutti
la source

Réponses:

10

Voici une solution POSIX qui devrait être assez simple à comprendre:

find . -type f -exec awk -v x=10 'NR==x{exit 1}' {} \; -exec echo rm -f {} \;

Comme dans la réponse de Stéphane , supprimez le echolorsque vous êtes satisfait de ce qui sera supprimé.


Explications, écrites pour ceux qui sont totalement nouveaux sur Unix / Linux:

Le point .représente le répertoire courant. findtrouve les fichiers et les répertoires de manière récursive à l'intérieur .et peut faire des choses avec eux.

-typeest l' un des findde » les primaires ; c'est un test qui sera effectué pour chaque fichier et répertoire qui se trouve récursivement (à l'intérieur .), et les autres primaires sur la ligne ne sont évaluées que si cela aboutit à "true".

Dans ce cas particulier, nous ne continuons que si nous avons affaire à un fichier normal , pas à un répertoire ou à quelque chose d'autre (par exemple, un périphérique bloc).


Le -execprimaire (de find) appelle une commande externe et ne passe au primaire suivant que si la commande externe se termine avec succès (état de sortie "0"). Le {}est remplacé par le nom de fichier "considéré" par la findcommande. Le premier -execappel équivaut donc à la commande shell suivante, exécutée tour à tour pour chaque fichier:

awk -v x=10 'NR==x{exit 1}' ./somefilename

Awk est une langue entière en soi, conçue pour gérer des fichiers texte délimités tels que les CSV. Les conditions et commandes Awk (qui sont contenues entre des guillemets simples et commencent par les lettres NR) sont exécutées pour chaque ligne d'un fichier texte. (Boucle implicite.)

Pour apprendre pleinement Awk, je recommande fortement le Tutoriel Grymoire , mais je vais expliquer les fonctionnalités Awk utilisées dans la commande ci-dessus.


Le -vdrapeau à Awk nous permet de définir une variable Awk (une fois) avant que les commandes Awk ne soient exécutées (pour chaque ligne du fichier.) Dans ce cas, nous définissons xsur 10.


NRest une variable particulière Awk se référant à la « N mbre de courant R ECORD. » En d'autres termes, c'est le numéro de ligne que nous regardons dans un passage particulier à travers la boucle.

(Notez qu'il est possible, bien que peu commun, d'utiliser un autre « R ECORD S eparator » que la valeur par défaut d'un retour à la ligne, par le réglage RS. Voici un exemple de jouer avec des séparateurs record. )


Les scripts awk en général se composent de conditions (en dehors des accolades) combinées avec des actions (à l'intérieur des accolades.) Il peut y avoir des conditions composées et des actions composées, et il y a une condition par défaut (true) et une action par défaut (print), mais nous n'avons pas besoin t'embête pas avec ça.

La condition ici est: "Est-ce la 10ème ligne?" Si tel est le cas, nous quittons avec un état de sortie différent de zéro, ce qui dans le script shell signifie «terminaison de commande infructueuse».

Ainsi, la seule façon dont cette commande Awk se terminera avec succès est si la fin du fichier est atteinte avant que la 10e ligne ne soit atteinte.

Donc, si le script Awk se termine avec succès, cela signifie que vous avez un fichier de moins de dix lignes.


Le prochain -execappel (si vous supprimez le echo) supprimera chaque fichier (qui ira aussi loin dans l'évaluation des findprimaires de) en exécutant:

rm -f ./somefilename
Caractère générique
la source
5

En supposant une findimplémentation qui prend en charge le -readableprédicat (si votre findne le prend pas en charge, supprimez-le simplement, vous obtiendrez simplement des messages d'erreur pour les fichiers non lisibles ou remplacez-les par -exec test -r {} \;):

x=10 find . -type f -readable -exec sh -c '
  for file do
    lines=$(wc -l < "$file") && [ "$((lines))" -lt "$x" ] && echo rm -f "$file"
  done' sh {} +

Retirez le echosi heureux.

Ce n'est pas particulièrement efficace en ce qu'elle compte toutes les lignes dans chaque fichier alors qu'il n'a besoin que d'arrêter au xth un et il court un wc(et peut -être une rm) commande pour chaque fichier.

Avec GNU awk, vous pouvez le rendre beaucoup plus efficace avec:

x=10
find . -type f -readable -exec awk -v x="$x" -v ORS='\0' '
  FNR == x {nextfile}
  ENDFILE {if (FNR < x) print FILENAME}' {} +|
  xargs -r0 echo rm -f

(encore une fois, retirez echolorsque vous êtes heureux).

La même chose avec perl:

x=10 find . -type f -readable -exec perl -Tlne '
  if ($. == $ENV{x}) {close ARGV}
  elsif (eof) {print $ARGV; close ARGV}' {} +

Remplacez printpar unlinksi heureux.

Stéphane Chazelas
la source
1. À quoi sert le dernier sh? 2. Est wc -l < "$file"plus rapide que wc -l "$file"? 3. Comment sh connaît-il la valeur de $x, qui est définie dans le shell appelant Bash?
3
@tomas, le dernier shest ce qui se passe dans ce script en ligne $0, à utiliser pour les messages d'erreur par exemple. wc -l "$file"afficherait le nom de fichier dont nous ne voulons pas ici et s'exécuterait wcmême si le fichier ne peut pas être ouvert. $xest exporté vers find( x=10 find...) qui lui-même le transmet sh.
Stéphane Chazelas
Merci! Mais je suppose que cette erreur que j'obtiens sur OSX signifie que ma version Bash ne prend pas en charge l'indicateur -readable? find: -readable: unknown primary or operator.
durrrutti
1
@durrrutti, ce n'est pas à la hauteur bash. bashest juste un interpréteur de ligne de commande, mais de l' findimplémentation. -readableest une extension GNU, non disponible sous OS / X find. Il est uniquement utilisé pour limiter les fichiers lisibles (vous ne pourrez pas obtenir le nombre de lignes pour les fichiers non lisibles). Vous pouvez l'omettre pour le premier, vous obtiendrez alors simplement des messages d'erreur lors de l'ouverture des fichiers wcpour les fichiers qui ne sont pas lisibles.
Stéphane Chazelas
@ StéphaneChazelas, cette réponse est tellement délicate que je me demande: ai-je raté des cas marginaux avec ma réponse? :)
Wildcard
2

Par souci d'exhaustivité, à part AWK, vous pouvez également utiliser GNU sed pour obtenir le même résultat:

find . -type f -exec sed 11q1 '{}' ';' -exec echo rm -f '{}' ';'

Ce qui donne une ligne de commande un peu plus concise.

Explication

11 - is the address, i.e. "the eleventh line"
q - is for _q_uit (abort the execution)
1 - is the exit code parameter for q (GNU sed extension) 
Zeppelin
la source