¿Existe un campo que almacene el separador de campo exacto FS que se usa en una expresión regular, equivalente a RT para RS?

Jan 04 2021

En GNU Awk 4.1.2 Record Splitting congawk podemos leer:

Cuando RSes un solo carácter, RTcontiene el mismo solo carácter. Sin embargo, cuando RSes una expresión regular, RTcontiene el texto de entrada real que coincide con la expresión regular.

Esta variable RTes muy útil en algunos casos .

Del mismo modo, podemos establecer una expresión regular como separador de campo. Por ejemplo, aquí permitimos que sea ";" 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

Sin embargo, si queremos empaquetar los datos nuevamente, no tenemos forma de saber qué separador apareció entre dos campos. Entonces, si en el ejemplo anterior quiero recorrer los campos e imprimirlos juntos nuevamente usando FS, imprime la expresión completa en todos los 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

¿Hay alguna manera de "volver a empaquetar" los campos usando el separador de campo específico usado para dividir cada uno de ellos, de manera similar a lo que RT permitiría hacer?

(los ejemplos dados en la pregunta son bastante simples, pero solo para mostrar el punto)

Respuestas

8 anubhava Jan 04 2021 at 16:34

¿Hay alguna manera de "volver a empaquetar" los campos usando el separador de campo específico que se usa para dividir cada uno de ellos?

Usar gnu-awk split()eso tiene un cuarto parámetro adicional para el delimitador coincidente usando la expresión regular proporcionada:

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 versión más legible:

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"

Tome nota del cuarto sepsparámetro splitque almacena una matriz de texto coincidente con la expresión regular utilizada en el tercer parámetro, es decir /[;|]/.

Por supuesto, no es tan breve y simple como RS, ORSy RT, que se puede escribir como:

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

Como menciona @anubhava , gawk tiene split()(y patsplit()que es FPATcomo split()es FS- verhttps://www.gnu.org/software/gawk/manual/gawk.html#String-Functions) para hacer lo que quieras. Si desea la misma funcionalidad con un awk POSIX, entonces:

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

Tenga en cuenta el manejo específico anterior del caso donde el separador de campo es " "porque eso significa 2 cosas diferentes de todos los demás valores de separador de campo:

  1. Los campos están realmente separados por cadenas de cualquier espacio en blanco, y
  2. El espacio en blanco inicial debe ignorarse cuando se completa $ 1 (o flds [1] en este caso) y, por lo tanto, el espacio en blanco, si existe, debe capturarse en seps [0] `para nuestros propósitos, ya que cada seps [N] está asociado con los campos [N] que le preceden.

Por ejemplo, ejecutando lo anterior en estos 3 archivos de entrada:

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

==> file2 <==
hello how are_you

==> file3 <==
    hello how are_you

obtendríamos la siguiente salida donde cada campo se muestra como el número de campo, luego el valor del campo dentro, [...]luego el separador dentro <...>, todo dentro {...}(tenga en cuenta que seps[0]está lleno IFF, el FS es " "y el registro comienza con un espacio en blanco):

$ 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

Una opción alternativa para dividir es usar la coincidencia para encontrar los separadores de campo y leerlos en una 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"