2017-07-31 70 views
14

Mục đích của câu hỏi này là cung cấp câu trả lời chuẩn.Cách mạnh mẽ nhất để phân tích cú pháp CSV hiệu quả bằng awk là gì?

Cho một CSV như có thể được tạo ra bởi Excel hoặc các công cụ khác với newlines nhúng, dấu ngoặc kép nhúng và các lĩnh vực có sản phẩm nào như:

$ 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 

gì là cách mạnh mẽ nhất có hiệu quả sử dụng awk để xác định các hồ sơ và các lĩnh vực riêng biệt :

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ó có thể được sử dụng như những bản ghi và trường trong nội bộ phần còn lại của tập lệnh awk.

CSV hợp lệ sẽ là CSV phù hợp với RFC 4180 hoặc có thể được tạo bởi MS-Excel.

Giải pháp phải chịu đựng phần cuối của bản ghi chỉ là LF (\n) như là điển hình cho tệp UNIX chứ không phải CRLF (\r\n) theo tiêu chuẩn đó và Excel hoặc các công cụ Windows khác sẽ tạo. Nó cũng sẽ chấp nhận các trường không được trích dẫn được trộn lẫn với các trường được trích dẫn. Nó sẽ đặc biệt không cần phải chịu đựng được sự thoáts với dấu gạch chéo ngược trước (tức là \" thay vì "") như một số định dạng CSV khác cho phép - nếu bạn có. một kịch bản sẽ làm cho kịch bản không cần thiết mong manh và phức tạp.

Trả lời

13

Nếu CSV của bạn không thể chứa ký tự dòng mới hoặc thoát khỏi dấu ngoặc kép thì tất cả bạn cần là (với GNU awk cho FPAT):

$ 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> 

Nếu không, tuy nhiên, các, mạnh mẽ, giải pháp di động tổng quát hơn rằng sẽ làm việc với bất kỳ awk hiện đại nào là:

$ 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> 
---- 

Ở trên giả định kết thúc dòng UNIX là \n. Với kết thúc dòng Windows \r\n thì đơn giản hơn nhiều khi "dòng mới" trong mỗi trường thực sự chỉ là nguồn cấp dữ liệu dòng (ví dụ: \n s) và bạn có thể đặt RS="\r\n" và sau đó các trường \n s sẽ không được coi là kết thúc dòng.

Nó hoạt động bằng cách đơn giản đếm có bao nhiêu " s có mặt cho đến nay trong hồ sơ hiện bất cứ khi nào nó gặp các RS - nếu nó là một số lẻ thì RS (có lẽ \n nhưng không phải) là giữa cánh đồng và vì vậy chúng tôi tiếp tục xây dựng bản ghi hiện tại nhưng nếu nó thậm chí thì đó là phần cuối của bản ghi hiện tại và vì vậy chúng tôi có thể tiếp tục với phần còn lại của tập lệnh xử lý bản ghi hoàn chỉnh hiện tại.

Các gsub(/@/,"@A"); gsub(/""/,"@B") chuyển đổi tất cả các cặp dấu ngoặc kép axcross toàn bộ hồ sơ (hãy nhớ những "" cặp chỉ có thể áp dụng trong lĩnh vực trích dẫn) thành một chuỗi @B mà không chứa một dấu nháy kép để khi chúng ta chia các hồ sơ vào các lĩnh vực trận đấu() không bị vấp ngã bởi các dấu ngoặc kép xuất hiện bên trong các trường. Các gsub(/@B/,"\"",$i); gsub(/@A/,"@",$i) khôi phục các dấu ngoặc kép bên trong từng trường riêng lẻ và cũng chuyển đổi các số "" s thành các số " mà chúng thực sự đại diện.

Các vấn đề liên quan