Может ли awk работать с CSV-файлом, содержащим запятую внутри заключенного в кавычки поля?


Я использую awk для подсчета суммы одного столбца в csv-файле. Формат данных примерно такой:

id, name, value
1, foo, 17
2, bar, 76
3, "I am the, question", 99

Я использовал этот скрипт awk для подсчета суммы:

awk -F, '{sum+=$3} END {print sum}'

Часть значения в поле name содержит запятую, и это нарушает мой сценарий awk. Мой вопрос: Может ли awk решить эту проблему? Если да, то как я могу это сделать?

Спасибо.

11 20

11 ответов:

Вы пишете функцию в awk, как показано ниже:

$ awk 'func isnum(x){return(x==x+0)}BEGIN{print isnum("hello"),isnum("-42")}'
0 1

Вы можете включить в свой скрипт эту функцию и проверить, является ли третье поле числовым или нет.если не числовое, то переходите к 4-му полю, а если 4-е поле inturn не числовое, переходите к 5-му ...пока вы не достигнете числового значения.вероятно, здесь поможет петля, и добавьте ее к сумме.

Один из способов использования GNU awk и FPAT

awk 'BEGIN { FPAT = "([^, ]+)|(\"[^\"]+\")" } { sum+=$3 } END { print sum }' file.txt

Результат:

192

Вам, вероятно, лучше сделать это в perl с Text:: CSV, так как это быстрое и надежное решение.

Вы можете помочь awk работать с полями данных, содержащими запятые (или новые строки), используя небольшой скрипт, который я написал под названием csvquote. Он заменяет оскорбительные запятые внутри закавыченных полей непечатными символами. Если вам нужно, вы можете позже восстановить эти запятые - но в этом случае вам это не нужно.

Вот команда:

csvquote inputfile.csv | awk -F, '{sum+=$3} END {print sum}'

См. https://github.com/dbro/csvquote для кода

Для такого простого входного файла, как этот, вы можете просто написать небольшую функцию для преобразования всех реальных FSs вне кавычек в какое-то другое значение (я выбрал RS, так как разделитель записей не может быть частью записи), а затем использовать его в качестве FS, например:

$ cat decsv.awk
BEGIN{ fs=FS; FS=RS }

{
   decsv()

   for (i=1;i<=NF;i++) {
       printf "Record %d, Field %d is <%s>\n" ,NR,i,$i
   }
   print ""
}

function decsv(         curr,head,tail)
{
   tail = $0
   while ( match(tail,/"[^"]+"/) ) {
       head = substr(tail, 1, RSTART-1);
       gsub(fs,RS,head)
       curr = curr head substr(tail, RSTART, RLENGTH)
       tail = substr(tail, RSTART + RLENGTH)
   }
   gsub(fs,RS,tail)
   $0 = curr tail
}

$ cat file
id, name, value
1, foo, 17
2, bar, 76
3, "I am the, question", 99

$ awk -F", " -f decsv.awk file
Record 1, Field 1 is <id>
Record 1, Field 2 is <name>
Record 1, Field 3 is <value>

Record 2, Field 1 is <1>
Record 2, Field 2 is <foo>
Record 2, Field 3 is <17>

Record 3, Field 1 is <2>
Record 3, Field 2 is <bar>
Record 3, Field 3 is <76>

Record 4, Field 1 is <3>
Record 4, Field 2 is <"I am the, question">
Record 4, Field 3 is <99>

Это становится сложным только тогда, когда вам приходится иметь дело с внедренными новыми линиями и внедренными экранированными кавычками внутри котировок, и даже тогда это не слишком сложно, и все это было сделано раньше...

Смотрите Что самое главное надежный способ эффективного разбора CSV с помощью awk? для получения дополнительной информации.

Вы всегда можете решить проблему из первоисточника. Поместите кавычки вокруг поля имени, так же, как поле "я-вопрос". Это гораздо проще, чем тратить свое время на разработку обходных путей для этого.

Обновить (как просил Деннис). Простой пример

$ s='id, "name1,name2", value 1, foo, 17 2, bar, 76 3, "I am the, question", 99'

