Yalnızca virgülle ayrılmış bir dosyada alıntılar arasındaki virgülleri kaldırın.


23

Virgülle ( ,) ayrılmış bir girdi dosyasına sahibim . Çift tırnak içine alınmış ve içinde virgül bulunan bazı alanlar vardır. İşte örnek satır

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

Çift tırnak içinde ve çift tırnak içinde oluşan tüm virgülleri silmeliyim. Bu yüzden yukarıdaki satır aşağıda gösterildiği gibi ayrıştırılmalıdır.

123,ABC DEV 23,345,534.202,NAME

Aşağıdakileri kullanmayı denedim, sedancak beklenen sonuçları vermedim.

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

İle herhangi bir hızlı hileler sed, awkveya başka herhangi bir unix yarar lütfen?


Ne yapmaya çalıştığınızdan emin değilim, ancak "csvtool" yardımcı programı, csv'yi ayırmak için sed veya awk gibi genel araçlardan çok daha iyidir. Hemen hemen her linux dağıtımında.
figtrap

Yanıtlar:


32

Tırnaklar dengeli ise, diğer tüm alıntılar arasında virgülleri kaldırmak isteyeceksiniz, bu şöyle ifade edilebilir awk:

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

Çıktı:

123,ABC DEV 23,345,534.202,NAME

açıklama

-F"Markaları her alan arası alıntı metin olacağı anlamına gelir çift tırnak işaretinde hattını ayırmak awk. For-loop gsub, global olarak ikame edilenler için kısa, her alanda, virgül ( ",") ' ü hiçbir şey ( "") ile değiştirir . 1Sonunda varsayılan kod bloğunu çağırır: { print $0 }.


1
Lütfen, gsubbu astarın nasıl çalıştığını kısaca açıklayabilir misiniz ? Lütfen.
mtk

Teşekkür ederim! Bu senaryo gerçekten iyi çalışıyor ama senaryonun sonundaki yalnız 1'i açıklayabilir misiniz? -} 1 '-
CocoaEv

@CocoaEv: Yürütür { print $0 }. Bunu da açıklamaya ekledim.
Thor

