Elabora prima l'ultima riga usando awk


11

Ho un file di dati che voglio normalizzare utilizzando awk, basato sull'ultimo punto dati. Pertanto, vorrei accedere prima all'ultimo punto dati, per normalizzare i dati, quindi elaborarli normalmente.

Il seguente metodo, usando tacdue volte, fa il lavoro, ma è forse più complicato del necessario.

$ 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

La mia domanda è la seguente: è possibile ottenere il risultato sopra usando solo awk?

Penso che la risposta sia "No, awk scansiona il file riga per riga", ma sono aperto a suggerimenti per alternative.

Risposte:


5

Puoi farlo come soluzione a due passaggi in awk:

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

Se la tua versione di awk supporta il blocco ENDFILE (ad esempio GNU awk 4+), puoi farlo in questo modo:

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

Nota che è più efficiente alla seekfine del file vedere prima la risposta di Camh .

Spiegazione

Il primo esempio funziona ricordando il precedente $2, ovvero viene valutato solo quando il contatore di linea locale ( FNR) è uguale al contatore di linea globale ( NR). Il nextcomando salta alla riga successiva, in questo caso garantisce che l'ultimo blocco venga valutato solo quando viene analizzato il secondo argomento.

Il secondo esempio ha una logica simile, ma sfrutta il blocco ENDFILE che viene valutato quando viene raggiunta la fine di un file di input.


Il primo esempio funziona bene, il secondo no $ awk --version GNU Awk 3.1.8. Puoi forse aggiungere una spiegazione molto piccola su come vengono gestiti due file di input e cosa nextfa?
Bernhard,

1
@Bernhard: vedi modifica
Thor

6

Se l'origine dati è un file che può essere letto più volte (ovvero non è un flusso), è necessario innanzitutto utilizzare tail(1)per ottenere i dati desiderati dall'ultima riga e passarli in awk per l'elaborazione sequenziale del file. tailcercherà alla fine del file di leggere l'ultima riga senza dover leggere tutti i dati prima di esso.

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

Questa sarà una grande vittoria su file di grandi dimensioni in cui l'intero file non si adatta alla cache del buffer (il che significa che dovrebbe essere letto dal disco due volte, una volta per ogni passaggio) e aiuterà in misura minore non avendo bisogno di scansionare l'input per arrivare all'ultima riga. I file più piccoli potrebbero non mostrare molta differenza rispetto a un approccio a due passaggi.


3

È possibile caricarli in un array e leggerlo al contrario:

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

Potresti farlo in modo più efficiente, ma questo tipo di illustrazione illustra perché awknon è lo strumento giusto per questo. Continuare a utilizzare tacove disponibile, GNU tac è generalmente il più veloce tra una varietà di strumenti per questo lavoro.


Sono d'accordo, usare un for-loops in awknon è la soluzione.
Bernhard,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.