केवल अल्पविराम सीमांकित फ़ाइल में उद्धरणों के बीच अल्पविराम निकालें


23

मेरे पास अल्पविराम ( ,) के साथ एक इनपुट फ़ाइल सीमांकित है । कुछ क्षेत्र दोहरे उद्धरण चिह्नों में संलग्न हैं जो उनमें एक अल्पविराम हैं। यहाँ नमूना पंक्ति है

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

मुझे डबल कोट्स और डबल कोट्स के अंदर भी सभी कॉमा की छेड़छाड़ को दूर करने की आवश्यकता है। तो ऊपर दी गई लाइन को नीचे दिखाए गए अनुसार पार्स किया जाना चाहिए

123,ABC DEV 23,345,534.202,NAME

मैंने निम्नलिखित का उपयोग करने की कोशिश की, sedलेकिन अपेक्षित परिणाम नहीं दिए।

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

किसी भी त्वरित चाल के साथ sed, awkया किसी अन्य यूनिक्स उपयोगिता कृपया?


मुझे यकीन नहीं है कि आप क्या करने की कोशिश कर रहे हैं, लेकिन उपयोगिता "csvtool" सीएसवी के लिए सामान्य उपकरण जैसे कि sed या awk से पार्स करने के लिए बेहतर है। यह लाइनक्स के हर डिस्ट्रो के बारे में है।
अंजीर

जवाबों:


32

यदि उद्धरण संतुलित हैं, तो आप हर दूसरे उद्धरण के बीच अल्पविराम निकालना चाहेंगे, इसे इस awkतरह व्यक्त किया जा सकता है :

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

आउटपुट:

123,ABC DEV 23,345,534.202,NAME

व्याख्या

-F"बनाता दोहरे-उद्धरण संकेत पर लाइन, हर दूसरे क्षेत्र अंतर-बोली पाठ होगा जिसका मतलब है कि अलग AWK। फॉर-लूप चलता है gsub, विश्व स्तर पर स्थानापन्न के लिए कम, हर दूसरे क्षेत्र पर, अल्पविराम ( ",") के साथ कुछ नहीं ( "")। 1अंत में डिफ़ॉल्ट कोड-ब्लॉक का आह्वान: { print $0 }


1
कृपया आप विस्तार से gsubबता सकते हैं और संक्षेप में बता सकते हैं कि यह एक लाइनर कैसे काम करता है ?? कृप्या।
mtk

धन्यवाद! यह स्क्रिप्ट वास्तव में अच्छी तरह से काम करती है, लेकिन क्या आप स्क्रिप्ट के अंत में अकेला 1 समझा सकते हैं? -} 1 '-
कोको

@ कोको: यह निष्पादित करता है { print $0 }। मैंने उसे स्पष्टीकरण में भी जोड़ा।
थोर

