Konversi CSV ke TSV

27

Saya memiliki sejumlah file CSV besar dan menginginkannya dalam TSV (format tab terpisah). Masalahnya adalah ada koma di bidang file CSV, misalnya:

 A,,C,"D,E,F","G",I,"K,L,M",Z

Output yang diharapkan:

 A      C   D,E,F   G   I   K,L,M   Z

(di mana spasi putih di antaranya adalah tab 'sulit')

Saya memiliki Perl, Python, dan coreutils yang diinstal pada server ini.

Hati gelap
sumber
Saya akan melakukan ini dengan node.js atau dengan perl.
peterh mengatakan mengembalikan Monica
1
Ganti koma yang tidak dikutip dengan tab ...
cricket_007
Ya, jika saya memiliki lebih dari 5 menit untuk pertanyaan ini. Tetapi saya akan dengan senang hati mendukung para penjawab dengan suara saya. Apa yang saya coba katakan, bahwa hal-hal umum / awk mungkin tidak memenuhi syarat untuk itu (setidaknya dalam penggunaan yang umum digunakan).
peterh mengatakan mengembalikan Monica
6
Saya tidak yakin apakah contoh Anda mewakili data aktual, tetapi jika itu adalah string teks aktual maka jangan lupa bahwa Anda mungkin perlu menangani kasing di mana string menyertakan tab ...
AC
3
Bagian rumit lainnya adalah bahwa CSV adalah format yang didefinisikan sangat longgar, tidak ada standar nyata (ada RFC tetapi ditulis bertahun-tahun setelah fakta). Saya telah menulis kode yang menggunakan parser CSV yang disediakan bahasa dan kemudian harus menulis ulang dengan parser khusus karena saya menemukan data input dalam varian format csv yang rusak.
plugwash

Jawaban:

37

Python

Tambahkan ke file bernama csv2tab.sh, dan buat itu bisa dieksekusi

#!/usr/bin/env python
import csv, sys
csv.writer(sys.stdout, dialect='excel-tab').writerows(csv.reader(sys.stdin))

Tes berjalan

$ echo 'A,,C,"D,E,F","G",I,"K,L,M",Z' | ./csv2tab.sh                         
A       C   D,E,F   G   I   K,L,M   Z

$ ./csv2tab.sh < data.csv > data.tsv && head data.tsv                                                   
1A      C   D,E,F   G   I   K,L,M   Z
2A      C   D,E,F   G   I   K,L,M   Z
3A      C   D,E,F   G   I   K,L,M   Z
cricket_007
sumber
5
Kemungkinan bug: jawaban ini tidak luput dari tab internal.
Morgen
4
@Morgen csv.writer(sys.stdout, dialect='excel-tab').writerows(csv.reader(sys.stdin))? Menghilangkan loop juga.
muru
1
@ chx coba python -c 'import csv,sys; csv.writer(sys.stdout, dialect="excel-tab").writerows(csv.reader(sys.stdin))'. Saya ragu -mbekerja seperti itu.
muru
18

Untuk bersenang-senang sed,.

sed -E 's/("([^"]*)")?,/\2\t/g' file

Jika Anda sedtidak mendukung -E, coba dengan -r. Jika Anda sedtidak mendukung \ttab literal, coba letakkan tab literal (dalam banyak shell, ctrl- v tab) atau di Bash, gunakan $'...'string gaya-C (dalam hal ini backslash in \2perlu digandakan). Jika Anda ingin menyimpan tanda kutip, gunakan \1sebagai ganti \2(dalam hal ini pasangan dalam tanda kurung tidak berguna, dan dapat dihapus).

Ini tidak berusaha untuk menangani tanda kutip ganda yang lolos dalam tanda kutip ganda; beberapa dialek CSV mendukung ini dengan menggandakan penawaran ganda yang dikutip.

tripleee
sumber
1
Saya pikir saya sudah mencoba sekitar 100 skrip sed yang berbeda untuk mencapai yang ini tetapi semua usaha saya gagal. Ini luar biasa.
George Vasiliou
16

Menggunakan csvkitutilitas (Python), misalnya:

$ csvformat -T in.csv > out.txt

Apakah streaming, dengan CSV dan TSV yang benar mengutip dan melarikan diri

Ada di manajer paket apt dan lainnya

Neil McGuigan
sumber
13

Satu pilihan mungkin teks Perl :: modul CSV misalnya

perl -MText::CSV -lne 'BEGIN { $csv = Text::CSV->new() }
  print join "\t", $csv->fields() if $csv->parse($_)
' somefile

untuk menunjukkan

echo 'A,,C,"D,E,F","G",I,"K,L,M",Z' |
  perl -MText::CSV -lne 'BEGIN { $csv = Text::CSV->new() }
  print join "\t", $csv->fields() if $csv->parse($_)
'
A       C   D,E,F   G   I   K,L,M   Z
Steeldriver
sumber
1
Tidak akan benar jika bidang berisi tab
Neil McGuigan
6

Perl

