2010-11-17 15 views
26

Mam plik csv, w którym każdy wiersz definiuje pomieszczenie w danym budynku. Wraz z pokojem, każdy rząd ma pole podłogowe. To, co chcę wydobyć, to wszystkie piętra we wszystkich budynkach.Analizowanie pliku csv za pomocą awk i ignorowanie przecinków w polu

Mój plik wygląda tak ...

"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 " 

Co chcę to wszystko Podłogi we wszystkich budynkach.

Używam cat, awk, sort i uniq, aby uzyskać tę listę, chociaż mam problem z "," w polu nazwy budynku, np. "BRICKER HALL, JOHN W" i zrzuca całą moją generowanie csv.

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

Jak mogę awk użyć przecinka, ale zignorować przecinek między "" pola? Czy ktoś może mieć lepsze rozwiązanie?

Na podstawie odpowiedzi pod warunkiem, sugerując parser awk csv udało mi się uzyskać rozwiązanie:

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

Nie chcemy korzystać z programu csv awk a następnie stamtąd chcę używać "-> 2 | " który jest formatowany na podstawie programu csv awk. Drugi plik $ 2 drukuje tylko zawartość przeanalizowaną przez CSV, ponieważ program wypisuje pierwotną linię, a następnie "-> #", gdzie # jest liczbą z pliku csv. (Tj. Kolumny). Stamtąd mogę podzielić ten wynik CSV awk na "|" co jest tym, co zastępuje przecinek. Następnie sortuj, dodawaj i wykopuj do pliku i gotowe!

Dzięki za pomoc.

Odpowiedz

7

Dodatkowe wyjście dostajesz od csv.awk jest z kodu demo. Zamierzone jest użycie funkcji w skrypcie do wykonania analizy, a następnie wyprowadzenia jej zgodnie z oczekiwaniami.

Pod koniec csv.awk znajduje się pętla { ... }, która demonstruje jedną z funkcji. To jest kod, który wyprowadza -> 2|.

Zamiast tego po prostu wywołaj funkcję parsowania i wykonaj print csv[1], csv[2].

Ta część kodu będzie wtedy wyglądać tak:

{ 
    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] 
    } 
} 

zapisać go jako your_script (na przykład).

Do chmod +x your_script.

I cat jest niepotrzebne. Możesz także wykonać sort -u zamiast sort | uniq.

Twój komenda będzie wtedy wyglądać tak:

./yourscript Buildings.csv | sort -u > floors.csv 
+0

działa to doskonale z wyjątkiem "csv druku [1], csv [2]" powinno być w rzeczywistości "CSV druku [0], csv [1]" Dzięki! – Chris

+0

Każdy pomysł, jak uzyskać awk, aby pozbyć się dodatkowych białych znaków na polach i nie używać stałej szerokości? "TEST LOTNISKA" Chcę być "TESTEM LOTNISKA" – Chris

+0

@ CHRIS: Czy białe znaki są osobnym pytaniem, ponieważ jeśli "drukuję csv [0], csv [1]" otrzymuję "0 00BDF" zamiast "TEST LOTNICZKI" ? –

4

Moje obejście jest rozebrać przecinki z pliku CSV, używając:

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

Oznacza to, że pierwszy otwór zastępczy cytaty z "((" i zamykanie cytuje za pomocą "))", następnie zamień "((" cokolwiek, cokolwiek "))" z "cokolwiek co", następnie zmień wszystkie pozostałe wystąpienia "((" i "))" z powrotem na ".

+4

Nie rozumiem, jak pomaga usuwanie przecinków z pliku CSV? – Chris

2

Możesz użyj skryptu, który napisałem c allv csvquote, aby awk ignorowało przecinki wewnątrz cytowanych pól. Komenda by następnie stać:

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

i cięcie może być nieco łatwiejsze niż awk do tego:

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

można znaleźć kod csvquote tutaj: https://github.com/dbro/csvquote

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

Jest niesamowite rozszerzenie GNU Awk 4, w którym definiujesz wzór pola zamiast deseń separatora pól. Czy cuda dla CSV. (docs)

ETA (dzięki Mitchus): Aby usunąć otaczające cytaty, gsub("^\"|\"$","",$3); jeśli jest więcej pól niż tylko $3 do przetworzenia w ten sposób, po prostu przepuść je.
Uwaga: to proste podejście nie jest tolerancyjne na zniekształcone dane wejściowe ani na niektóre znaki specjalne występujące między cytatami - obejmujące wszystkie te elementy wykraczałyby poza zakres zgrabnej jednolinijki.

+0

To jest świetne znalezisko! Sprawia, że ​​zewnętrzna biblioteka CSV jest niepotrzebna w wielu przypadkach. – MattK

+0

Niesamowite !! - czy można go zmodyfikować tak, aby wycinki były usuwane, jeśli są obecne. Mam dane wyjściowe, które mają tylko cytaty, jeśli przecinek występuje w samym polu. – nwaltham

+1

Tylko dla innych osób używających maców: OS X nie ma GAWK, ma awk z 2007 roku. Więc w zasadzie musisz go zainstalować samodzielnie 'brew install gawk' i naprawdę robi cuda dla CSV. –

0

W pełni funkcjonalne parsery CSV, takie jak Perl's Text::CSV_XS są specjalnie zaprojektowane do obsługi tego rodzaju dziwactwa.

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

Linia wejściowa jest podzielony na tablicy @f
Pole 1 $f[0] od Perl rozpoczyna indeksowanie w 0

wyjściowego:

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 

I, pod warunkiem więcej wyjaśnienie Text::CSV_XS w moim odpowiedź tutaj: parse csv file using gawk

0

Ponieważ problem polega na rozróżnianiu przecinka w polu CSV od tego, który oddziela pola, możemy zastąpić pierwszy przecinek czymś innym, aby łatwiej było parsować dalej, tj. Coś takiego:

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

Ten skrypt gawk (replace-comma.awk) robi:

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

ta wykorzystuje funkcję gawk że oddaje rzeczywisty separator rekordu do zmiennej o nazwie RT. Dzieli każdy znak na rekord i podczas czytania rekordów zastępujemy przecinek napotkany wewnątrz cytatu (\x022) za pomocą <comma>.

Rozwiązanie FPAT nie w jednym szczególnym przypadku, w którym oba uciekł cytaty i przecinek wewnątrz cudzysłowów, ale to rozwiązanie działa we wszystkich przypadkach, tj

§ 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 

Jako jedną wkładką do łatwego kopiowania i wklejania :

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