awk: Точное совпадение строки с полем не работает с NUL в качестве разделителя записей

awk: Точное совпадение строки с полем не работает с NUL в качестве разделителя записей

Учитывая файл с новыми строками в полях (встроенными в двойные кавычки), я попытался использовать 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 видимыми как ^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

Если есть какая-либо причина, по которой вышеизложенное не сработает для вас, см.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

Связанный контент