Existe-t-il un champ qui stocke le séparateur de champ exact FS utilisé dans une expression régulière, équivalent à RT pour RS?

Jan 04 2021

Dans le partage d' enregistrementgawk 4.1.2 de GNU Awk avec nous pouvons lire:

Quand RSest un caractère unique, RTcontient le même caractère unique. Cependant, when RSest une expression régulière, RTcontient le texte d'entrée réel qui correspond à l'expression régulière.

Cette variable RTest très utile dans certains cas .

De même, nous pouvons définir une expression régulière comme séparateur de champ. Par exemple, ici, nous autorisons qu'il soit ";" ou "|":

$ gawk -F';' '{print NF}' <<< "hello;how|are you" 2 # there are 2 fields, since ";" appears once $ gawk -F'[;|]' '{print NF}' <<< "hello;how|are you"
3  # there are 3 fields, since ";" appears once and "|" also once

Cependant, si nous voulons à nouveau compresser les données, nous n'avons aucun moyen de savoir quel séparateur est apparu entre deux champs. Donc, si dans l'exemple précédent je veux parcourir les champs et les imprimer à nouveau ensemble en utilisant FS, il imprime l'expression entière dans tous les cas:

$ gawk -F'[;|]' '{for (i=1;i<=NF;i++) printf ("%s%s", $i, FS)}' <<< "hello;how|are you"
hello[;|]how[;|]are you[;|]  # a literal "[;|]" shows in the place of FS

Existe-t-il un moyen de «reconditionner» les champs en utilisant le séparateur de champ spécifique utilisé pour diviser chacun d'eux, de la même manière que RT permettrait de faire?

(les exemples donnés dans la question sont assez simples, mais juste pour montrer le point)

Réponses

8 anubhava Jan 04 2021 at 16:34

Existe-t-il un moyen de "reconditionner" les champs en utilisant le séparateur de champ spécifique utilisé pour diviser chacun d'eux

L'utilisation de gnu-awk split()cela a un 4ème paramètre supplémentaire pour le délimiteur correspondant à l'aide de l'expression régulière fournie:

s="hello;how|are you"
awk 'split($0, flds, /[;|]/, seps) {for (i=1; i in seps; i++) printf "%s%s", flds[i], seps[i]; print flds[i]}' <<< "$s"

hello;how|are you

Une version plus lisible:

s="hello;how|are you"
awk 'split($0, flds, /[;|]/, seps) { for (i=1; i in seps; i++) printf "%s%s", flds[i], seps[i] print flds[i] }' <<< "$s"

Prenez note du 4ème sepsparamètre splitqui stocke un tableau de texte correspondant par expression régulière utilisée dans le 3ème paramètre ie /[;|]/.

Bien sûr, ce n'est pas aussi court et simple que RS, ORSet RT, qui peut s'écrire:

awk -v RS='[;|]' '{ORS = RT} 1' <<< "$s"
5 EdMorton Jan 04 2021 at 22:41

Comme @anubhava mentionne , gawk a split()(et patsplit()qui doit FPATen split()est de FS- voirhttps://www.gnu.org/software/gawk/manual/gawk.html#String-Functions) pour faire ce que vous voulez. Si vous voulez la même fonctionnalité avec un awk POSIX, alors:

$ cat tst.awk function getFldsSeps(str,flds,fs,seps, nf) { delete flds delete seps str = $0

    if ( fs == " " ) {
        fs = "[[:space:]]+"
        if ( match(str,"^"fs) ) {
            seps[0] = substr(str,RSTART,RLENGTH)
            str = substr(str,RSTART+RLENGTH)
        }
    }

    while ( match(str,fs) ) {
        flds[++nf] = substr(str,1,RSTART-1)
        seps[nf]   = substr(str,RSTART,RLENGTH)
        str = substr(str,RSTART+RLENGTH)
    }

    if ( str != "" ) {
        flds[++nf] = str
    }

    return nf
}

{
    print
    nf = getFldsSeps($0,flds,FS,seps)
    for (i=0; i<=nf; i++) {
        printf "{%d:[%s]<%s>}%s", i, flds[i], seps[i], (i<nf ? "" : ORS)
    }
}

Notez la gestion spécifique ci-dessus du cas où le séparateur de champ est " "parce que cela signifie 2 choses différentes de toutes les autres valeurs de séparateur de champ:

  1. Les champs sont en fait séparés par des chaînes de n'importe quel espace blanc, et
  2. L'espace blanc de début doit être ignoré lors du remplissage de $ 1 (ou flds [1] dans ce cas) et de sorte que l'espace blanc, s'il existe, doit être capturé dans seps [0] `pour nos besoins puisque chaque seps [N] est associé avec le flds [N] qui le précède.

Par exemple, exécutez ce qui précède sur ces 3 fichiers d'entrée:

$ head file{1..3}
==> file1 <==
hello;how|are you

==> file2 <==
hello how are_you

==> file3 <==
    hello how are_you

nous obtiendrions la sortie suivante où chaque champ est affiché comme le numéro de champ, puis la valeur du champ à l'intérieur, [...]puis le séparateur à l'intérieur <...>, le tout à l'intérieur {...}(notez que seps[0]IFF est le FS " "et que l'enregistrement commence par un espace blanc):

$ awk -F'[,|]' -f tst.awk file1
hello;how|are you
{0:[]<>}{1:[hello;how]<|>}{2:[are you]<>}

$ awk -f tst.awk file2 hello how are_you {0:[]<>}{1:[hello]< >}{2:[how]< >}{3:[are_you]<>} $ awk -f tst.awk file3
    hello how are_you
{0:[]<    >}{1:[hello]< >}{2:[how]< >}{3:[are_you]<>}
3 RamanSailopal Jan 04 2021 at 16:51

Une autre option pour fractionner consiste à utiliser match pour trouver les séparateurs de champs et les lire dans un tableau:

awk -F'[;|]' '{
    str=$0; # Set str to the line while (match(str,FS)) { # Loop through rach match of the field separator map[cnt+=1]=substr(str,RSTART,RLENGTH); # Create an array of the field separators str=substr(str,RSTART+RLENGTH) # Set str to the rest of the string after the match string } for (i=1;i<=NF;i++) { printf "%s%s",$i,map[i] # Loop through each record, printing it along with the field separator held in the array map.
    } 
    printf "\n" 
   }' <<< "hello;how|are you"