Разобрать CSV, используя awk и игнорируя запятые внутри поля - PullRequest
34 голосов
/ 17 ноября 2010

У меня есть CSV-файл, где каждая строка определяет комнату в данном здании. Наряду с комнатой, в каждом ряду есть пол поля. Что я хочу извлечь, так это все этажи во всех зданиях.

Мой файл выглядит так ...

"u_floor","u_room","name"
0,"00BDF","AIRPORT TEST            "
0,0,"BRICKER HALL, JOHN W    "
0,3,"BRICKER HALL, JOHN W    "
0,5,"BRICKER HALL, JOHN W    "
0,6,"BRICKER HALL, JOHN W    "
0,7,"BRICKER HALL, JOHN W    "
0,8,"BRICKER HALL, JOHN W    "
0,9,"BRICKER HALL, JOHN W    "
0,19,"BRICKER HALL, JOHN W    "
0,20,"BRICKER HALL, JOHN W    "
0,21,"BRICKER HALL, JOHN W    "
0,25,"BRICKER HALL, JOHN W    "
0,27,"BRICKER HALL, JOHN W    "
0,29,"BRICKER HALL, JOHN W    "
0,35,"BRICKER HALL, JOHN W    "
0,45,"BRICKER HALL, JOHN W    "
0,59,"BRICKER HALL, JOHN W    "
0,60,"BRICKER HALL, JOHN W    "
0,61,"BRICKER HALL, JOHN W    "
0,63,"BRICKER HALL, JOHN W    "
0,"0006M","BRICKER HALL, JOHN W    "
0,"0008A","BRICKER HALL, JOHN W    "
0,"0008B","BRICKER HALL, JOHN W    "
0,"0008C","BRICKER HALL, JOHN W    "
0,"0008D","BRICKER HALL, JOHN W    "
0,"0008E","BRICKER HALL, JOHN W    "
0,"0008F","BRICKER HALL, JOHN W    "
0,"0008G","BRICKER HALL, JOHN W    "
0,"0008H","BRICKER HALL, JOHN W    "

Что я хочу, так это все этажи во всех зданиях.

Я использую cat, awk, sort и uniq для получения этого списка, хотя у меня проблема с "," в поле названия здания, например "BRICKER HALL, JOHN W", и он сбрасывает весь мой CSV поколения.

cat Buildings.csv | awk -F, '{print $1","$2}' | sort | uniq > Floors.csv 

Как получить awk, чтобы использовать запятую, но игнорировать запятую между "" поля? Или у кого-нибудь есть лучшее решение?

На основании предоставленного ответа с предложением синтаксического анализатора awk csv я смог получить решение:

cat Buildings.csv | awk -f csv.awk | awk -F" -> 2|"  '{print $2}' | awk -F"|" '{print $2","$3}' | sort | uniq > floors.csv 

Там мы хотим использовать программу csv awk , а затем оттуда я хочу использовать "-> 2 |" который форматирует на основе программы CSV AWK. Печать $ 2 там печатает только проанализированное csv-содержимое, потому что программа печатает исходную строку, за которой следует «-> #», где # - это количество, проанализированное из csv. (То есть столбцы.) Оттуда я могу разделить этот результат awk csv на "|" который заменяет запятую. Затем сортировка, uniq и труба в файл и готово!

Спасибо за помощь.

Ответы [ 7 ]

37 голосов
/ 25 июня 2013
gawk -vFPAT='[^,]*|"[^"]*"' '{print $1 "," $3}' | sort | uniq

Это потрясающее расширение GNU Awk 4, в котором вы определяете шаблон поля вместо шаблона разделителя полей.Делает чудеса для CSV.( документы )

ETA (спасибо, mitchus): Чтобы удалить окружающие цитаты, gsub("^\"|\"$","",$3);если для обработки таким способом имеется больше полей, чем просто $3, просто выполните их обходразмах аккуратной однострочной.

9 голосов
/ 17 ноября 2010

Дополнительный вывод, который вы получаете от csv.awk, взят из демонстрационного кода. Предполагается, что вы используете функции в скрипте для анализа, а затем выводите его так, как хотите.

В конце csv.awk находится цикл { ... }, который демонстрирует одну из функций. Это тот код, который выводит -> 2|.

Вместо всего этого просто вызовите функцию синтаксического анализа и выполните print csv[1], csv[2].

