Bagaimana cara memanipulasi file CSV dengan sed atau awk?

23

Bagaimana saya bisa melakukan hal berikut untuk file CSV menggunakan sedatau awk?

  • Hapus kolom
  • Gandakan kolom
  • Pindahkan kolom

Saya memiliki meja besar dengan lebih dari 200 baris, dan saya tidak terlalu familiar sed.

Binoy Babu
sumber
1
Cross memposting di AskUbuntu
enzotib
@enzotib dapatkah Anda memposting tautannya?
n0pe
@MaxMackie askubuntu.com/questions/88142/… . Saya tidak bisa mendapatkan mod di sana pada jam ini, jadi saya menandainya meminta mereka untuk bermigrasi jika mereka mau; sudah ada jawaban yang diterima jadi saya tidak yakin apakah mereka mau
Michael Mrozek
@MichaelMrozek, hmmm apa yang biasanya terjadi dalam situasi ini? Apakah kita hanya menyimpan duplikatnya?
n0pe
1
Kecuali jika Anda perlu menjalankan pada sistem yang hanya memiliki alat dasar yang tersedia, lihat Apakah ada alat baris perintah yang kuat untuk memproses file csv?
Gilles 'SO- stop being evil'

Jawaban:

7

Selain cara memotong dan menata ulang ladang (tercakup dalam jawaban lain), ada masalah bidang CSV yang unik.

Jika data Anda termasuk dalam kategori "aneh" ini, sedikit pemfilteran sebelum dan sesudah pemilahan dapat mengatasinya. Filter yang ditunjukkan di bawah memerlukan karakter \x01, \x02, \x03, \x04untuk tidak muncul di mana saja di data Anda.

Berikut adalah filter yang melilit tempat awkpembuangan sederhana .

Catatan: bidang-lima memiliki tata letak "bidang kutip" yang tidak valid / tidak lengkap, tetapi tidak berbahaya di akhir baris (tergantung pada pengurai CSV). Tapi, tentu saja, hal itu akan menyebabkan hasil unexpedted bermasalah jika itu untuk ditukarkan jauh dari saat ini akhir-of-baris posisi.

Memperbarui; user121196 telah menunjukkan bug ketika koma mendahului kutipan trailing. Inilah solusinya.

Data

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

Kode

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' 

Hasil:

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

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

Berikut adalah filter pra , diperluas dengan komentar.
The pasca Filter hanya pembalikan \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
sumber
bagaimana Anda menghapus kolom ke-n berdasarkan filter ini?
user121196
@ user121196 - Seperti disebutkan dalam kalimat pembuka, jawaban ini menunjukkan cara untuk membuat data CSV lebih konsisten .. mis. dengan secara sementara mengganti koma yang disematkan kutipan dengan karakter token netral ... dan kemudian mengembalikannya menjadi koma setelah pindah / potong / hapus. Sekali lagi, seperti yang disebutkan, langkah move / cut / delete digantikan oleh field-dump awk sederhana .
Peter.O
1
gagal untuk kasus ini: "15111 N. Hayden Rd., Ste 160,", ""
user121196
@ user121196: Terima kasih telah menunjukkannya. Saya telah memperbarui jawabannya dengan perbaikan.
Peter.O
15

Ini tergantung pada apakah file CSV Anda menggunakan koma hanya untuk pembatas, atau jika Anda memiliki kegilaan seperti:

bidang satu, "bidang, dua", bidang tiga

Ini mengasumsikan Anda menggunakan file CSV sederhana:

Menghapus kolom

Anda dapat menyingkirkan satu kolom banyak cara; Saya menggunakan kolom 2 sebagai contoh. Cara termudah adalah menggunakan cut, yang memungkinkan Anda menentukan pembatas -ddan bidang mana yang ingin Anda cetak -f; ini memerintahkannya untuk membagi koma dan bidang keluaran 1, dan bidang 3 sampai akhir:

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

Jika Anda benar-benar perlu menggunakan sed, Anda dapat menulis ekspresi reguler yang cocok dengan n-1bidang pertama , bidang nth, dan sisanya, dan lewati menghasilkan nth (di sini nadalah 2, sehingga kelompok pertama adalah 1waktu yang cocok:) \{1\}:

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

Ada beberapa cara untuk melakukan ini awk, tidak ada yang sangat elegan. Anda dapat menggunakan forlingkaran, tetapi berurusan dengan koma yang tertinggal adalah hal yang menyakitkan; mengabaikan bahwa itu akan menjadi seperti:

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

Saya merasa lebih mudah untuk menampilkan bidang 1 dan kemudian menggunakan substruntuk melakukan semuanya setelah bidang 2:

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

Ini menjengkelkan untuk kolom lebih jauh

Duplikat kolom

Pada seddasarnya ini adalah ekspresi yang sama seperti sebelumnya, tetapi Anda juga menangkap kolom target dan memasukkan grup itu beberapa kali dalam penggantian:

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

Dalam awkcara for loop itu akan menjadi sesuatu seperti (lagi-lagi mengabaikan tanda koma):

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

The substrcara:

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

(tcdyl datang dengan metode yang lebih baik dalam jawabannya )

Memindahkan kolom

Saya pikir sedsolusinya mengikuti secara alami dari yang lain, tetapi mulai menjadi sangat panjang

Michael Mrozek
sumber
Itu jawaban yang dimuat! +1 :)
jaypal singh
Sangat panjang? Pah !
Gilles 'SO- stop being evil'
12

awkadalah taruhan terbaik Anda. awkmencetak bidang dengan nomor, jadi ...

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

Untuk menghapus kolom, jangan cetak:

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

Untuk mengubah urutan:

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

Arahkan ulang ke file output.

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

awk dapat memformat output juga.

Output format awk

Harimau kumbang
sumber
Karena ini CSV, Anda juga perlu BEGIN { FS=","; OFS=","; }.
1
Saya pikir bahkan FS = OFS = "," akan bekerja.
5

Diberi file terbatas-ruang dalam format berikut:

1 2 3 4 5

Anda dapat menghapus bidang 2 dengan awk seperti:

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

yang kembali

1  3 4 5

Ganti kolom 2 dengan kolom n jika perlu.

Untuk menduplikasi kolom 2,

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

yang kembali

1 2 2 3 4 5

Untuk mengganti kolom 2 dan 3,

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

yang kembali

1 3 2 4 5

awk umumnya sangat bagus dalam berurusan dengan konsep bidang . Jika Anda berurusan dengan CSV, dan bukan file yang dibatasi ruang, Anda dapat menggunakannya

awk -F,

untuk menentukan bidang Anda sebagai koma, bukan spasi (yang merupakan default). Ada sejumlah sumber daya awk online yang bagus, salah satunya saya cantumkan sebagai sumber di bawah ini.

Sumber untuk # 3

tcdyl
sumber
Saya tidak tahu banyak tentang awk, tetapi tampaknya menghasilkan ruang-dipisahkan bahkan jika pemisah lapangan (pemisah ,lapangan hanya mengontrol bagaimana ia menangani input)
Michael Mrozek
@MichaelMrozek: ya, itu adalah variabel aws OFS yang mengontrol pemisah bidang keluaran.
enzotib
Ya, dan seperti yang saya sebutkan dalam jawaban saya, Anda dapat melewatkan opsi -F untuk awk untuk mengubah pembatas (misalnya -F,)
tcdyl
0

Ini akan berfungsi untuk menghapus

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

Memasukkan

a b c d

Keluaran

a c d
Steven Penny
sumber