Esiste un campo che memorizza il separatore di campo esatto FS utilizzato in un'espressione regolare, equivalente a RT per RS?

Jan 04 2021

In GNU Awk 4.1.2 Record Splitting withgawk possiamo leggere:

Quando RSè un singolo carattere, RTcontiene lo stesso singolo carattere. Tuttavia, quando RSè un'espressione regolare, RTcontiene il testo di input effettivo che corrisponde all'espressione regolare.

Questa variabile RTè molto utile in alcuni casi .

Allo stesso modo, possiamo impostare un'espressione regolare come separatore di campo. Ad esempio, qui permettiamo che sia ";" o "|":

$ 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

Tuttavia, se vogliamo impacchettare nuovamente i dati, non abbiamo un modo per sapere quale separatore è apparso tra due campi. Quindi, se nell'esempio precedente voglio scorrere i campi e stamparli di nuovo insieme usando FS, stampa l'intera espressione in ogni caso:

$ 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

Esiste un modo per "reimballare" i campi utilizzando il separatore di campo specifico utilizzato per dividere ciascuno di essi, analogamente a ciò che RT consentirebbe di fare?

(gli esempi forniti nella domanda sono piuttosto semplici, ma solo per mostrare il punto)

Risposte

8 anubhava Jan 04 2021 at 16:34

C'è un modo per "reimballare" i campi utilizzando il separatore di campo specifico utilizzato per dividere ciascuno di essi

L'utilizzo di gnu-awk split()questo ha un quarto parametro aggiuntivo per il delimitatore corrispondente utilizzando l'espressione regolare fornita:

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

Una versione più leggibile:

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"

Prendi nota del quarto sepsparametro in splitquanto memorizza un array di testo abbinato tramite l'espressione regolare utilizzata nel terzo parametro, ad es /[;|]/.

Naturalmente non è così breve e semplice come RS, ORSe RT, che può essere scritto come:

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

Come menziona @anubhava , gawk ha split()(e patsplit()che è FPATcome split()è FS- vedihttps://www.gnu.org/software/gawk/manual/gawk.html#String-Functions) per fare quello che vuoi. Se desideri la stessa funzionalità con un awk POSIX, allora:

$ 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)
    }
}

Nota la gestione specifica sopra del caso in cui il separatore di campo è " "perché ciò significa 2 cose diverse da tutti gli altri valori di separatore di campo:

  1. I campi sono effettivamente separati da catene di qualsiasi spazio bianco e
  2. Lo spazio bianco iniziale deve essere ignorato quando si popola $ 1 (o flds [1] in questo caso) e quindi lo spazio bianco, se esiste, deve essere catturato in seps [0] `per i nostri scopi poiché ogni seps [N] è associato con il flds [N] che lo precede.

Ad esempio, eseguendo quanto sopra su questi 3 file di input:

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

==> file2 <==
hello how are_you

==> file3 <==
    hello how are_you

avremmo il seguente output in cui ogni campo viene visualizzato come il numero del campo, quindi il valore del campo all'interno, [...]quindi il separatore all'interno <...>, tutto all'interno {...}(nota che seps[0]è popolato IFF, FS è " "e il record inizia con uno spazio bianco):

$ 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

Un'opzione alternativa per dividere è usare match per trovare i separatori di campo e leggerli in un array:

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"