¿Existe un campo que almacene el separador de campo exacto FS que se usa en una expresión regular, equivalente a RT para RS?
En GNU Awk 4.1.2 Record Splitting congawk podemos leer:
Cuando
RS
es un solo carácter,RT
contiene el mismo solo carácter. Sin embargo, cuandoRS
es una expresión regular,RT
contiene el texto de entrada real que coincide con la expresión regular.
Esta variable RT
es 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
¿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 seps
parámetro split
que 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
, ORS
y RT
, que se puede escribir como:
awk -v RS='[;|]' '{ORS = RT} 1' <<< "$s"
Como menciona @anubhava , gawk tiene split()
(y patsplit()
que es FPAT
como 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:
- Los campos están realmente separados por cadenas de cualquier espacio en blanco, y
- 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]<>}
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"