Mam plik CSV, który pracuję nad manipulowaniem sed. Co robię wkładanie prądu YYYY-MM-DD HH: MM: SS do piątego pola po adresie IP. Jak widać poniżej, każda wartość jest zamknięta przez podwójne cytaty, a każda kolumna CSV jest oddzielona przecinkiem.

"12345","","","None","192.168.2.1","qqq","000"
"67890","ABC-1234-5678","9.9","Low","192.168.2.1","qqq","000"

Korzystanie z polecenia: sed 'N;s/","/","YYYY-MM-DD HH:MM:SS","/5' FILENAME Dodam datę po piątej dziedzinie. Zwykle to działa, ale często Niektóre wartości w pliku CSV bądź zliczanie tej liczby, które włożyłoby datę do piątego pola. Aby zaradzić ten problem, jak mogę dodać tylko datę po piątej dziedzinie, ale także upewnij się, że 5th Field jest adresem IP?

Ostateczna wydajność powinna być:

"12345","","","None","192.168.2.1","YYYY-MM-DD HH:MM:SS","qqq","000"
"67890","ABC-1234-5678","9.9","Low","192.168.2.1","YYYY-MM-DD HH:MM:SS","qqq","000"

Proszę odpowiedzieć, jak to się robi za pomocą sed i nie awk. A jak mogę upewnić się, że piąte pole jest również adresem IP przed dodaniem daty?

2
Alby 17 luty 2017, 01:01

2 odpowiedzi

Najlepsza odpowiedź

Ta odpowiedź obecnie zakłada, że plik CSV jest pięknie spójny i prosty (jak w przypadku danych próbki), więc:

  • Pola zawsze mają podwójne cytaty wokół nich.
  • Nigdy nie są pola, takie jak "…""…", aby wskazać podwójny cytat osadzony w ciągu.
  • Nigdy nie są pola z przecinkami pomiędzy cytatami ({x0}}).

Biorąc pod uwagę te warunki wstępne, skrypt sed wykonuje zadanie:

sed 's/^\("[^"]*",\)\{4\}"\([0-9]\{1,3\}\.\)\{3\}[0-9]\{1,3\}",/&"YYYY-MM-DD HH:MM:SS",/'

Spliczmy ten wzór wyszukiwania na kawałki:

  • ^\("[^"]*",\)\{4\}

    Dopasuj początek linii, a następnie: 4 powtórzenia podwójnego cytatu, sekwencję zero lub więcej nie podwójnych cytatów, podwójnego cytatu i przecinka.

    Innymi słowy, to identyfikuje pierwsze cztery pola.

  • "\([0-9]\{1,3\}\.\)\{3\}

    Dopasuj podwójną cytat, a następnie 3 powtórzenia 1-3 cyfr dziesiętnych, a następnie kropkę - pierwsze trzy trojaczki adresu przerywkowego IPv4.

  • [0-9]\{1,3\}",

    Dopasuj 1-3 cyfry dziesiętne, a następnie podwójny cytat i przecinek - ostatnia triplet adresu przerywanego IPv4 oraz koniec pola.

Oczywiście, dla każdej idiosynkrazji plików CSV, z którymi musisz sobie poradzić, musisz zmodyfikować wyrażenia regularne. To nie jest trywialne.

Korzystając z rozszerzonych wyrażeń regularnych (włączonych przez -E na obu GNU i BSD {X1}}), możesz pisać:

sed -E 's/^("(([^"]*"")*[^"]*)",){4}"([0-9]{1,3}\.){3}[0-9]{1,3}",/&"YYYY-MM-DD HH:MM:SS",/'

Wzór rozpoznawania pierwszych 4 pól jest bardziej złożony niż wcześniej. Dopasowuje się 4 powtórzenia: podwójny cytat, zero lub więcej wystąpień {zero lub więcej nie podwójnych cytatów, a następnie dwa podwójne cytaty}, a następnie zero lub więcej nie podwójnych cytatów, a następnie podwójny cytat i przecinek.

Możesz także pisać to w klasycznym {x0}} (podstawowe wyrażenia regularne) z liberalnym posypaniem odwrotnych ukośników:

sed 's/^\("\(\([^"]*""\)*[^"]*\)",\)\{4\}"\([0-9]\{1,3\}\.\)\{3\}[0-9]\{1,3\}",/&"YYYY-MM-DD HH:MM:SS",/'

Biorąc pod uwagę plik danych:

"12345","","","None","192.168.2.1","qqq","000"
"67890","ABC-1234-5678","9.9","Low","192.168.2.1","qqq","000"
"23456","Quaternions","2.3","Pisces","Heredotus","qqq","000"
"34567","Commas, oh commas!","3.14159","""Quotes"" quoth he","192.168.99.37","zzz","011"
"45678","Commas, oh commas!","3.14159","""Quote me"",""or not""","192.168.99.37","zzz","011"

Pierwszy pokazany skrypt wytwarza wyjście:

"12345","","","None","192.168.2.1","YYYY-MM-DD HH:MM:SS","qqq","000"
"67890","ABC-1234-5678","9.9","Low","192.168.2.1","YYYY-MM-DD HH:MM:SS","qqq","000"
"23456","Quaternions","2.3","Pisces","Heredotus","qqq","000"
"34567","Commas, oh commas!","3.14159","""Quotes"" quoth he","192.168.99.37","zzz","011"
"45678","Commas, oh commas!","3.14159","""Quote me"",""or not""","192.168.99.37","zzz","011"

Pierwsze dwie linie są prawidłowo mapowane; Trzeci jest poprawnie niezmieniony, ale ostatnie dwa powinny być mapowane i nie.

Drugie i trzecie polecenia produkują:

"12345","","","None","192.168.2.1","YYYY-MM-DD HH:MM:SS","qqq","000"
"67890","ABC-1234-5678","9.9","Low","192.168.2.1","YYYY-MM-DD HH:MM:SS","qqq","000"
"23456","Quaternions","2.3","Pisces","Heredotus","qqq","000"
"34567","Commas, oh commas!","3.14159","""Quotes"" quoth he","192.168.99.37","YYYY-MM-DD HH:MM:SS","zzz","011"
"45678","Commas, oh commas!","3.14159","""Quote me"",""or not""","192.168.99.37","YYYY-MM-DD HH:MM:SS","zzz","011"

Należy pamiętać, że Heredotus nie jest modyfikowany (poprawnie), a ostatnie dwie linie otrzymują ciąg daty dodawania po adresie IP (poprawnie).

Te ostatnie wyrażenia regularne nie są dla słabych serca.

Oczywiście, jeśli chcesz nalegać, aby adresy IP pasują tylko liczby w zakresie 0..255 w każdym komponencie, bez prowadzenia 0, wtedy musisz wtedy, gdy adres IP pasujący do części wyrażenia regularnego. To może być zrobione; To nie jest ładne. Najłatwiej to zrobić z rozszerzonymi wyrażeniami regularnymi:

([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])

Używasz tego urządzenia zamiast jednego [0-9]{3} jednostki w wygenerach pokazanych wcześniej.

Zauważ, że nadal nie próbuje poradzić sobie z polami nie otoczonymi podwójnymi cytatami.

Nie określa również wartości do zastąpienia z polecenia date. To jest wykonalne z (jeśli nie, wtedy elementarne) rutynowe skryptowanie ostrożnie zarządzania cytatami:

dt=$(date +'%Y-%m-%d %H:%M:%S')
sed -E 's/^("(([^"]*"")*[^"]*)",){4}"([0-9]{1,3}\.){3}[0-9]{1,3}",/&"'"$dt"'",/'

Sekwencja '…"'"$dt"'",/' jest częścią tego, co zaczyna się jako ciąg jednowo cytowany. Pierwszy podwójny cytat to proste dane w ciągu; Następna pojedyncza cytat kończy cytowanie, "$dt" interpoluje wartość z date wewnątrz podwójnych cytatów Shell (więc przestrzeń nie powoduje żadnych problemów), wtedy pojedynczy cytat wznawia jednolity notacji, Dodawanie innego podwójnego cytatu, przecinka i slash przed ciągiem (argument do sed).

2
Jonathan Leffler 16 luty 2017, 23:54

Próbować:

awk -vdate1=$(date +"%Y-%m-%d") -vdate2=$(date +"%H:%M:%S") -F, '$5 ~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]/{$5=$5 FS date1 " " date2} 1' OFS=,   Input_file

Ponadto, jeśli chcesz edytować ten sam wprowadzenie_fil, możesz wykonać wyjście powyżej polecenia do pliku temp, a następnie ponownie zmień nazwę (polecenie MV) do tego samego wejścia_file

Dodawanie jednej postaci rozwiązania tylko teraz.

awk -vdate1=$(date +"%Y-%m-%d") -vdate2=$(date +"%H:%M:%S") -F, '
            $5 ~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]/{
            $5=$5 FS date1 " " date2
                                                }
            1
    '  OFS=,    Input_file
1
Jonathan Leffler 16 luty 2017, 23:44