Traitez la dernière ligne en premier en utilisant awk

11

J'ai un fichier de données que je souhaite normaliser à l'aide awkdu dernier point de données. Pour cela, je voudrais d'abord accéder au dernier point de données, normaliser les données, puis traiter normalement.

La méthode suivante, utilisant tacdeux fois, fait le travail, mais est peut-être plus compliquée que nécessaire.

$ cat file
0 5
1 2
2 3
3 4
$ tac file | awk 'NR==1{norm=$2} {print $1, $2/norm}' | tac
0 1.25
1 0.5
2 0.75
3 1

Ma question est la suivante: est-il possible d'obtenir le résultat ci-dessus en utilisant uniquement awk?

Je pense que la réponse est "Non, awk scanne le fichier ligne par ligne", mais je suis ouvert aux suggestions d'alternatives.

Bernhard
la source

Réponses:

5

Vous pouvez le faire comme une solution à deux passes dans awk:

awk 'FNR == NR { n = $2; next } { print $1, $2/n }' infile infile

Si votre version de awk prend en charge le bloc ENDFILE (par exemple GNU awk 4+), vous pouvez le faire comme ceci:

awk 'ENDFILE { n = $2 } FNR != NR { print $1, $2/n }' infile infile

Notez qu'il est plus efficace de voir d'abord seekà la fin du fichier la réponse de camh .

Explication

Le premier exemple fonctionne en se souvenant du précédent $2, c'est-à-dire qu'il n'est évalué que lorsque le compteur de ligne local ( FNR) est égal au compteur de ligne global ( NR). La nextcommande passe à la ligne suivante, dans ce cas, elle garantit que le dernier bloc n'est évalué que lorsque le deuxième argument est analysé.

Le deuxième exemple a une logique similaire, mais tire parti du bloc ENDFILE qui est évalué lorsque la fin d'un fichier d'entrée est atteinte.

Thor
la source
Le premier exemple fonctionne bien, le second non $ awk --version GNU Awk 3.1.8. Pouvez-vous peut-être ajouter une très petite explication sur la façon dont deux fichiers d'entrée sont traités et que nextfait-on?
Bernhard
1
@Bernhard: voir edit
Thor
6

Si votre source de données est un fichier qui peut être lu plusieurs fois (c'est-à-dire qu'il ne s'agit pas d'un flux), vous devez d'abord utiliser tail(1)pour obtenir les données que vous souhaitez à partir de la dernière ligne et les transmettre à awk pour son traitement séquentiel du fichier. tailcherchera à la fin du fichier pour lire la dernière ligne sans avoir besoin de lire toutes les données avant lui.

awk -v norm=$(tail -n 1 file | cut -d' ' -f2) '{print $1, $2/norm}' file

Ce sera une grande victoire sur les gros fichiers où le fichier entier ne rentrera pas dans le cache du tampon (ce qui signifie qu'il devra être lu deux fois sur le disque, une fois pour chaque passage), et aidera dans une moindre mesure en n'ayant pas besoin de scanner l'entrée pour arriver à la dernière ligne. Les fichiers plus petits peuvent ne pas montrer beaucoup de différence avec une approche en deux passes.

camh
la source
3

Vous pouvez les charger dans un tableau et les lire à l'envers:

awk '{x[i++]=$0} END{for (j=i-1; j>=0;) print x[j--] }'

Vous pourriez le faire plus efficacement, mais ce genre d'illustration illustre pourquoi ce awkn'est pas le bon outil pour cela. Continuez à utiliser le taccas échéant, GNU tac est généralement le plus rapide parmi une variété d'outils pour ce travail.

Chris Down
la source
Je suis d'accord, l'utilisation d'un for-loops awkn'est pas la solution.
Bernhard