Hapus koma di antara tanda kutip hanya dalam file yang dibatasi koma

23

Saya memiliki file input yang dibatasi dengan koma ( ,). Ada beberapa bidang terlampir dalam tanda kutip ganda yang memiliki koma di dalamnya. Berikut adalah contoh baris

123,"ABC, DEV 23",345,534.202,NAME

Saya perlu menghapus semua koma yang terjadi di dalam tanda kutip ganda dan tanda kutip ganda juga. Jadi baris di atas harus diurai menjadi seperti yang ditunjukkan di bawah ini

123,ABC DEV 23,345,534.202,NAME

Saya mencoba yang berikut ini sedtetapi tidak memberikan hasil yang diharapkan.

sed -e 's/\(".*\),\(".*\)/\1 \2/g'

Adakah trik cepat dengan sed, awkatau utilitas unix lainnya?

mtk
sumber
Saya tidak yakin apa yang Anda coba lakukan, tetapi utilitas "csvtool" jauh lebih baik untuk parsing csv daripada alat generik seperti sed atau awk. Ini ada di hampir setiap distro linux.
figtrap

Jawaban:

32

Jika kuotasi seimbang, Anda ingin menghapus koma di antara setiap kuotasi lainnya, ini dapat dinyatakan dalam awkseperti ini:

awk -F'"' -v OFS='' '{ for (i=2; i<=NF; i+=2) gsub(",", "", $i) } 1' infile

Keluaran:

123,ABC DEV 23,345,534.202,NAME

Penjelasan

Tanda -F"awk memisahkan garis pada tanda kutip ganda, yang berarti setiap bidang lainnya akan menjadi teks antar kutip. Menjalankan for-loop gsub, kependekan dari pengganti global, pada setiap bidang lainnya, menggantikan koma ( ",") dengan tidak ada ( ""). The 1pada akhir memanggil default kode-blok: { print $0 }.

