2017-07-31 11 views
14

इस प्रश्न का उद्देश्य एक कैननिकल उत्तर प्रदान करना है।अजीब का उपयोग कर सीएसवी को कुशलतापूर्वक पार्स करने का सबसे मजबूत तरीका क्या है?

को देखते हुए एक CSV के रूप Excel या एम्बेडेड नई-पंक्तियों, एम्बेडेड दोहरे उद्धरण चिह्नों और तरह खाली क्षेत्रों के साथ अन्य उपकरणों द्वारा उत्पन्न किया जा सकता है:

$ cat file.csv 
"rec1, fld1",,"rec1"",""fld3.1 
"", 
fld3.2","rec1 
fld4" 
"rec2, fld1.1 

fld1.2","rec2 fld2.1""fld2.2""fld2.3","",rec2 fld4 

क्या सबसे मजबूत तरीका कुशलतापूर्वक awk का उपयोग कर अलग रिकॉर्ड और क्षेत्रों की पहचान करने के लिए :

Record 1: 
    $1=<rec1, fld1> 
    $2=<> 
    $3=<rec1","fld3.1 
", 
fld3.2> 
    $4=<rec1 
fld4> 
---- 
Record 2: 
    $1=<rec2, fld1.1 

fld1.2> 
    $2=<rec2 fld2.1"fld2.2"fld2.3> 
    $3=<> 
    $4=<rec2 fld4> 
---- 

इसलिए इसका उपयोग उन रिकॉर्ड्स और फ़ील्ड के रूप में आंतरिक रूप से अजीब स्क्रिप्ट द्वारा किया जा सकता है।

एक मान्य सीएसवी एक होगा जो RFC 4180 के अनुरूप है या एमएस-एक्सेल द्वारा उत्पन्न किया जा सकता है।

समाधान को रिकॉर्ड के अंत को केवल एलएफ (\n) के रूप में सहन करना चाहिए जैसा कि सीआरएलएफ (\r\n) के बजाय यूनिक्स फ़ाइलों के लिए सामान्य है, क्योंकि उस मानक की आवश्यकता है और एक्सेल या अन्य विंडोज उपकरण उत्पन्न होंगे। यह उद्धृत क्षेत्रों के साथ मिश्रित अनगिनत फ़ील्ड भी सहन करेगा। " एस से पहले " एस से पहले "" के बजाय \") से बचने की आवश्यकता नहीं होगी क्योंकि कुछ अन्य सीएसवी स्वरूपों की अनुमति है - यदि आपके पास यह है तो gsub(/\\"/,"\"\"") ऊपर आगे जोड़कर इसे संभालने और स्वचालित रूप से दोनों भागने वाली तंत्रों को संभालने का प्रयास किया जाएगा एक स्क्रिप्ट स्क्रिप्ट को अनावश्यक रूप से नाजुक और जटिल बना देगी।

उत्तर

13

अपनी CSV नई पंक्तियों या भाग निकले दोहरे उद्धरण चिह्न नहीं कर सकते हैं तो आप सभी की जरूरत है (FPAT के लिए जीएनयू awk के साथ):

$ echo 'foo,"field,with,commas",bar' | 
    awk -v FPAT='[^,]*|"[^"]+"' '{for (i=1; i<=NF;i++) print i, "<" $i ">"}' 
1 <foo> 
2 <"field,with,commas"> 
3 <bar> 

अन्यथा, हालांकि, अधिक सामान्य, मजबूत, पोर्टेबल समाधान के साथ काम करेंगे कोई आधुनिक अजीब है:

$ cat decsv.awk 
function buildRec(  i,orig,fpat,done) { 
    $0 = PrevSeg $0 
    if (gsub(/"/,"&") % 2) { 
     PrevSeg = $0 RS 
     done = 0 
    } 
    else { 
     PrevSeg = "" 
     gsub(/@/,"@A"); gsub(/""/,"@B")   # <"[email protected]""bar"> -> <"[email protected]@Bbar"> 
     orig = $0; $0 = ""       # Save $0 and empty it 
     fpat = "([^" FS "]*)|(\"[^\"]+\")"   # Mimic GNU awk FPAT meaning 
     while ((orig!="") && match(orig,fpat)) { # Find the next string matching fpat 
      $(++i) = substr(orig,RSTART,RLENGTH) # Create a field in new $0 
      gsub(/@B/,"\"",$i); gsub(/@A/,"@",$i) # <"[email protected]@Bbar"> -> <"[email protected]"bar"> 
      gsub(/^"|"$/,"",$i)     # <"[email protected]"bar"> -> <[email protected]"bar> 
      orig = substr(orig,RSTART+RLENGTH+1) # Move past fpat+sep in orig $0 
     } 
     done = 1 
    } 
    return done 
} 

BEGIN { FS=OFS="," } 
!buildRec() { next } 
{ 
    printf "Record %d:\n", ++recNr 
    for (i=1;i<=NF;i++) { 
     # To replace newlines with blanks add gsub(/\n/," ",$i) here 
     printf " $%d=<%s>\n", i, $i 
    } 
    print "----" 
} 

$ awk -f decsv.awk file.csv 
Record 1: 
    $1=<rec1, fld1> 
    $2=<> 
    $3=<rec1","fld3.1 
", 
fld3.2> 
    $4=<rec1 
fld4> 
---- 
Record 2: 
    $1=<rec2, fld1.1 

fld1.2> 
    $2=<rec2 fld2.1"fld2.2"fld2.3> 
    $3=<> 
    $4=<rec2 fld4> 
---- 

उपर्युक्त \n की यूनिक्स लाइन समाप्ति मानता है। विंडोज \r\n लाइन एंडिंग के साथ यह बहुत आसान है क्योंकि प्रत्येक फ़ील्ड के भीतर "न्यूलाइन" वास्तव में केवल लाइन फ़ीड्स (यानी \n एस) होगी और इसलिए आप RS="\r\n" सेट कर सकते हैं और फिर \n फ़ील्ड के भीतर लाइन अंतराल के रूप में नहीं माना जाएगा।

यह बस गिनती कितने " रों अब तक मौजूदा रिकॉर्ड में मौजूद हैं जब भी यह RS मुठभेड़ों से काम करता है - अगर यह एक विषम संख्या तो RS (संभवतः \n लेकिन होना जरूरी नहीं है) है मध्य क्षेत्र और इसलिए हम वर्तमान रिकॉर्ड का निर्माण करते रहते हैं, लेकिन यदि यह भी है तो यह वर्तमान रिकॉर्ड का अंत है और इसलिए हम अब पूरे रिकॉर्ड को पढ़ने के बाकी स्क्रिप्ट के साथ जारी रख सकते हैं।

gsub(/@/,"@A"); gsub(/""/,"@B") धर्मान्तरित दोहरे उद्धरण चिह्नों की प्रत्येक जोड़ी के एक स्ट्रिंग @B के पूरे रिकॉर्ड (मन इन "" जोड़े केवल उद्धृत के क्षेत्र में लागू कर सकते हैं में भालू) जो दोहरा उद्धरण शामिल नहीं है axcross इतनी है कि जब हम खेतों में रिकॉर्ड विभाजित मैच() फ़ील्ड के अंदर दिखाई देने वाले उद्धरणों से नहीं निकलता है। gsub(/@B/,"\"",$i); gsub(/@A/,"@",$i) व्यक्तिगत रूप से प्रत्येक फ़ील्ड के अंदर उद्धरण बहाल करता है और "" एस को " में परिवर्तित करता है जो वे वास्तव में प्रतिनिधित्व करते हैं।

संबंधित मुद्दे

 संबंधित मुद्दे