Подтвердить что ты не робот

Разбор csv с использованием awk и игнорирование запятых внутри поля

У меня есть файл 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 и выход из файла в файл!

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

4b9b3361

Ответ 1

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

В конце 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

Ответ 2

gawk -vFPAT='[^,]*|"[^"]*"' '{print $1 "," $3}' | sort | uniq

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

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

Ответ 3

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

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

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

Ответ 5

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

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

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

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

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

Ответ 6

Полноценные синтаксические анализаторы 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

Ответ 7

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

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

Этот gawk script (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); }'