Gibt es ein Feld, in dem das genaue Feldtrennzeichen FS gespeichert ist, das in einem regulären Ausdruck verwendet wird, das RT für RS entspricht?

Jan 04 2021

In GNU Awks 4.1.2 Record Splitting mit könnengawk wir lesen:

Wann RSist ein einzelnes Zeichen, RTenthält das gleiche einzelne Zeichen. Wenn RSes sich jedoch um einen regulären Ausdruck handelt, RTenthält er den tatsächlichen Eingabetext, der mit dem regulären Ausdruck übereinstimmt.

Diese Variable RTist in einigen Fällen sehr nützlich .

Ebenso können wir einen regulären Ausdruck als Feldtrennzeichen festlegen. Zum Beispiel lassen wir hier zu, dass es entweder ";" oder "|":

$ 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

Wenn wir die Daten jedoch erneut packen möchten, können wir nicht feststellen, welches Trennzeichen zwischen zwei Feldern angezeigt wurde. Wenn ich also im vorherigen Beispiel die Felder durchlaufen und sie mithilfe von erneut zusammen drucken möchte, wird FSin jedem Fall der gesamte Ausdruck gedruckt :

$ 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

Gibt es eine Möglichkeit, die Felder mit dem speziellen Feldtrennzeichen, das zum Aufteilen der einzelnen Felder verwendet wird, neu zu verpacken, ähnlich wie dies bei RT möglich wäre?

(Die Beispiele in der Frage sind ziemlich einfach, aber nur um den Punkt zu zeigen)

Antworten

8 anubhava Jan 04 2021 at 16:34

Gibt es eine Möglichkeit, die Felder mit dem speziellen Feldtrennzeichen, das zum Teilen der einzelnen Felder verwendet wird, neu zu verpacken?

Wenn Sie dies verwenden gnu-awk split(), erhalten Sie einen zusätzlichen 4. Parameter für das übereinstimmende Trennzeichen unter Verwendung des mitgelieferten regulären Ausdrucks:

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

Eine besser lesbare Version:

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"

Beachten Sie den 4. sepsParameter, in splitdem ein Array von übereinstimmendem Text durch reguläre Ausdrücke gespeichert wird, die im 3. Parameter verwendet werden, d /[;|]/. H.

Natürlich ist es nicht so kurz und einfach wie RS, ORSund RT, die wie folgt geschrieben werden kann:

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

Wie @anubhava erwähnt , hat gawk split()(und patsplit()das ist so FPATwie es split()ist FS- zu sehenhttps://www.gnu.org/software/gawk/manual/gawk.html#String-Functions) um zu tun was du willst. Wenn Sie die gleiche Funktionalität mit einem POSIX awk wünschen, dann:

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

Beachten Sie die oben beschriebene spezifische Behandlung des Falles, in dem sich das Feldtrennzeichen befindet, " "da dies zwei Dinge bedeutet, die sich von allen anderen Feldtrennerwerten unterscheiden:

  1. Felder sind tatsächlich durch Ketten eines beliebigen Leerraums getrennt, und
  2. Führender Leerraum ist beim Auffüllen von $ 1 (oder in diesem Fall flds [1]) zu ignorieren, und dieser Leerraum muss, falls vorhanden, für unsere Zwecke in seps [0] `erfasst werden, da jeder seps [N] zugeordnet ist mit den flds [N], die davor stehen.

Führen Sie beispielsweise die folgenden Schritte für diese 3 Eingabedateien aus:

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

==> file2 <==
hello how are_you

==> file3 <==
    hello how are_you

Wir würden die folgende Ausgabe erhalten, in der jedes Feld als Feldnummer angezeigt wird, [...]dann der Feldwert innerhalb <...>und dann das Trennzeichen innerhalb (alles innerhalb {...}, wenn seps[0]IFF der FS ist " "und der Datensatz mit Leerzeichen beginnt):

$ 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

Eine alternative Option zum Teilen besteht darin, die Feldtrennzeichen mithilfe von match zu finden und in ein Array einzulesen:

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"