Jak manipulować plikiem CSV za pomocą sed lub awk?

23

Jak mogę wykonać następujące czynności dla pliku CSV za pomocą sedlub awk?

  • Usuń kolumnę
  • Duplikuj kolumnę
  • Przenieś kolumnę

Mam duży stół z ponad 200 rzędami i nie znam się tak dobrze sed.

Binoy Babu
źródło
1
Krzyż wysłany na AskUbuntu
enzotib,
@enzotib czy możesz opublikować link?
n0pe
@MaxMackie askubuntu.com/questions/88142/… . O tej porze nie mogę dostać modu, więc zgłosiłem go, prosząc o migrację, jeśli chcą; ma już zaakceptowaną odpowiedź, więc nie jestem pewien, czy to zrobią
Michał Mrożek
@MichaelMrozek, hmmm co zwykle dzieje się w takich sytuacjach? Czy po prostu przechowujemy duplikaty?
n0pe
1
Jeśli nie musisz uruchamiać w systemie, który ma tylko podstawowe narzędzia, zobacz Czy istnieje niezawodne narzędzie wiersza polecenia do przetwarzania plików csv?
Gilles „SO- przestań być zły”

Odpowiedzi:

7

Oprócz tego, jak wycinać i ponownie układać pola (omówione w innych odpowiedziach), istnieje problem dziwnych pól CSV.

Jeśli Twoje dane należą do tej „dziwacznej” kategorii, możesz zająć się tym trochę wstępnego i końcowego filtrowania. Filtry przedstawione poniżej wymagają znaki \x01, \x02, \x03, \x04aby nie pojawia się nigdzie w swoich danych.

Oto filtry owinięte wokół prostego awkzrzutu pola.

Uwaga: pole pięć ma niepoprawny / niekompletny układ „pola cytowanego”, ale jest łagodny na końcu wiersza (w zależności od parsera CSV). Ale, oczywiście, spowodowałoby to problematyczne nieużywane wyniki, gdyby miał zostać zamieniony z bieżącej pozycji na końcu rzędu .

Aktualizacja; użytkownik121196 wskazał błąd, gdy przecinek poprzedza końcowy cytat. Oto poprawka.

Dane

cat <<'EOF' >file
field one,"fie,ld,two",field"three","field,\",four","field,five
"15111 N. Hayden Rd., Ste 160,",""
EOF

Kod

sed -r 's/^/,/; s/\\"/\x01/g; s/,"([^"]*)"/,\x02\1\x03/g; s/,"/,\x02/; :MC; s/\x02([^\x03]*),([^\x03]*)/\x02\1\x04\2/g; tMC; s/^,// ' file |
  awk -F, '{ for(i=1; i<=NF; i++) printf "%s\n", $i; print NL}' |
    sed -r 's/\x01/\\"/g; s/(\x02|\x03)/"/g; s/\x04/,/g' 

Wyjście:

field one
"fie,ld,two"
field"three"
"field,\",four"
"field,five

"15111 N. Hayden Rd., Ste 160,"
""

Oto filtr wstępny , rozszerzony o komentarze. Filtr po to tylko odwrócenie . , ,
\x01\x02\x03\x04

sed -r '
    s/^/,/                # add a leading comma delimiter
    s/\\"/\x01/g          # obfuscate escaped quotation-mark (\")
    s/,"([^"]*)"/,\x02\1\x03/g    # obfuscate quotation-marks
    s/,"/,\x02/           # when no trailing quote on last field  
    :MC                   # obfuscate commas embedded in quotes
    s/\x02([^\x03]*),([^\x03]*)/\x02\1\x04\2/g
    tMC
    s/^,//                # remove spurious leading delimiter
'
Peter.O
źródło
jak usunąłbyś n-tą kolumnę w oparciu o ten filtr?
user121196,
@ user121196 - Jak wspomniano w zdaniu otwierającym, ta odpowiedź pokazuje sposób na zwiększenie spójności danych CSV. np. zastępując tymczasowo przecinek umieszczony w cudzysłowie neutralnym znakiem tokena ... a następnie przenosząc go z powrotem do przecinka po przeniesieniu / wycięciu / usunięciu. Ponownie, jak wspomniano, krok przenoszenia / wycinania / usuwania jest zastępowany zwykłym zrzutem pola awk .
Peter.O
1
nie udaje się w tym przypadku: „15111 N. Hayden Rd., Ste 160,”, „”
121196
@ user121196: Dziękujemy za zwrócenie na to uwagi. Zaktualizowałem odpowiedź z poprawką.
Peter.O,
15

Zależy to od tego, czy plik CSV używa przecinków tylko do ograniczników, czy też masz szaleństwo, takie jak:

pole pierwsze, „pole dwa”, pole trzecie

Zakłada się, że używasz prostego pliku CSV:

Usuwanie kolumny

Możesz pozbyć się jednej kolumny na wiele sposobów; Jako przykład użyłem kolumny 2. Najłatwiejszym sposobem jest prawdopodobnie użycie cut, które pozwala określić ogranicznik -di które pola chcesz wydrukować -f; nakazuje to podział na przecinki i pole wyjściowe 1, a pola 3 do końca:

$ cut -d, -f1,3- /path/to/your/file

Jeśli faktycznie potrzebujesz użyć sed, możesz napisać wyrażenie regularne pasujące do pierwszych n-1pól, pola nth i reszty, i pomiń wypisywanie nth (tutaj njest 2, więc pierwsza grupa dopasowuje 1czas :) \{1\}:

$ sed 's/\(\([^,]\+,\)\{1\}\)[^,]\+,\(.*\)/\1\3/' /path/to/your/file

Istnieje wiele sposobów, aby to zrobić awk, żaden z nich nie jest szczególnie elegancki. Możesz użyć forpętli, ale radzenie sobie z przecinkiem końcowym jest uciążliwe; ignorując, że byłoby to coś takiego:

$ awk -F, '{for(i=1; i<=NF; i++) if(i != 2) printf "%s,", $i; print NL}' /path/to/your/file

Uważam, że łatwiej jest wyprowadzić pole 1, a następnie użyć substrdo wyciągnięcia wszystkiego po polu 2:

$ awk -F, '{print $1 "," substr($0, length($1)+length($2)+3)}' /path/to/your/file

Jest to denerwujące dla kolumn dalej

Duplikowanie kolumny

W sedten jest zasadniczo taki sam wyraz twarzy jak poprzednio, ale także przechwytywać kolumnę docelową i uwzględnić tę grupę wielokrotnie w wymianie:

$ sed 's/\(\([^,]\+,\)\{1\}\)\([^,]\+,\)\(.*\)/\1\3\3\4/' /path/to/your/file

W awkprzypadku pętli for byłoby to coś w stylu (ponownie ignorując przecinek końcowy):

$ awk -F, '{
for(i=1; i<=NF; i++) {
    if(i == 2) printf "%s,", $i;
    printf "%s,", $i
}
print NL
}' /path/to/your/file

substrSposób:

$ awk -F, '{print $1 "," $2 "," substr($0, length($1)+2)}' /path/to/your/file

(tcdyl wymyślił lepszą metodę w swojej odpowiedzi )

Przenoszenie kolumny

Myślę, że sedrozwiązanie wynika naturalnie z pozostałych, ale zaczyna robić się absurdalnie długie

Michał Mrożek
źródło
To pełna odpowiedź! +1 :)
jaypal singh
Śmiesznie długo? Pa !
Gilles „SO- przestań być zły”
12

awkto twój najlepszy zakład. awkdrukuje pola według numeru, więc ...

awk 'BEGIN { FS=","; OFS=","; } {print $1,$2,$3}' file

Aby usunąć kolumnę, nie drukuj jej:

 awk 'BEGIN { FS=","; OFS=","; } {print $1,$3}' file

Aby zmienić kolejność:

awk 'BEGIN { FS=","; OFS=","; } {print $3,$1,$2}' file

Przekieruj do pliku wyjściowego.

awk 'BEGIN { FS=","; OFS=","; } {print $3,$1,$2}' file > output.file

awk może również sformatować dane wyjściowe.

Wyjście w formacie awk

Pantera
źródło
Ponieważ jest to CSV, będziesz także potrzebować BEGIN { FS=","; OFS=","; }.
1
Myślę, że nawet FS = OFS = "," zadziała.
5

Biorąc pod uwagę plik rozdzielany spacjami w następującym formacie:

1 2 3 4 5

Możesz usunąć pole 2 za pomocą awk tak:

awk '{ sub($2,""); print}' file

który zwraca

1  3 4 5

W razie potrzeby zastąp kolumnę 2 kolumną n.

Aby powielić kolumnę 2,

awk '{ col = $2 " " $2; $2 = col; print }' file

który zwraca

1 2 2 3 4 5

Aby przełączyć kolumny 2 i 3,

awk '{temp = $2; $2 = $3; $3 = temp; print}'

który zwraca

1 3 2 4 5

awk jest ogólnie bardzo dobry w radzeniu sobie z koncepcją pól . Jeśli masz do czynienia z plikiem CSV, a nie plikiem rozdzielanym spacjami, możesz po prostu użyć

awk -F,

aby zdefiniować pole jako przecinek zamiast spacji (która jest domyślna). Istnieje wiele dobrych zasobów awk online, z których jeden wymieniłem jako źródło poniżej.

Źródło dla # 3

tcdyl
źródło
Niewiele wiem o tym awk, ale wydaje się, że generuje separację spacji, nawet jeśli separator pól jest ,(separator pól kontroluje tylko sposób, w jaki obsługuje dane wejściowe)
Michał Mrożek
@MichaelMrozek: tak, to zmienna aws OFS, która kontroluje separator pól wyjściowych.
enzotib
Tak, i jak wspomniałem w mojej odpowiedzi, możesz przekazać opcję -F, aby awk zmienić separator (np. -F,)
tcdyl
0

Będzie to działać w przypadku usuwania

awk '{$2="";$0=$0;$1=$1}1'

Wkład

a b c d

Wydajność

a c d
Steven Penny
źródło