
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) awk
con 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 awk
la 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 -A
se 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",c
registro al anterior.
Una "solución" rápida y sucia es configurar RS
en \0\n
lugar 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 -v
solo para hacer visibles los CR como ^M
s):
$ 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