awk: La coincidencia exacta de cadena en el campo no funciona con NUL como separador de registros

awk: La coincidencia exacta de cadena en el campo no funciona con NUL como separador de registros

Dado un archivo con nuevas líneas en los campos (incrustadas entre comillas dobles), intenté usar NUL como separador de registros y luego seleccioné los registros deseados. Para esto, reemplacé los extremos de las líneas con NUL y luego corregí los campos divididos por una nueva línea (hecho usando sed). Sin embargo, falla la coincidencia exacta del primer campo en (GNU) awkcon una cadena. Curiosamente, falla una coincidencia de patrón de cadena en el primer campo, lo que me hace suponer que RS="\x00"se aplicó correctamente.

¿Por qué fallaría? ¿Por qué funciona la coincidencia de patrones?

Archivo de ejemplo input.txt:

head1,head2,head3
a,b,c
b,no a in first field,c
a,"with quotes",c
a,"with ,",c
b,a,1
a,"with
 newline",c
b,1,a

La selección de registros mediante awkla cadena exacta antes de introducir NUL funciona:

$awk 'BEGIN {FS=OFS=","} {if ($1=="a") print}' input.txt

Resultado:

a,b,c
a,"with quotes",c
a,"with ,",c
a,"with

La introducción de NUL y la corrección de "divisiones de nueva línea" funcionan (tenga en cuenta la "with\n newline"entrada):

$sed -e 's/$/\x00/' -e 's/\(,"[^,"]*\)\x00/\1/' input.txt | cat -A

head1,head2,head3^@$
a,b,c^@$
b,no a in first field,c^@$
a,"with quotes",c^@$
a,"with ,",c^@$
b,a,1^@$
a,"with$
 newline",c^@$
b,1,a^@$

Usar una coincidencia de patrón para a en el campo 1 funciona (observe cómo "a"en otros campos falla, pero "head1"coincide):

$sed -e 's/$/\x00/' -e 's/\(,"[^,"]*\)\x00/\1/' input.txt |
awk 'BEGIN {RS=ORS="\x00" ; FS=OFS=","}
     { if ($1~"a") print}' |
cat -A

head1,head2,head3^@$
a,b,c^@$
a,"with quotes",c^@$
a,"with ,",c^@$
a,"with$
 newline",c^@

SIN EMBARGO: la coincidencia exacta "a"del campo 1 falla:

sed -e 's/$/\x00/' -e 's/\(,"[^,"]*\)\x00/\1/' input.txt |
awk 'BEGIN {RS=ORS="\x00" ; FS=OFS=","} { if ($1=="a") print}' 

##<no output>##

¿Dónde me equivoco? ¿Por qué funciona antes de usar NUL como RS?

Respuesta1

Su comando sed no cambia las nuevas líneas ( \n) a NUL ( \0) sino a NUL + nuevas líneas ( \0\n) (como cat -Ase muestra).

Cuando use GNU awk con RS configurado en \0, el primer carácter de un registro posterior (y de su primer campo) será \n, lo que anulará su coincidencia exacta.

Y la 's/\(,"[^,"]*\)\x00/\1/'corrección de divisiones de nueva línea no cambia eso en absoluto: simplemente agrega el newline",cregistro al anterior.


Una "solución" rápida y sucia es configurar RSen \0\nlugar de solo \0. Pero esa forma de manipular archivos csv para que awk pueda analizarlos no es confiable, por lo que REALMENTE deberías encontrar algo mejor.

Con tu último ejemplo:

sed -e 's/$/\x00/' -e 's/\(,"[^,"]*\)\x00/\1/' input.txt |
gawk 'BEGIN {RS=ORS="\x00\n" ; FS=OFS=","} { if ($1=="a") print}' | cat -A
a,b,c^@$
a,"with quotes",c^@$
a,"with ,",c^@$
a,"with$
 newline",c^@$
sed -e 's/$/\x00/' -e 's/\(,"[^,"]*\)\x00/\1/' input.txt |
gawk 'BEGIN {RS="\x00\n" ; FS=OFS=","} { if ($1=="a") print}'
a,b,c
a,"with quotes",c
a,"with ,",c
a,"with
 newline",c

Respuesta2

Su archivo puede contener LF en el medio del campo con finales de línea CRLF, por ejemplo, si se exportó desde MS-Excel. En ese caso, todo lo que necesitas con gawk es:

awk 'BEGIN{RS=ORS="\r\n"; FPAT="[^,]*|(\"[^\"]*\")+"} $1=="a"' file

Por ejemplo (usando cat -vsolo para hacer visibles los CR como ^Ms):

$ cat -v file
head1,head2,head3^M
a,b,c^M
b,no a in first field,c^M
a,"with quotes",c^M
a,"with ,",c^M
b,a,1^M
a,"with
 newline",c^M
b,1,a^M

$ awk 'BEGIN{RS=ORS="\r\n"; FPAT="[^,]*|(\"[^\"]*\")+"} $1=="a"' file | cat -v
a,b,c^M
a,"with quotes",c^M
a,"with ,",c^M
a,"with
 newline",c^M

Si hay alguna razón por la cual lo anterior no funciona para usted, consultehttps://stackoverflow.com/questions/45420535/whats-the-most-robust-way-to-ficiently-parse-csv-using-awko descargue/use la extensión del analizador CSV de gawks en gawkextlib.

Respuesta3

enfoque mixto sed awk:

$ < file \
sed -e '
  s/$/\x00/
  s/\(,"[^,"]*\)\x00/\1/
  H;1h;$!d;g
  s/\x00\n/\x00/g
' |
awk '/^a,/' RS="\x00" -

Comentarios: la mezcla sed+awk. Tomé su código y lo modifiqué ligeramente para obtener los resultados deseados. La idea principal es eliminar las nuevas líneas que invariablemente pone sed. Por lo tanto, evitamos que sed se imprima después de procesar cada registro. Luego, al final, eliminamos las nuevas líneas y pasamos estos datos delimitados por NUL a awk con NUL como separador de registros. Luego simplemente buscamos registros que comiencen con a,

Producción:

a,b,c
a,"with quotes",c
a,"with ,",c
a,"with
 newline",c

A continuación se proporcionan los métodos solo awk y solo sed. Se basan en comillas dentro de un campo entrecomillado para duplicarlas.

enfoque sed puro:

$ sed -Ee ':a
    /^(([^"]*"){2})*[^"]*$/!{
      $d;N;ba
    }
    /^a,/!d
' file

enfoque puro y extraño

$ awk -F\" '
   !(NF%2){
      t = $0; n = NF
      while (getline a > 0) {
        t = t ORS a
        n = n + split(a, _x, FS)
        if (!(nf%2)) break 
      }
      $0 = t
   }/^a,/
' file

información relacionada