perl -lne '
   my $re = qr/,(?=(?:[^"]*"[^"]*")*(?![^"]*"))/;
   print join "\t", map { s/(?<!\\)"//gr =~ s/\\"/"/gr } split $re;
'

Awk

awk -v Q=\" -v FPAT="([^,]*)|(\"[^\"]+\")" -v OFS="\t" '{
   for (i=1; i<=NF; ++i)
      if ( substr($i, 1, 1) == Q )
         $i = substr($i, 2, length($i) - 2)
   print $1, $2, $3, $4, $5, $6, $7, $8
}'

Hasil:

A               C       D,E,F   G       I       K,L,M   Z

sumber
+1 Versi Perl berfungsi seperti pesona
ATorras
4

Solusi penerbang lalat termonuklir harus menggunakan libreoffice. Sementara https://ask.libreoffice.org/en/question/19042/is-is-possible-to-convert-comma-separated-value-csv-to-tab-separated-value-tsv-via-headless-mode Saya menyarankan ini tidak mungkin tetapi itu salah (atau hanya kedaluwarsa?) dan perintah berikut ini berfungsi pada 5.3 saya.

loffice "-env:UserInstallation=file:///tmp/LibO_Conversion" --convert-to csv:"Text - txt - csv (StarCalc)":9,34,UTF8 --headless --outdir some/path --infilter='csv:44,34,UTF8' *.csv

yang envargumen bisa dilewati tetapi cara ini dokumen tidak akan muncul dalam dokumen terbaru Anda.

chx
sumber
2
Saya pikir flyswatter termonuklir sejati akan menulis utilitas Java untuk melakukannya melalui API UNO LibreOffice :).
Pont
3

Jika Anda memiliki, atau dapat menginstal, csvtoolutilitas:

csvtool -t COMMA -u TAB cat in.csv > out.ctv

Perhatikan bahwa karena alasan tertentu csvtooltidak memiliki halaman manual, tetapi csvtool --helpakan mencetak beberapa ratus baris dokumentasi.

Keith Thompson
sumber
3

Menggunakannya mlrhampir ringkas, tetapi menonaktifkan tajuk membutuhkan opsi panjang:

mlr --c2t --implicit-csv-header --headerless-csv-output cat file.csv 

Keluaran:

A       C   D,E,F   G   I   K,L,M   Z
agc
sumber
3

Saya membuat CSV ke TSV sumber terbuka converter yang menangani transformasi yang dijelaskan. Ini cukup cepat, mungkin patut dilihat jika ada kebutuhan yang sedang berlangsung untuk mengkonversi file CSV besar. Tool adalah bagian dari toolkit utilitas TSV eBay (dokumentasi csv2tsv di sini ). Opsi default cukup untuk input yang dijelaskan:

$ csv2tsv file.csv > file.tsv
JonDeg
sumber
2

Vim

Hanya untuk bersenang-senang, penggantian regex dapat dilakukan di Vim . Berikut ini adalah solusi empat jalur potensial, yang diadaptasi dari: /programming/33332871/remove-all-commas-between-quotes-with-a-vim-regex

  1. Koma di antara tanda kutip pertama kali diubah menjadi garis bawah (atau karakter tidak ada lainnya),
  2. Semua koma lainnya diganti dengan tab,
  3. Tanda garis bawah di dalam dikembalikan ke koma,
  4. Tanda kutip dihapus.

    :%s/".\{-}"/\=substitute(submatch(0), ',', '_' , 'g')/g
    :%s/,/\t/g
    :%s/_/,/g
    :%s/"//g

Untuk skrip solusinya agak, empat baris di atas (sans leading colon) dapat disimpan ke file, misalnya to_tsv.vim. Buka setiap CSV untuk mengedit dengan Vim dan sourceyang to_tsv.vimscript pada Vim baris perintah (diadaptasi dari /programming/3374179/run-vim-script-from-vim-commandline/8806874#8806874 ):

    :source /path/to/vim/filename/to_tsv.vim
jubilatious1
sumber
1

Berikut adalah contoh konversi CSV ke TSV menggunakan jqutilitas :

$ jq -rn '@tsv "\(["A","","C","D,E,F","G","I","K,L,M","Z"])"'
A       C   D,E,F   G   I   K,L,M   Z

atau:

$ echo '["A","","C","D,E,F","G","I","K,L,M","Z"]' | jq -r @tsv
A       C   D,E,F   G   I   K,L,M   Z

Namun format CSV perlu diformat dengan baik, sehingga setiap string perlu dikutip.

Sumber: Format output TSV sederhana .

kenorb
sumber
1

Dengan perl, anggap bidang csv tidak memiliki "baris atau tab yang disematkan atau baru:

perl -pe 's{"(.*?)"|,}{$1//"\t"}ge'
Stéphane Chazelas
sumber
0

Berikut ini hanyalah koreksi terhadap jawaban dari @tripleee sehingga menghilangkan tanda kutip dari bidang terakhir sama halnya dengan semua bidang lainnya.

Untuk menunjukkan apa yang sedang diperbaiki, di bawah ini adalah jawaban tripleee , ditambah sedikit modifikasi pada contoh data OP dengan kutipan tambahan di sekitar bidang ' Z ' akhir .

echo 'A,,C,"D,E,F","G",I,"K,L,M","Z"' |  sed -r -e 's/("([^"]*)")?,/\2\t/g'
A       C   D,E,F   G   I   K,L,M   "Z"

Anda dapat melihat bahwa ' Z ' dibiarkan dengan tanda kutip di sekitarnya. Ini berbeda dengan bagaimana bidang dalam ditangani. Misalnya, ' G ' tidak memiliki tanda kutip di atasnya.

Perintah berikut menggunakan subtitusi kedua untuk membersihkan kolom terakhir:

echo 'A,,C,"D,E,F","G",I,"K,L,M","Z"' |  sed -r -e 's/("([^"]*)")?,/\2\t/g' \
                                                -e 's/\t"([^"]*)"$/\t\1/'
A       C   D,E,F   G   I   K,L,M   Z
Fonnae
sumber
1
Ketika data 'A,,C,"D,E,F","G",I,"K,L,M","Z,A"'input dimasukkan ke jawaban ini, maka "Z,A"diganti dengan salah Z A, bukan yang benar Z,A.
agc