2
: Bazen CSV gibi birkaç satırdan satır vardır: bu yaklaşım bir sorunu var prefix,"something,otherthing[newline]something , else[newline]3rdline,and,things",suffix (yani: her yerde içinde çeşitli çizgiler ve iç içe "" Bir çok hatlı çift alıntı: Bütün "...."bölüm yanına döndü edilmeli ve içeride ,olmalı değiştirildi / kaldırıldı ...): betiğiniz bu durumda çift tırnak çiftleri görmeyecek ve çözülmesi gerçekten kolay değil ("açık" olan (tek sayılı) satırları "yeniden birleştirmeniz gerekir" çifte alıntı ... + ipin \" içinde kaçanlar varsa daha fazla özen
gösterin

1
Bu çözümü çok sevdim, ancak sık sık virgülle tutmayı sevdiğim halde hala sınırlandırmak istediğim için çimdikledim. Bunun yerine, tırnakların dışındaki virgülleri boruya dönüştürdüm, csv'yi psv dosyasına dönüştürdüm:awk -F'"' -v OFS='"' '{ for (I=1; i<=NF; i+=2) gsub(",", "|", $i) } 1' infile
Danton Noriega

7

Bir Orada iyi yanıtı ile sed sadece bir kez kullanarak, 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

Açıklama:

  • :a; kürkçü şubesi için bir etikettir
  • s/^\(\([^"]*,\?\|"[^",]*",\?\)*"[^",]*\),/\1 / 3 kapalı parça içerebilir
    • ilk önce 2.: [^"]*,\?\|"[^",]*",\?çift ​​alıntı içermeyen bir dizgenin eşleşmesi, belki de koma veya iki çift alıntı ile çevrelenmiş bir dizgenin komaya girmesi ve belki de bir komanın izlenmesi.
    • ilk RE bölümü daha önce tarif edilen 2. bölümün tekrarı, bunu takiben 1 çifte alıntı ve bazı karamellerden oluşur, fakat çifte alıntı, ne de komalar oluşur.
    • Koma tarafından takip edilecek ilk RE bölümü.
    • Nota, hattın geri kalanına dokunmana gerek yok
  • ta:aÖnceki s/komut bazı değişiklikler yaptıysa döngü olacaktır .

İç içe geçmiş tırnak işaretleri ile de çalışır. Müthiş, teşekkürler!
Tricasse

5

Dengeli tırnaklar arasında birkaç virgül işleyebilen genel bir çözüm, iç içe geçmiş bir ikameye ihtiyaç duyar. Belirli bir girişin her satırını işleyen ve diğer tüm tırnak çiftlerinde yalnızca virgül yerine geçen bir çözüm uygularım:

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'

veya kısaca

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

İşlemek istediğiniz metni komuta yönlendirebilir veya son komut satırı argümanı olarak işlenecek metin dosyasını belirtebilirsiniz.


1
Bu [^\\], tırnak işaretleri içindeki son karakteri eşleştirme ve onu kaldırma (\ olmayan) için istenmeyen bir etkiye sahip olacak, yani, o karakteri kullanmamalısınız. (?<!\\)Bunun yerine deneyin .
tojrobinson

İtirazınız için teşekkürler, bunu düzelttim. Yine de burada iddiaların arkasına bakmamıza gerek olmadığını düşünüyorum, değil mi?
user1146332 20:12

1
Yakalama grubunuza \ non dahil etmek eşdeğer bir sonuç verir. +1
tojrobinson 20:12

1
+1. sed ile birkaç şey denedikten sonra, sed'nin belgelerini kontrol ettim ve bir çizginin sadece eşleşen kısmına değiştiremeyeceğimi doğruladım ... bu yüzden vazgeçip perl'i denedim. Çok benzer bir yaklaşımla ancak bu sürüm kullanımları ile sona erdi [^"]*maç olmayan açgözlü hale getirmek için (yani birinden şeyi maçları "için önümüzdeki " ): perl -pe 's/"([^"]+)"/($match = $1) =~ (s:,::g);$match;/ge;'. Bir teklifin ters eğik çizgi ile kaçabileceği tuhaf fikrini kabul etmez :-)
cas

Yorumun için teşekkürler. [^"]*Yaklaşım veya açık açgözlü olmayan yaklaşım daha az cpu zaman tüketirse ilginç olurdu .
user1146332 20:12

3

CSV ayrıştırıcısına sahip bir dili kullanırdım. Örneğin:

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

başlangıçta bu çözümü sevmeme rağmen, büyük dosyalar için inanılmaz derecede yavaş olduğu ortaya çıktı ...
KIC

3

İkinci teklifiniz yanlış yerleştirildi:

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

Ek olarak, düzenli ifadeleri kullanmak metnin mümkün olan en uzun bölümünü eşleştirme eğilimindedir; bu, dizede birden fazla alıntılanmış alan varsa, bunun işe yaramayacağı anlamına gelir.

Birden çok alıntılanmış alanı sed'de işleyen bir yol

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

Bu aynı zamanda bunu çözmenin bir yoludur, ancak, belirtilen alan başına birden fazla virgül içerebilen girdiyle, sed'deki ilk ifadenin tek bir alandaki maksimum virgül içeriği kadar veya tekrarlanana kadar tekrarlanması gerekir. çıkışı hiç değiştirmez.

Birden fazla ifade ile sed'in çalıştırılması, çalışan birkaç sed işlemden ve hepsi açık borularla çalışan bir "tr" den daha verimli olmalıdır.

Bununla birlikte, giriş uygun şekilde biçimlendirilmemişse, bunun istenmeyen sonuçları olabilir. yani, yuvalanmış tırnaklar, sonlandırılmamış tırnaklar.

Çalışan örneği kullanarak:

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'

Çıktı:

123,ABC  DEV 23,345,534,some more  comma-separated  words,202,NAME

Sed Sen mesela GNU ERE ile bu koşullu dallanma ile daha genel ve daha okunabilir yapabilirsiniz: sed -r ':r; s/("[^",]+),([^",]*)/\1 \2/g; tr; s/"//g'.
Thor,

2

Perl'de - bunu Text::CSVayrıştırmak için kullanabilirsiniz ve önemsizce yapın:

#!/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";
}

İle yazdırabilirsiniz, Text::CSVancak yaparsanız, teklifleri koruma eğilimindedir. (Her ne kadar, önerebilirim - çıktınız için teklifleri çıkarmak yerine Text::CSV, ilk etapta kullanarak ayrıştırmaya başlayabilirsiniz ).


0

Dizgideki her karakterin içine döngü ekleyen bir fonksiyon yarattım.
Karakter bir alıntı ise, kontrol (b_in_qt) doğru olarak işaretlenir.
B_in_qt true olsa da, tüm virgüller bir boşlukla değiştirilir.
Bir sonraki virgül bulunduğunda b_in_qt false değerine ayarlanır.

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"
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.