Thor
sumber
1
Bisakah Anda menguraikan gsubdan menjelaskan secara singkat, bagaimana liner yang satu ini bekerja ?? silahkan.
mtk
Terima kasih! Skrip ini berfungsi dengan sangat baik, tetapi bisakah Anda menjelaskan 1 yang kesepian di akhir skrip? -} 1 '-
CocoaEv
@ CocoaEv: Ini dijalankan { print $0 }. Saya menambahkan itu ke penjelasannya juga.
Thor
2
pendekatan ini memiliki masalah: kadang-kadang csv memiliki baris yang merentang beberapa baris, seperti: prefix,"something,otherthing[newline]something , else[newline]3rdline,and,things",suffix (yaitu: beberapa baris, dan bersarang "," di mana saja dalam tanda kutip ganda multi-baris: seluruh "...."bagian harus bergabung kembali dan di dalam ,harus diganti / dihapus ...): skrip Anda tidak akan melihat pasangan tanda kutip ganda dalam kasus itu, dan itu tidak benar-benar mudah untuk dipecahkan (perlu "bergabung kembali" dengan garis yang ada di "terbuka" (yaitu, ganjil bernomor) kutipan ganda ... + hati-hati jika ada juga yang lolos \" di dalam string)
Olivier Dulac
1
Saya menyukai solusi ini, tetapi saya men-tweak-nya mengingat saya sering suka menyimpan koma tetapi masih ingin membatasi. Sebagai gantinya, saya mengalihkan koma di luar tanda kutip ke pipa, mengubah csv ke file psv:awk -F'"' -v OFS='"' '{ for (I=1; i<=NF; i+=2) gsub(",", "|", $i) } 1' infile
Danton Noriega
7

Ada respons yang baik , menggunakan sed hanya satu kali dengan satu loop :

echo '123,"ABC, DEV 23",345,534,"some more, comma-separated, words",202,NAME'|
  sed ':a;s/^\(\([^"]*,\?\|"[^",]*",\?\)*"[^",]*\),/\1 /;ta'
123,"ABC  DEV 23",345,534,"some more  comma-separated  words",202,NAME

Penjelasan:

  • :a; adalah label untuk cabang furter
  • s/^\(\([^"]*,\?\|"[^",]*",\?\)*"[^",]*\),/\1 / dapat berisi 3 bagian tertutup
    • pertama ke-2: [^"]*,\?\|"[^",]*",\?cocok dengan string yang tidak mengandung kuotasi ganda, mungkin diikuti oleh koma atau string yang dilingkupi oleh dua kuotasi ganda, tanpa koma dan mungkin diikuti oleh koma.
    • daripada bagian RE pertama disusun oleh banyak pengulangan dari bagian 2 yang dijelaskan sebelumnya, diikuti oleh 1 penawaran ganda dan beberapa karakter, tetapi tidak ada penawaran ganda, atau koma.
    • Bagian RE pertama diikuti oleh koma.
    • Nota, sisa baris tidak perlu disentuh
  • taakan diulang :ajika s/perintah sebelumnya melakukan beberapa perubahan.
F. Hauri
sumber
Bekerja juga dengan kutipan bersarang. Keren terima kasih!
tricasse
5

Solusi umum yang juga dapat menangani beberapa koma di antara kuotasi seimbang membutuhkan substitusi bersarang. Saya menerapkan solusi dalam perl, yang memproses setiap baris dari input yang diberikan dan hanya koma pengganti di setiap pasangan kutipan lainnya:

perl -pe 's/ "  (.+?  [^\\])  "               # find all non escaped 
                                              # quoting pairs
                                              # in a non-greedy way

           / ($ret = $1) =~ (s#,##g);         # remove all commas within quotes
             $ret                             # substitute the substitution :)
           /gex'

atau singkatnya

perl -pe 's/"(.+?[^\\])"/($ret = $1) =~ (s#,##g); $ret/ge'

Anda bisa mem-pipe teks yang ingin Anda proses ke perintah atau menentukan file teks untuk diproses sebagai argumen baris perintah terakhir.

pengguna1146332
sumber
1
The [^\\]akan memiliki efek yang tidak diinginkan dari pencocokan karakter terakhir dalam tanda kutip dan menghapus (non \ karakter), yaitu, Anda tidak harus mengkonsumsi karakter itu. Coba (?<!\\)saja.
tojrobinson
Terima kasih atas keberatan Anda, saya telah memperbaikinya. Namun demikian saya pikir kita tidak perlu melihat ke belakang pernyataan di sini, atau kita !?
user1146332
1
Termasuk non \ dalam grup tangkap Anda menghasilkan hasil yang setara. +1
tojrobinson
1
+1. setelah mencoba beberapa hal dengan sed, saya memeriksa dokumen sed dan mengkonfirmasi bahwa itu tidak dapat menggantikan bagian yang cocok dari sebuah baris ... jadi menyerah dan mencoba perl. Berakhir dengan pendekatan yang sangat mirip tetapi versi ini menggunakan [^"]*untuk membuat pertandingan non-serakah (yaitu cocok segala sesuatu dari satu "ke berikutnya " ): perl -pe 's/"([^"]+)"/($match = $1) =~ (s:,::g);$match;/ge;'. Itu tidak mengakui ide aneh bahwa kutipan mungkin lolos dengan backslash :-)
cas
Terima kasih atas komentar Anda. Akan menarik jika [^"]*pendekatan atau pendekatan non-serakah eksplisit mengkonsumsi waktu cpu lebih sedikit.
user1146332
3

Saya akan menggunakan bahasa dengan parser CSV yang tepat. Sebagai contoh:

ruby -r csv -ne '
  CSV.parse($_) do |row|
    newrow = CSV::Row.new [], []
    row.each {|field| newrow << field.delete(",")}
    puts newrow.to_csv
  end
' < input_file
glenn jackman
sumber
sementara saya menyukai solusi ini pada awalnya, ternyata sangat lambat untuk file-file besar ...
KIC
3

Kutipan kedua Anda salah tempat:

sed -e 's/\(".*\),\(.*"\)/\1 \2/g'

Selain itu, menggunakan ekspresi reguler cenderung cocok dengan bagian teks terpanjang yang mungkin, artinya ini tidak akan berfungsi jika Anda memiliki lebih dari satu bidang yang dikutip dalam string.

Cara yang menangani beberapa bidang dikutip dalam sed

sed -e 's/\(\"[^",]\+\),\([^",]*\)/\1 \2/g' -e 's/\"//g'

Namun, ini juga merupakan cara untuk menyelesaikannya, dengan input yang mungkin mengandung lebih dari satu koma per bidang yang dikutip, ekspresi pertama dalam sed harus diulangi sebanyak yang koma maksimum dalam satu bidang, atau sampai tidak mengubah output sama sekali.

Menjalankan sed dengan lebih dari satu ekspresi harus lebih efisien daripada beberapa proses sed berjalan dan "tr" semua berjalan dengan pipa terbuka.

Namun, ini mungkin memiliki konsekuensi yang tidak diinginkan jika input tidak diformat dengan benar. yaitu kutipan bersarang, kutipan tidak ditentukan.

Menggunakan contoh yang berjalan:

echo '123,"ABC, DEV 23",345,534,"some more, comma-separated, words",202,NAME' \
| sed -e 's/\(\"[^",]\+\),\([^",]*\)/\1 \2/g' \
-e 's/\(\"[^",]\+\),\([^",]*\)/\1 \2/g' -e 's/\"//g'

Keluaran:

123,ABC  DEV 23,345,534,some more  comma-separated  words,202,NAME
Didi Kohen
sumber
Anda dapat membuatnya lebih umum dengan percabangan bersyarat dan lebih mudah dibaca dengan ERE, misalnya dengan GNU sed: sed -r ':r; s/("[^",]+),([^",]*)/\1 \2/g; tr; s/"//g'.
Thor
2

Dalam perl - Anda dapat menggunakannya Text::CSVuntuk menguraikan ini, dan melakukannya dengan sepele:

#!/usr/bin/env perl
use strict;
use warnings;

use Text::CSV; 

my $csv = Text::CSV -> new();

while ( my $row = $csv -> getline ( \*STDIN ) ) {
    #remove commas in each field in the row
    $_ =~ s/,//g for @$row;
    #print it - use print and join, rather than csv output because quotes. 
    print join ( ",", @$row ),"\n";
}

Anda dapat mencetak dengan Text::CSVtetapi cenderung mempertahankan tanda kutip jika Anda melakukannya. (Meskipun, saya sarankan - daripada mencabut tanda kutip untuk output Anda, Anda bisa mem-parsing menggunakan Text::CSVdi tempat pertama).

Sobrique
sumber
0

Saya membuat fungsi untuk loop melalui setiap karakter dalam string.
Jika karakter adalah kutipan maka tanda centang (b_in_qt) ditandai benar.
Meskipun b_in_qt benar, semua koma diganti dengan spasi.
b_in_qt disetel ke false ketika koma berikutnya ditemukan.

FUNCTION f_replace_c (str_in  VARCHAR2) RETURN VARCHAR2 IS
str_out     varchar2(1000)  := null;
str_chr     varchar2(1)     := null;
b_in_qt     boolean         := false;

BEGIN
    FOR x IN 1..length(str_in) LOOP
      str_chr := substr(str_in,x,1);
      IF str_chr = '"' THEN
        if b_in_qt then
            b_in_qt := false;
        else
            b_in_qt := true;
        end if;
      END IF;
      IF b_in_qt THEN
        if str_chr = ',' then
            str_chr := ' ';
        end if;
      END IF;
    str_out := str_out || str_chr;
    END LOOP;
RETURN str_out;
END;

str_in := f_replace_c ("blue","cat,dog,horse","",yellow,"green")

RESULTS
  "blue","cat dog horse","",yellow,"green"
pengguna143598
sumber