Czy istnieje pole, które przechowuje dokładny separator pola FS używany w wyrażeniu regularnym, odpowiednik RT dla RS?

Jan 04 2021

W GNU Awk 4.1.2 Record Split withgawk możemy przeczytać:

Kiedy RSjest pojedynczym znakiem, RTzawiera ten sam pojedynczy znak. Jednak gdy RSjest wyrażeniem regularnym, RTzawiera rzeczywisty tekst wejściowy, który był zgodny z wyrażeniem regularnym.

Ta zmienna RTjest bardzo przydatna w niektórych przypadkach .

Podobnie możemy ustawić wyrażenie regularne jako separator pól. Na przykład w tym miejscu zezwalamy, aby było to albo ";" lub „|”:

$ 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

Jeśli jednak chcemy ponownie spakować dane, nie mamy sposobu, aby dowiedzieć się, który separator pojawił się między dwoma polami. Więc jeśli w poprzednim przykładzie chcę przejrzeć pola i wydrukować je ponownie razem, używając FS, to w każdym przypadku wypisuje całe wyrażenie:

$ 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

Czy istnieje sposób na „przepakowanie” pól przy użyciu określonego separatora pól używanego do rozdzielenia każdego z nich, podobnie do tego, co pozwoliło na to RT?

(przykłady podane w pytaniu są dość proste, ale tylko po to, by pokazać, o co chodzi)

Odpowiedzi

8 anubhava Jan 04 2021 at 16:34

Czy istnieje sposób na „przepakowanie” pól przy użyciu określonego separatora pól używanego do podziału każdego z nich

Użycie gnu-awk split()tego ma dodatkowy czwarty parametr dla dopasowanego separatora przy użyciu podanego wyrażenia regularnego:

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

Bardziej czytelna wersja:

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"

Zwróć uwagę na czwarty sepsparametr, splitktóry przechowuje tablicę dopasowanego tekstu za pomocą wyrażenia regularnego używanego w trzecim parametrze, tj /[;|]/.

Oczywiście to nie jest tak proste, jak Short & RS, ORSi RT, co można zapisać jako:

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

Jak wspomina @anubhava , gawk ma split()(a patsplit()co jest do tego, FPATco split()trzeba FS- zobaczhttps://www.gnu.org/software/gawk/manual/gawk.html#String-Functions) robić, co chcesz. Jeśli chcesz mieć tę samą funkcjonalność z awk POSIX, to:

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

Zwróć uwagę na szczególną obsługę powyżej przypadku, w którym separator pól jest " "taki, że oznacza to dwie rzeczy różne od wszystkich innych wartości separatorów pól:

  1. Pola są w rzeczywistości oddzielone łańcuchami dowolnych białych znaków i
  2. Wiodący biały znak ma być zignorowany podczas zapełniania $ 1 (lub w tym przypadku flds [1]) i dlatego biały znak, jeśli istnieje, musi być przechwycony w sepach [0] `dla naszych celów, ponieważ wszystkie sepy [N] są skojarzone z poprzedzającymi go flds [N].

Na przykład uruchomienie powyższego na tych 3 plikach wejściowych:

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

==> file2 <==
hello how are_you

==> file3 <==
    hello how are_you

otrzymalibyśmy następujące dane wyjściowe, w których każde pole jest wyświetlane jako numer pola, a następnie wartość pola w obrębie, [...]a następnie separator wewnątrz <...>, wszystko w obrębie {...}(uwaga, że seps[0]jest wypełniony IFF FS, " "a rekord zaczyna się od spacji):

$ 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

Alternatywną opcją podziału jest użycie dopasowania, aby znaleźć separatory pól i wczytać je do tablicy:

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"