
Учитывая файл с новыми строками в полях (встроенными в двойные кавычки), я попытался использовать NUL в качестве разделителя записей, а затем выбрать нужные записи. Для этого я заменил концы строк на NUL, а затем исправил поля, разделенные новой строкой (сделано с помощью sed
). Однако затем точное сопоставление первого поля в (GNU) awk
со строкой не удается. Интересно, что сопоставление шаблона строки в первом поле не удается, что заставляет меня предположить, что RS="\x00"
это правильно применено.
Почему это не сработает? Почему сопоставление с образцом работает?
Пример файла 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
Выбор записи с помощью awk
точной строки перед введением NUL работает:
$awk 'BEGIN {FS=OFS=","} {if ($1=="a") print}' input.txt
Результат:
a,b,c
a,"with quotes",c
a,"with ,",c
a,"with
Введение NUL и исправление "newline-splits" работает (обратите внимание на "with\n newline"
запись):
$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^@$
Использование сопоставления с шаблоном для поля 1 работает (обратите внимание, что "a"
в других полях сопоставление не работает, но "head1"
соответствует):
$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^@
ОДНАКО: точное совпадение для "a"
поля 1 не удалось:
sed -e 's/$/\x00/' -e 's/\(,"[^,"]*\)\x00/\1/' input.txt |
awk 'BEGIN {RS=ORS="\x00" ; FS=OFS=","} { if ($1=="a") print}'
##<no output>##
Где я не прав? Почему is работает до использования NUL as RS
?
решение1
Ваша команда sed не меняет символы новой строки ( \n
) на NUL ( \0
), а на NUL + символы новой строки ( \0\n
) (как cat -A
показано).
При использовании GNU awk с RS, установленным на \0
, первым символом последующей записи (и ее первого поля) будет \n
, что нарушит точное совпадение.
И 's/\(,"[^,"]*\)\x00/\1/'
исправление с помощью newline-splits ничего не меняет — оно просто добавляет newline",c
запись к предыдущей.
Быстрое и грязное «решение» — установить RS
вместо \0\n
просто \0
. Но этот способ обработки csv-файлов так, чтобы их можно было проанализировать с помощью awk, ненадежен, так что вам ДЕЙСТВИТЕЛЬНО следует найти что-то лучшее.
Ваш последний пример:
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
решение2
Ваш файл может содержать LF в середине поля с CRLF в конце строки, например, если он был экспортирован из MS-Excel. В этом случае все, что вам нужно с gawk, это:
awk 'BEGIN{RS=ORS="\r\n"; FPAT="[^,]*|(\"[^\"]*\")+"} $1=="a"' file
Например (используется cat -v
только для того, чтобы сделать CR видимыми как ^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
Если есть какая-либо причина, по которой вышеизложенное не сработает для вас, см.https://stackoverflow.com/questions/45420535/whats-the-most-robust-way-to-effectively-parse-csv-using-awkили загрузите/используйте расширение парсера CSV gawks в gawkextlib.
решение3
смешанный подход sed awk:
$ < file \
sed -e '
s/$/\x00/
s/\(,"[^,"]*\)\x00/\1/
H;1h;$!d;g
s/\x00\n/\x00/g
' |
awk '/^a,/' RS="\x00" -
Комментарии: смешанный sed+awk Я взял ваш код и немного подправил его, чтобы получить желаемые результаты. Основная идея в том, чтобы убрать символы новой строки, которые sed неизменно вставляет. Таким образом, мы удерживаем sed от печати после обработки каждой записи. Затем в конце файла мы удаляем символы новой строки и передаем эти данные, разделенные NUL, в awk с NUL в качестве разделителя записей. Затем мы просто ищем записи, начинающиеся с a,
Выход:
a,b,c
a,"with quotes",c
a,"with ,",c
a,"with
newline",c
Ниже приведены методы только awk и только sed. Они полагаются на кавычки внутри кавычек, которые будут удвоены.
чистый подход sed:
$ sed -Ee ':a
/^(([^"]*"){2})*[^"]*$/!{
$d;N;ba
}
/^a,/!d
' file
чистый подход 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