Slurp-mode dans awk?

16

Des outils comme sed, awkou perl -ntraiter leur une entrée enregistrement à la fois, les enregistrements étant lignes par défaut.

Certains, comme awkavec RS, GNU sedavec -zou perlavec -0ooopeuvent changer le type d'enregistrement en sélectionnant un séparateur d'enregistrement différent.

perl -npeut faire de l'entrée entière (chaque fichier individuel lorsque plusieurs fichiers sont passés) un seul enregistrement avec l' -0777option (ou -0suivi d'un nombre octal supérieur à 0377, 777 étant le numéro canonique). C'est ce qu'ils appellent le mode slurp .

Peut-on faire quelque chose de similaire avec awkle s RSou tout autre mécanisme? Où awktraite chaque contenu de fichier dans son ensemble par opposition à chaque ligne de chaque fichier?

Stéphane Chazelas
la source

Réponses:

15

Vous pouvez prendre différentes approches selon que awktraite RScomme un seul caractère (comme traditionnelles awkmises en œuvre font) ou comme une expression régulière (comme gawkou mawkfaire). Les fichiers vides sont également difficiles à considérer car ils ont awktendance à les ignorer.

gawk, mawkou d'autres awkimplémentations où RSpeut être une expression rationnelle.

Dans ces implémentations ( mawksachez que certains systèmes d'exploitation comme Debian livrent une version très ancienne au lieu de la version moderne maintenue par @ThomasDickey ), s'il RScontient un seul caractère, le séparateur d'enregistrement est ce caractère, ou awkpasse en mode paragraphe lorsqu'il RSest vide, ou traite RScomme une expression régulière dans le cas contraire.

La solution consiste à utiliser une expression régulière qui ne peut pas correspondre. Certains viennent à l'esprit comme x^ou $x( xavant le début ou après la fin). Cependant, certains (en particulier avec gawk) sont plus chers que d'autres. Jusqu'à présent, j'ai trouvé que ^$c'était le plus efficace. Il ne peut correspondre qu'à une entrée vide, mais il n'y aurait alors rien à comparer.

On peut donc faire:

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

Une mise en garde est cependant qu'il saute les fichiers vides (contrairement à perl -0777 -n). Cela peut être résolu avec GNU awken mettant le code dans une ENDFILEinstruction à la place. Mais nous devons également réinitialiser $0dans une instruction BEGINFILE car elle ne serait pas autrement réinitialisée après le traitement d'un fichier vide:

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

awkimplémentations traditionnelles , POSIXawk

Dans ceux-ci, il RSn'y a qu'un seul caractère, ils n'ont pas BEGINFILE/ ENDFILE, ils n'ont pas la RTvariable, ils ne peuvent généralement pas non plus traiter le caractère NUL.

On pourrait penser que l'utilisation RS='\0'pourrait fonctionner alors car de toute façon ils ne peuvent pas traiter l'entrée qui contient l'octet NUL, mais non, celle RS='\0'dans les implémentations traditionnelles est traitée comme RS=, qui est le mode paragraphe.

Une solution peut être d'utiliser un caractère qui ne se trouvera probablement pas dans l'entrée comme \1. Dans les paramètres régionaux de caractères multi-octets, vous pouvez même créer des séquences d'octets qui sont très peu susceptibles de se produire car ils forment des caractères qui ne sont pas affectés ou des caractères non comme $'\U10FFFE'dans les paramètres régionaux UTF-8. Pas vraiment infaillible et vous avez également un problème avec les fichiers vides.

Une autre solution peut être de stocker l'intégralité de l'entrée dans une variable et de la traiter dans l'instruction END à la fin. Cela signifie que vous ne pouvez traiter qu'un seul fichier à la fois:

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

C'est l'équivalent de sed:

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

Un autre problème avec cette approche est que si le fichier ne se terminait pas par un caractère de nouvelle ligne (et n'était pas vide), un est toujours ajouté arbitrairement $0à la fin (avec gawk, vous contourneriez cela en utilisant RTau lieu de RSdans le code ci-dessus). Un avantage est que vous avez un enregistrement du nombre de lignes dans le fichier dans NR/ FNR.

Stéphane Chazelas
la source
comme pour la dernière partie ("si le fichier ne se terminait pas par un caractère de nouvelle ligne (et n'était pas vide), on est quand même ajouté arbitrairement en $ 0 à la fin"): pour les fichiers texte, ils sont censés avoir une fin nouvelle ligne. vi en ajoute un, par exemple, et modifie ainsi le fichier lors de son enregistrement. Le fait de ne pas avoir de nouvelle ligne de terminaison fait qu'une commande rejette la dernière "ligne" (ex: wc) mais d'autres "voient" toujours la dernière ligne ... ymmv. Votre solution est donc valable, imo, si vous êtes censé traiter des fichiers texte (ce qui est probablement le cas, car awk est bon pour le traitement de texte mais pas si bon pour les binaires ^^)
Olivier Dulac
1
essayer de slurp tout peut frapper quelques limitations ... awk traditionnel avait apparemment (a?) une limite de 99 champs sur une ligne ... donc vous devrez peut-être aussi utiliser un FS différent pour éviter cette limite, mais vous pouvez ont également des limites sur combien de temps la longueur totale d'une ligne (ou le tout, si vous parvenez à obtenir tout cela sur une seule ligne) peut être?
Olivier Dulac
enfin: un (idiot ...) hack pourrait être de 1er analyser tout le fichier et chercher un caractère qui n'y est pas, puis tr '\n' 'thatchar' le fichier avant de l'envoyer à awk, et tr 'thatchar' \n'la sortie? (vous devrez peut-être encore ajouter une nouvelle ligne pour vous assurer, comme je l'ai noté ci-dessus, que votre fichier d'entrée a une nouvelle ligne de fin: { tr '\n' 'missingchar' < thefile ; printf "\n" ;} | awk ..... | { tr 'missingchar' '\n' }(mais qui ajoute un '\ n' à la fin, dont vous devrez peut-être vous débarrasser ... peut-être ajout d'un sed avant le tr final si ce tr accepte des fichiers sans terminer les sauts de ligne ...)
Olivier Dulac
@OlivierDulac, la limite du nombre de champs ne serait atteinte que si nous accédions à NF ou à n'importe quel champ. awkne fait pas le fractionnement si nous ne le faisons pas. Cela dit, même le /bin/awkSolaris 9 (basé sur le awk des années 1970) n'avait pas cette limitation, donc je ne suis pas sûr que nous puissions en trouver un qui le fait (toujours possible car l'oawk du SVR4 avait une limite de 99 et nawk 199, donc c'est la levée de cette limite a probablement été ajoutée par Sun et peut ne pas être trouvée dans d'autres awks basés sur SVR4, pouvez-vous tester sur AIX?).
Stéphane Chazelas