$ echo $s|awk -F'"' '{ for(i=1;i<=NF;i+=2) print $i}'
id,
, value 1, foo, 17 2, bar, 76 3,
, 99

$ echo $s|awk -F'"' '{ for(i=2;i<=NF;i+=2) print $i}'
name1,name2
I am the, question

Как вы можете видеть, установив разделитель в двойную кавычку, поля, принадлежащие к "кавычкам", всегда имеют четное число. Поскольку OP не может позволить себе роскошь изменять исходные данные, этот метод не будет к нему подходить.

Если вы точно знаете, что столбец 'value' всегда является последним столбцом:

awk -F, '{sum+=$NF} END {print sum}'

NF представляет число полей, поэтому $NF-последний столбец

Эта статья помогла мне решить эту же проблему с полем данных. Большинство CSV помещают кавычки вокруг полей с пробелами или запятыми внутри них. Это портит подсчет полей для awk, если вы не отфильтруете их.

Если вам нужны данные в тех полях, которые содержат мусор, это не для вас. ghostdog74 предоставил ответ, который опустошает это поле, но сохраняет общее количество полей в конце, что является ключом к обеспечению согласованности вывода данных. Мне не понравилось, как такое решение ввели новые линии. Это версия этого решения, которое я использовал. У первых трех полей никогда не было этой проблемы в данных. Четвертое поле, содержащее имя клиента, часто так и делалось, но мне нужны были эти данные. Остальные поля, которые показывают проблему, я мог бы выбросить без проблем, потому что они не были нужны в выводе моего отчета. Поэтому я сначала отсеял мусор 4-го поля очень конкретно и удалил первые два экземпляра кавычек. Затем я применяю то, что ghostdog74дал, чтобы очистить оставшиеся поля, которые есть запятые внутри них - это также удаляет кавычки, но я использую printfдля сохранения данных в одной записи. Я начинаю с 85 полей и заканчиваю с 85 полей во всех случаях из моих 8000+ строк грязных данных. Отличный результат!

grep -i $1 $dbfile | sed 's/\, Inc.//;s/, LLC.//;s/, LLC//;s/, Ltd.//;s/\"//;s/\"//' | awk -F'"' '{ for(i=1;i<=NF;i+=2) printf ($i);printf ("\n")}' > $tmpfile

Решение, которое опустошает поля с запятыми внутри них, но также поддерживает запись, конечно, является:

awk -F'"' '{ for(i=1;i<=NF;i+=2) printf ($i);printf ("\n")}

Огромное спасибо ghostdog74 за отличное решение!

NetsGuy256/

Я использую

`FPAT="([^,]+)|(\"[^\"]+\")" `

Определяют с полей поглазеть. Я обнаружил, что когда поле равно null, это не распознает правильное количество полей. Потому что " + " требует по крайней мере 1 символ в поле. Я изменил его на:

`FPAT="([^,]*)|(\"[^\"]*\")"`

И заменить "+" на "*". Он работает правильно.

Я также нахожу, что руководство пользователя GNU Awk также имеет эту проблему. https://www.gnu.org/software/gawk/manual/html_node/Splitting-By-Content.html

FPAT является элегантным решением, потому что он может обрабатывать страшные запятые в задаче кавычек, но суммировать столбец чисел в последнем столбце независимо от числа предыдущих разделителей, $NF работает хорошо:

awk -F"," '{sum+=$NF} END {print sum}'

Для доступа к предпоследнему столбцу вы можете использовать следующее:

awk -F"," '{sum+=$(NF-1)} END {print sum}'

Полноценные CSV-Парсеры, такие как Perl Text::CSV_XS, специально созданы для обработки такого рода странностей.

perl -MText::CSV_XS -lne 'BEGIN{$csv=Text::CSV_XS->new({allow_whitespace => 1})} if($csv->parse($_)){@f=$csv->fields();$sum+=$f[2]} END{print $sum}' file

allow_whitespace необходимо, так как входные данные имеют пробелы, окружающие разделители запятых. Очень старые версии Text::CSV_XS могут не поддерживать этот параметр.

Я дал более подробное объяснение Text::CSV_XS в моем ответе здесь: разбирать csv-файл с помощью gawk