Эта часть кода будет выглядеть следующим образом:

{
    num_fields = parse_csv($0, csv, ",", "\"", "\"", "\\n", 1);
    if (num_fields < 0) {
        printf "ERROR: %s (%d) -> %s\n", csverr, num_fields, $0;
    } else {
#        printf "%s -> ", $0;
#        printf "%s", num_fields;
#        for (i = 0;i < num_fields;i++) {
#            printf "|%s", csv[i];
#        }
#        printf "|\n";
        print csv[1], csv[2]
    }
}

Сохраните его как your_script (например).

До chmod +x your_script.

и cat не нужны. Также вы можете сделать sort -u вместо sort | uniq.

Ваша команда будет выглядеть так:

./yourscript Buildings.csv | sort -u > floors.csv
6 голосов
/ 21 декабря 2010

Мой обходной путь - убрать запятые из csv, используя:

decommaize () {
  cat $1 | sed 's/"[^"]*"/"((&))"/g' | sed 's/\(\"((\"\)\([^",]*\)\(,\)\([^",]*\)\(\"))\"\)/"\2\4"/g' | sed 's/"(("/"/g' | sed 's/"))"/"/g' > $2
}

То есть сначала замените открывающие кавычки на "((" и закрывающие кавычки на "))", затем подставьте "(("независимо от того, что "))" с помощью "бы то ни было", затем измените все оставшиеся экземпляры "((" и "))" обратно на ".

3 голосов
/ 17 ноября 2010

Вы можете попробовать этот psv на базе awkbased:

http://lorance.freeshell.org/csv/

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

Вы можете использовать скрипт, который я написал, с именем csvquote, чтобы позволить awk игнорировать запятые внутри полей в кавычках. Команда тогда станет:

csvquote Buildings.csv | awk -F, '{print $1","$2}' | sort | uniq | csvquote -u > Floors.csv

и cut может быть немного проще, чем awk для этого:

csvquote Buildings.csv | cut -d, -f1,2 | sort | uniq | csvquote -u > Floors.csv

Вы можете найти код csvquote здесь: https://github.com/dbro/csvquote

0 голосов
/ 12 декабря 2016

Поскольку проблема состоит в том, чтобы действительно отличить запятую внутри поля CSV от той, которая разделяет поля, мы можем заменить первый тип запятой на что-то другое, чтобы легче было анализировать дальше, то есть что-то вроде этого:

0,"00BDF","AIRPORT TEST            "
0,0,"BRICKER HALL<comma> JOHN W    "

Этот скрипт gawk (replace-comma.awk) делает следующее:

BEGIN { RS = "(.)" } 
RT == "\x022" { inside++; } 
{ if (inside % 2 && RT == ",") printf("<comma>"); else printf(RT); }

При этом используется функция gawk, которая захватывает фактический разделитель записей в переменную с именем RT.Он разбивает каждый символ на запись, и когда мы читаем записи, мы заменяем запятую, встречающуюся внутри кавычки (\x022), на <comma>.

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

§ echo '"Adams, John ""Big Foot""",1' | gawk -vFPAT='[^,]*|"[^"]*"' '{ print $1 }'
"Adams, John "
§ echo '"Adams, John ""Big Foot""",1' | gawk -f replace-comma.awk | gawk -F, '{ print $1; }'
"Adams<comma> John ""Big Foot""",1

Как однострочная для простой копирования-вставки:

gawk 'BEGIN { RS = "(.)" } RT == "\x022" { inside++; } { if (inside % 2 && RT == ",") printf("<comma>"); else printf(RT); }'
0 голосов
/ 14 ноября 2015

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

perl -MText::CSV_XS -lne 'BEGIN{$csv=Text::CSV_XS->new()} if($csv->parse($_)){ @f=$csv->fields(); print "$f[0],$f[1]" }' file

Входная строка разбита на массив @f
Поле 1 равно $f[0], поскольку Perl начинает индексирование с 0

выход:

u_floor,u_room
0,00BDF
0,0
0,3
0,5
0,6
0,7
0,8
0,9
0,19
0,20
0,21
0,25
0,27
0,29
0,35
0,45
0,59
0,60
0,61
0,63
0,0006M
0,0008A
0,0008B
0,0008C
0,0008D
0,0008E
0,0008F
0,0008G
0,0008H

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...