Slurp-mode in awk?


16

Strumenti come sed, awko perl -nelaborare loro un ingresso disco alla volta, i record essendo le linee di default.

Alcuni, come awkcon RS, GNU sedcon -zo perlcon -0ooopossono cambiare il tipo di record selezionando un diverso separatore di record.

perl -npuò rendere l'intero input (ogni singolo file quando viene passato più file) un singolo record con l' -0777opzione (o -0seguito da un numero ottale maggiore di 0377, 777 è quello canonico). Questo è ciò che chiamano la modalità slurp .

Può qualcosa di simile essere fatto con awks' RSo qualsiasi altro meccanismo? Dove awkelabora il contenuto di ciascun file nel suo complesso in ordine contrario a ciascuna riga di ciascun file?

Risposte:


15

È possibile adottare approcci diversi a seconda che si awktratti RSdi un singolo carattere (come awkfanno le implementazioni tradizionali ) o di un'espressione regolare (simile gawko simile mawk). Anche i file vuoti sono difficili da considerare perché awktendono a saltarli.

gawk, mawkO altre awkimplementazioni in cui RSpossono essere un espressione regolare.

In quelle implementazioni (per mawk, attenzione che alcuni sistemi operativi come Debian forniscono una versione molto vecchia invece di quella moderna gestita da @ThomasDickey ), se RScontiene un singolo carattere, il separatore di record è quel personaggio o awkentra nella modalità paragrafo quando RSè vuoto, o considera RSaltrimenti un'espressione regolare.

La soluzione è quella di utilizzare un'espressione regolare che non può essere abbinata. Alcuni vengono in mente come x^o $x( xprima dell'inizio o dopo la fine). Tuttavia alcuni (in particolare con gawk) sono più costosi di altri. Finora ho scoperto che ^$è il più efficiente. Può corrispondere solo su un input vuoto, ma non ci sarebbe nulla con cui confrontarsi.

Quindi possiamo fare:

awk -v RS='^$' '{printf "%s: <%s>\n", FILENAME, $0}' file1 file2...

Un avvertimento però è che salta i file vuoti (contrariamente a perl -0777 -n). Ciò può essere risolto con GNU awkinserendo invece il codice in ENDFILEun'istruzione. Ma dobbiamo anche ripristinare $0in un'istruzione BEGINFILE in quanto altrimenti non verrebbe ripristinato dopo l'elaborazione di un file vuoto:

gawk -v RS='^$' '
   BEGINFILE{$0 = ""}
   ENDFILE{printf "%s: <%s>\n", FILENAME, $0}' file1 file2...

awkimplementazioni tradizionali , POSIXawk

In quelli, RSè solo un personaggio, non hanno BEGINFILE/ ENDFILE, non hanno la RTvariabile, inoltre generalmente non possono elaborare il carattere NUL.

Si potrebbe pensare che l'utilizzo RS='\0'possa funzionare, dato che comunque non possono elaborare input che contiene il byte NUL, ma no, che RS='\0'nelle implementazioni tradizionali viene trattato come RS=, che è la modalità paragrafo.

Una soluzione può essere quella di utilizzare un personaggio che difficilmente si trova nell'input come \1. Nelle localizzazioni di caratteri multibyte, puoi persino renderle sequenze di byte che è molto improbabile che si verifichino poiché formano caratteri che non sono assegnati o non caratteri come $'\U10FFFE'nelle localizzazioni UTF-8. Non è assolutamente infallibile e hai anche un problema con i file vuoti.

Un'altra soluzione può essere quella di memorizzare l'intero input in una variabile e di elaborarlo nell'istruzione END alla fine. Ciò significa che è possibile elaborare solo un file alla volta:

awk '{content = content $0 RS}
     END{$0 = content
       printf "%s: <%s>\n", FILENAME, $0
     }' file

Questo è l'equivalente di sed:

sed '
  :1
  $!{
   N;b1
  }
  ...' file1

Un altro problema con questo approccio è che se il file non terminava con un carattere di nuova riga (e non era vuoto), $0alla fine ne viene aggiunto uno arbitrariamente (con gawk, si aggira il problema usando RTinvece che RSnel codice sopra). Un vantaggio è che hai un record del numero di righe nel file in NR/ FNR.


come per l'ultima parte ("se il file non terminava con un carattere di nuova riga (e non era vuoto), alla fine viene aggiunto arbitrariamente $ 0 alla fine"): per i file di testo, dovrebbero avere una fine nuova linea. vi ne aggiunge uno, ad esempio, e quindi modifica il file quando lo salvi. Non avere una nuova riga che termina fa alcuni comandi scartare l'ultima "riga" (es: wc) ma altri ancora "vedono" l'ultima riga ... ymmv. La tua soluzione è quindi valida, imo, se dovresti trattare i file di testo (che è probabilmente il caso, poiché awk è buono per l'elaborazione del testo ma non così buono per i binari ^^)
Olivier Dulac

1
tentare di slurpare all in potrebbe colpire alcune limitazioni ... apparentemente il awk tradizionale aveva (avere?) un limite di 99 campi su una linea ... quindi potrebbe essere necessario usare anche un FS diverso per evitare quel limite, ma potresti hai anche dei limiti su quanto può essere lunga la lunghezza totale di una linea (o del tutto, se riesci a metterla tutta su una linea)?
Olivier Dulac,

infine: un hack (sciocco ...) potrebbe essere quello di analizzare prima l'intero file e cercare un carattere che non è lì, quindi tr '\n' 'thatchar' il file prima di inviarlo a awk e tr 'thatchar' \n'l'output? (potrebbe essere necessario aggiungere ancora una nuova riga per assicurarsi, come ho notato sopra, che il tuo file di input abbia una nuova riga che termina: { tr '\n' 'missingchar' < thefile ; printf "\n" ;} | awk ..... | { tr 'missingchar' '\n' }(ma che aggiunge un '\ n' alla fine, potrebbe essere necessario liberarti di ... forse aggiungendo un sed prima del tr? finale se quel tr accetta file senza terminare le nuove righe ...)
Olivier Dulac

@OlivierDulac, il limite al numero di campi verrebbe raggiunto solo se si accedesse a NF o in qualsiasi altro campo. awknon fa la scissione se non lo facciamo. Detto questo, nemmeno il /bin/awkSolaris 9 (basato sul awk degli anni '70) aveva quella limitazione, quindi non sono sicuro che possiamo trovarne uno che (ancora possibile poiché l'Oawk di SVR4 aveva un limite di 99 e nawk 199, quindi è probabilmente il superamento di quel limite è stato aggiunto da Sun e potrebbe non essere trovato in altri awk basati su SVR4, puoi testare su AIX?).
Stéphane Chazelas,
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.