awk: A correspondência exata da string no campo não funciona com NUL como separador de registros

awk: A correspondência exata da string no campo não funciona com NUL como separador de registros

Dado um arquivo com novas linhas nos campos (incorporados entre aspas duplas), tentei usar NUL como separador de registros e depois selecionar os registros desejados. Para isso substituí os finais das linhas por NUL e depois corrigi os campos divididos por uma nova linha (feito usando sed). No entanto, a correspondência exata do primeiro campo em (GNU) awkcom uma string falha. Curiosamente, uma correspondência de padrão de string no primeiro campo falha, o que me faz supor que RS="\x00"foi aplicado corretamente.

Por que isso falharia? Por que a correspondência de padrões funciona?

Arquivo de exemplo 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

A seleção de registros awkcom string exata antes de introduzir 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

A introdução de NUL e a correção de "divisões de nova linha" funcionam (observe a "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 uma correspondência de padrão para o campo 1 funciona (observe como "a"em outros campos falha, mas "head1"corresponde):

$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^@

NO ENTANTO: a correspondência exata "a"no campo 1 falha:

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

##<no output>##

Onde estou errado? Por que funciona antes de usar NUL como RS?

Responder1

Seu comando sed não está alterando novas linhas ( \n) para NULs ( \0), mas para NULs + novas linhas ( \0\n) (como cat -Amostra).

Ao usar GNU awk com RS definido como \0, o primeiro caractere de um registro subsequente (e de seu primeiro campo) será \n, o que quebrará sua correspondência exata.

E a 's/\(,"[^,"]*\)\x00/\1/'correção das divisões de nova linha não muda isso em nada - apenas anexa o newline",cregistro ao anterior.


Uma "solução" rápida e suja é definir RSem \0\nvez de apenas \0. Mas essa maneira de massagear arquivos csv para que possam ser analisados ​​pelo awk não é confiável, então você REALMENTE deve encontrar algo melhor.

Com seu último exemplo:

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

Responder2

Seu arquivo pode conter LFs no meio do campo com finais de linha CRLF, por exemplo, se tiver sido exportado do MS-Excel. Nesse caso, tudo que você precisa com gawk é:

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

Por exemplo (usando cat -vapenas para tornar os CRs visíveis 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

Se houver algum motivo pelo qual o procedimento acima não funcionará para você, consultehttps://stackoverflow.com/questions/45420535/whats-the-most-robust-way-to-efficiently-parse-csv-using-awkou baixe/use a extensão do analisador CSV gawks em gawkextlib.

Responder3

abordagem mista sed awk:

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

Comentários: o misto sed+awk Peguei seu código e ajustei-o levemente para obter os resultados desejados. A ideia principal é retirar as novas linhas que o sed invariavelmente coloca. Portanto, impedimos a impressão do sed após processar cada registro. Então, no eof, removemos as novas linhas e passamos esses dados delimitados por NUL para awk com NUL como separador de registros. Então simplesmente procuramos registros começando com a,

Saída:

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

Abaixo são fornecidos os métodos somente awk e somente sed. Eles contam com a cotação dentro de um campo citado para serem duplicados.

abordagem sed pura:

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

abordagem pura awk

$ 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

informação relacionada