Может ли awk иметь дело с CSV-файлом, который содержит запятую в кавычках? - PullRequest
23 голосов
/ 29 июня 2010

Я использую 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}'

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

Спасибо.

Ответы [ 11 ]

19 голосов
/ 18 октября 2012

В одну сторону, используя GNU awk и FPAT

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

Результат:

192
4 голосов
/ 30 июня 2010

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

3 голосов
/ 08 августа 2015

Я использую

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

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

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

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

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

3 голосов
/ 05 мая 2013

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

Вот команда:

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

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

3 голосов
/ 18 октября 2012

Для столь же простого входного файла, как вы, вы можете просто написать небольшую функцию для преобразования всех реальных FS вне кавычек в какое-то другое значение (я выбрал 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? для получения дополнительной информации.

2 голосов
/ 18 октября 2012

Эта статья помогла мне решить эту же проблему с полями данных.Большинство 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 /

2 голосов
/ 30 июня 2010

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

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

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

2 голосов
/ 29 июня 2010

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

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

$ 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

Как вы можете видеть, устанавливая разделитель на двойные кавычки, поля, которые принадлежат кавычкам, всегда находятся на четном числе.Поскольку у ОП нет такой роскоши, как модификация исходных данных, этот метод ему не подойдет.

1 голос
/ 03 ноября 2015

Полноценные парсеры 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

1 голос
/ 06 апреля 2014

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

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

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

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

...