
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) awk
com 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 awk
com 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 -A
mostra).
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",c
registro ao anterior.
Uma "solução" rápida e suja é definir RS
em \0\n
vez 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 -v
apenas para tornar os CRs visíveis 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
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