Je veux imprimer toutes les lignes sauf les trois dernières lignes de l'entrée via awk uniquement. Veuillez noter que mon fichier contient n nombre de lignes.
Par exemple,
file.txt
contient,
foo
bar
foobar
barfoo
last
line
Je veux que la sortie soit,
foo
bar
foobar
Je sais que cela pourrait être possible grâce à la combinaison de tac
et sed
ou tac
et awk
$ tac file | sed '1,3d' | tac
foo
bar
foobar
$ tac file | awk 'NR==1{next}NR==2{next}NR==3{next}1' | tac
foo
bar
foobar
Mais je veux que la sortie par awk seulement.
C'est toujours aussi maladroit, mais vous pouvez ajouter chaque ligne à un tableau et à la fin - quand vous connaissez la longueur - afficher tout, sauf les 3 dernières lignes.
... | awk '{l[NR] = $0} END {for (i=1; i<=NR-3; i++) print l[i]}'
Une autre approche (plus efficace ici ) consiste à empiler manuellement trois variables:
... | awk '{if (a) print a; a=b; b=c; c=$0}'
a
n’imprime qu’après qu’une ligne a été déplacée de c
à b
, puis à a
, ce qui la limite à trois lignes. Les avantages immédiats sont qu'il ne stocke pas tout le contenu en mémoire et qu'il ne devrait pas causer de problèmes de mise en mémoire tampon (fflush()
après l'impression si c'est le cas), mais l'inconvénient est que ce n'est pas simple. Si vous voulez ignorer les 100 dernières lignes, vous avez besoin de 100 variables et de 100 variables.
Si awk avait les opérateurs Push
et pop
pour les tableaux, ce serait plus simple.
Ou nous pourrions pré-calculer le nombre de lignes et jusqu'où nous voulons aller avec $(($(wc -l < file) - 3))
. Ceci est relativement inutile pour le contenu en streaming mais sur un fichier, fonctionne plutôt bien:
awk -v n=$(($(wc -l < file) - 3)) 'NR<n' file
En règle générale, vous utiliseriez simplement head
si:
$ seq 6 | head -n-3
1
2
3
En utilisant repère de terdon , nous pouvons réellement voir comment ils se comparent. Je pensais cependant offrir une comparaison complète:
head
: 0.018s (moi)awk
+ wc
: 0.169s (moi)awk
3 variables: 0,178s (moi)awk
double-fichier: 0.322s (terdon)awk
buffer circulaire: 0.355s (vérificateur)awk
for-loop: 0.693s (me)La solution la plus rapide utilise un utilitaire C-optimisé comme head
ou wc
pour gérer les tâches lourdes, mais en pure awk
, la pile en rotation manuelle est roi pour le moment .
Pour une utilisation minimale de la mémoire, vous pouvez utiliser un tampon circulaire:
awk 'NR>n{print A[NR%n]} {A[NR%n]=$0}' n=3 file
En utilisant l'opérateur mod sur les numéros de ligne, nous avons au plus n entrées de tableau.
Prenons l'exemple de n = 3:
Sur la ligne 1 NR%n
est égal à 1, la ligne 2 en produit 2, la ligne 3 en indiquant 0 et la ligne 4 de nouveau à 1.
Line 1 -> A[1]
Line 2 -> A[2]
Line 3 -> A[0]
Line 4 -> A[1]
Line 5 -> A[2]
...
Quand nous arrivons à la ligne 4, A[NR%n]
contient le contenu de la ligne 1. Donc, cela s’imprime et A[NR%n]
obtient le contenu de la ligne 4. La ligne suivante (ligne 5) affiche le contenu original de la ligne 2, et ainsi de suite, jusqu’à ce que nous obtenions jusqu'à la fin. Ce qui reste non imprimé, c'est le contenu du tampon, qui contient les 3 dernières lignes ...
Vous pouvez également traiter le fichier deux fois pour éviter de conserver rien en mémoire:
awk '{if(NR==FNR){c++}else if(FNR<=c-3){print}}' file file
L'astuce ici est le test NR==FNR
. NR
est le numéro de ligne actuel et FNR
est le numéro de ligne actuel du fichier en cours. Si plusieurs fichiers sont passés en entrée, FNR
sera égal à NR
uniquement pendant le traitement du premier fichier. De cette façon, nous obtenons rapidement le nombre de lignes dans le premier fichier et l'enregistrons sous le nom c
. Puisque les "deux" fichiers sont en réalité le même, nous connaissons maintenant le nombre de lignes que nous voulons et nous n'imprimons que s'il s'agit de l'une d'entre elles.
Vous pensez peut-être que cela sera plus lent que les autres approches, mais en réalité, il est plus rapide car il n’ya pratiquement aucun traitement en cours. Tout est fait en utilisant les outils internes awk
(NR
et FNR
) en dehors d'une simple comparaison arithmétique. J'ai testé sur un fichier de 50 Mo avec un million de lignes créées avec cette commande:
for i in {500000..1000000}; do
echo "The quick brown fox jumped over the lazy dog $i" >> file;
done
Comme vous pouvez le constater, les temps sont presque identiques, mais l'approche que j'ai proposée ici est légèrement plus rapide que la première suggestion d'Oli (mais plus lente que les autres):
$ for i in {1..10}; do (
time awk '{if(NR==FNR){c++}else if(FNR<=c-3){print}}' file file > /dev/null ) 2>&1 |
grep -oP 'real.*?m\K[\d\.]+';
done | awk '{k+=$1}END{print k/10" seconds"}';
0.4757 seconds
$ for i in {1..10}; do (
time awk '{l[NR] = $0} END {for (i=1; i<=NR-3; i++) print l[i]}' file > /dev/null ) 2>&1 |
grep -oP 'real.*?m\K[\d\.]+';
done | awk '{k+=$1}END{print k/10" seconds"}';
0.5347 seconds
Je sais que la question portait spécifiquement sur awk
, mais par souci de brièveté, on pourrait toujours utiliser:
head -n -3