2
इस दृष्टिकोण में एक समस्या है: कभी-कभी csv में कई पंक्तियों की पंक्तियाँ होती हैं, जैसे: prefix,"something,otherthing[newline]something , else[newline]3rdline,and,things",suffix (यानी: कई पंक्तियाँ, और नेस्टेड "," कहीं भी एक बहु-पंक्ति दोहरे-उद्धरण के भीतर: पूरे "...."भाग को फिर से जोड़ा जाना चाहिए और अंदर ,होना चाहिए प्रतिस्थापित / हटाए गए ...): आपकी स्क्रिप्ट को उस मामले में दोहरे उद्धरणों के जोड़े दिखाई नहीं देंगे, और इसे हल करना वास्तव में आसान नहीं है ("rejoin" लाइनों की आवश्यकता है जो एक "खुले" (यानी, विषम-संख्या में हैं) दोहरे भाव ... + अगर स्ट्रिंग के \" अंदर भी बच गए हैं तो अतिरिक्त देखभाल करें )
ओलिवियर दुलक

1
इस समाधान को पसंद किया, लेकिन मैंने इसे छोड़ दिया क्योंकि मुझे अक्सर कॉमा रखना पसंद है लेकिन फिर भी परिसीमन करना चाहता हूं। इसके बजाय, मैंने उद्धरणों को पाइप के बाहर अल्पविराम में बदल दिया , सीएसवी को एक psv फ़ाइल में परिवर्तित किया:awk -F'"' -v OFS='"' '{ for (I=1; i<=NF; i+=2) gsub(",", "|", $i) } 1' infile
डैंटन नोरिएगा

7

एक अच्छी प्रतिक्रिया है, लूप के साथ केवल एक बार sed का उपयोग करना :

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; फरंट शाखा के लिए एक लेबल है
  • s/^\(\([^"]*,\?\|"[^",]*",\?\)*"[^",]*\),/\1 / 3 संलग्न भाग हो सकते हैं
    • पहला दूसरा: [^"]*,\?\|"[^",]*",\?एक स्ट्रिंग के लिए मैच जिसमें कोई दोहरे उद्धरण नहीं है, शायद कोमा या उसके बाद दो स्ट्रिंग द्वारा संलग्न स्ट्रिंग हो, कोमा के बिना और शायद कोमा के बाद।
    • पहले आरई भाग की तुलना में की तुलना में पहले वर्णित भाग 2 के कई पुनरावृत्ति से बना है, इसके बाद 1 डबल उद्धरण और कुछ बैक्टीरिया हैं, लेकिन कोई डबल-उद्धरण, और न ही कोमा।
    • कोमा द्वारा पीछा किया जाने वाला पहला आरई भाग।
    • नोटा, बाकी लाइन को छूने की जरूरत नहीं है
  • ta:aयदि पिछले s/कमांड ने कुछ बदलाव किया है तो लूप करेगा ।

नेस्टेड कोट्स के साथ भी काम करता है। बहुत अच्छे धन्यवाद!
त्रिकसे

5

एक सामान्य समाधान जो संतुलित उद्धरणों के बीच कई अल्पविरामों को भी संभाल सकता है, एक नेस्टेड प्रतिस्थापन की आवश्यकता है। मैं पर्ल में एक समाधान लागू करता हूं, जो किसी दिए गए इनपुट की प्रत्येक पंक्ति को संसाधित करता है और हर दूसरे उद्धरणों में केवल कॉमा का विकल्प देता है:

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'

या संक्षेप में

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

आप या तो उस पाठ को पाइप कर सकते हैं जिसे आप कमांड को प्रोसेस करना चाहते हैं या टेक्स्टफाइल को अंतिम कमांड लाइन तर्क के रूप में संसाधित करने के लिए निर्दिष्ट करना चाहते हैं।


1
[^\\]कोट के अंदर अंतिम वर्ण मिलान और यह (गैर \ चरित्र) को हटाने, यानी की अवांछित प्रभाव हो रहा है, तो आप उस चरित्र सेवन नहीं करना चाहिए। (?<!\\)इसके बजाय कोशिश करें ।
tojrobinson

आपकी आपत्ति के लिए धन्यवाद, मैंने इसे सही किया है। फिर भी मुझे लगता है कि हमें यहाँ जोर देने की आवश्यकता नहीं है, या क्या हम !?
user1146332

1
आपके कैप्चर समूह में गैर \ _ को शामिल करना एक बराबर परिणाम उत्पन्न करता है। +1
tojrobinson

1
+1। sed के साथ कुछ चीजों की कोशिश करने के बाद, मैंने sed के डॉक्स की जाँच की और पुष्टि की कि यह एक लाइन के मिलान वाले हिस्से पर एक रिप्लेसमेंट लागू नहीं कर सकता है ... इसलिए छोड़ दिया और पर्ल की कोशिश की। एक बहुत ही समान दृष्टिकोण लेकिन इस संस्करण का उपयोग करता है के साथ समाप्त हो गया [^"]*मैच गैर लालची बनाने के लिए (यानी एक से मेल खाता है "करने के लिए अगले " ): perl -pe 's/"([^"]+)"/($match = $1) =~ (s:,::g);$match;/ge;'। यह उस बाह्य विचार को स्वीकार नहीं करता है कि एक उद्धरण के साथ बच सकता है :-)
cas

आपके कमेंट के लिए धन्यवाद। दिलचस्प होगा अगर या तो [^"]*दृष्टिकोण या स्पष्ट गैर-लालची दृष्टिकोण कम सीपीयू समय की खपत करता है।
user1146332

3

मैं उचित CSV पार्सर वाली भाषा का उपयोग करूंगा। उदाहरण के लिए:

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

जब मैंने शुरू में इस समाधान को पसंद किया, तो यह बड़ी फ़ाइलों के लिए अविश्वसनीय रूप से धीमा हो गया ...
KIC

3

आपके दूसरे उद्धरण गलत हैं:

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

इसके अलावा, नियमित अभिव्यक्ति का उपयोग पाठ के सबसे लंबे समय तक संभव भाग से मेल खाते हैं, जिसका अर्थ है कि यह काम नहीं करेगा यदि आपके पास स्ट्रिंग में एक से अधिक उद्धृत फ़ील्ड है।

एक ऐसा तरीका जो कई उद्धृत क्षेत्रों को sed में संभालता है

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

यह भी इसे हल करने का एक तरीका है, हालांकि, इनपुट के साथ जिसमें प्रति उद्धरण फ़ील्ड में एक से अधिक अल्पविराम शामिल हो सकते हैं, जो कि सेड में पहली बार एक ही क्षेत्र में अधिकतम अल्पविराम सामग्री के रूप में या जब तक इसे दोहराया जा सकता है। आउटपुट बिल्कुल नहीं बदलता है।

एक से अधिक एक्सप्रेशन के साथ रनिंग सेड, चलने वाली कई सीड प्रोसेस और ओपन पाइप से चलने वाले "tr" की तुलना में अधिक कुशल होना चाहिए।

हालाँकि, यह अवांछित परिणाम हो सकता है यदि इनपुट ठीक से स्वरूपित नहीं किया गया है। यानी नेस्टेड कोट्स, अनमैरिनेटेड कोट्स।

चल रहे उदाहरण का उपयोग करना:

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'

आउटपुट:

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

आप इसे सशर्त शाखाओं के साथ अधिक सामान्य बना सकते हैं और ERE के साथ अधिक पठनीय है, जैसे GNU sed के साथ sed -r ':r; s/("[^",]+),([^",]*)/\1 \2/g; tr; s/"//g':।
थोर

2

पर्ल में - आप इसे Text::CSVपार्स करने के लिए उपयोग कर सकते हैं , और इसे तुच्छ तरीके से कर सकते हैं:

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

आप के साथ मुद्रित कर सकते हैं, Text::CSVलेकिन यदि आप करते हैं तो यह उद्धरणों को संरक्षित करता है। (हालांकि, मेरा सुझाव है - अपने आउटपुट के लिए उद्धरणों को अलग करने के बजाय , आप Text::CSVपहले स्थान पर उपयोग करके पार्स कर सकते हैं )।


0

मैंने स्ट्रिंग में प्रत्येक वर्ण के माध्यम से लूप करने के लिए एक फ़ंक्शन बनाया।
यदि चरित्र एक उद्धरण है तो चेक (b_in_qt) सत्य है।
जबकि b_in_qt सत्य है, सभी कॉमा को एक स्थान से बदल दिया जाता है।
अगला कॉमा मिलने पर b_in_qt को गलत पर सेट किया जाता है।

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"
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.