Existe um campo que armazena o separador de campo exato FS usado quando em uma expressão regular, equivalente a RT para RS?

Jan 04 2021

No GNU Awk's 4.1.2 Record Splitting withgawk podemos ler:

Quando RSé um único caractere, RTcontém o mesmo caractere único. No entanto, quando RSé uma expressão regular, RTcontém o texto de entrada real que corresponde à expressão regular.

Esta variável RTé muito útil em alguns casos .

Da mesma forma, podemos definir uma expressão regular como separador de campo. Por exemplo, aqui permitimos que seja ";" 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

No entanto, se quisermos empacotar os dados novamente, não temos como saber qual separador apareceu entre dois campos. Portanto, se no exemplo anterior eu quiser percorrer os campos e imprimi-los juntos novamente usando FS, ele imprime a expressão inteira em todos os casos:

$ 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 uma maneira de "reembalar" os campos usando o separador de campo específico usado para dividir cada um deles, da mesma forma que o RT permitiria fazer?

(os exemplos dados na pergunta são bastante simples, mas apenas para mostrar o ponto)

Respostas

8 anubhava Jan 04 2021 at 16:34

Existe uma maneira de "reembalar" os campos usando o separador de campo específico usado para dividir cada um deles

Usar gnu-awk split()isso tem um quarto parâmetro extra para o delimitador correspondente usando regex fornecido:

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

Uma versão mais legível:

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"

Observe o 4º sepsparâmetro splitque armazena uma matriz de texto correspondido por expressão regular usada no 3º parâmetro, isto é /[;|]/.

Claro que não é tão curto e simples como RS, ORSe RT, que pode ser escrito como:

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

Como @anubhava menciona , gawk tem split()(e patsplit()é para FPATcomo split()deve FS- verhttps://www.gnu.org/software/gawk/manual/gawk.html#String-Functions) para fazer o que quiser. Se você deseja a mesma funcionalidade com um POSIX awk, então:

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

Observe o tratamento específico acima do caso em que o separador de campo é " "porque isso significa duas coisas diferentes de todos os outros valores do separador de campo:

  1. Os campos são, na verdade, separados por cadeias de qualquer espaço em branco e
  2. O espaço em branco inicial deve ser ignorado ao preencher $ 1 (ou flds [1] neste caso) e, portanto, o espaço em branco, se existir, deve ser capturado em seps [0] `para nossos propósitos, uma vez que cada seps [N] está associado com o flds [N] que o precede.

Por exemplo, executando o acima nestes 3 arquivos de entrada:

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

==> file2 <==
hello how are_you

==> file3 <==
    hello how are_you

obteríamos a seguinte saída em que cada campo é exibido como o número do campo, o valor do campo dentro [...]e o separador dentro <...>, tudo dentro {...}(observe que seps[0]é preenchido o IFF do FS " "e o registro começa com um espaço em branco):

$ 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

Uma opção alternativa para dividir é usar match para encontrar os separadores de campo e lê-los em uma matriz:

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"