代码之家  ›  专栏  ›  技术社区  ›  Chris

使用awk解析csv并忽略字段中的逗号

  •  33
  • Chris  · 技术社区  · 14 年前

    我有一个csv文件,其中每一行定义一个给定建筑中的房间。除了房间,每排都有一个楼层字段。我要提取的是所有建筑物的所有楼层。

    我的文件看起来像这样。。。

    "u_floor","u_room","name"
    0,"00BDF","AIRPORT TEST            "
    0,0,"BRICKER HALL, JOHN W    "
    0,3,"BRICKER HALL, JOHN W    "
    0,5,"BRICKER HALL, JOHN W    "
    0,6,"BRICKER HALL, JOHN W    "
    0,7,"BRICKER HALL, JOHN W    "
    0,8,"BRICKER HALL, JOHN W    "
    0,9,"BRICKER HALL, JOHN W    "
    0,19,"BRICKER HALL, JOHN W    "
    0,20,"BRICKER HALL, JOHN W    "
    0,21,"BRICKER HALL, JOHN W    "
    0,25,"BRICKER HALL, JOHN W    "
    0,27,"BRICKER HALL, JOHN W    "
    0,29,"BRICKER HALL, JOHN W    "
    0,35,"BRICKER HALL, JOHN W    "
    0,45,"BRICKER HALL, JOHN W    "
    0,59,"BRICKER HALL, JOHN W    "
    0,60,"BRICKER HALL, JOHN W    "
    0,61,"BRICKER HALL, JOHN W    "
    0,63,"BRICKER HALL, JOHN W    "
    0,"0006M","BRICKER HALL, JOHN W    "
    0,"0008A","BRICKER HALL, JOHN W    "
    0,"0008B","BRICKER HALL, JOHN W    "
    0,"0008C","BRICKER HALL, JOHN W    "
    0,"0008D","BRICKER HALL, JOHN W    "
    0,"0008E","BRICKER HALL, JOHN W    "
    0,"0008F","BRICKER HALL, JOHN W    "
    0,"0008G","BRICKER HALL, JOHN W    "
    0,"0008H","BRICKER HALL, JOHN W    "
    

    我要的是所有建筑物的所有楼层。

    我正在使用cat、awk、sort和uniq来获取此列表,尽管我对“建筑物名称”字段中的“,”有问题,例如“BRICKER HALL,JOHN W”,它正在丢弃我的整个csv一代。

    cat Buildings.csv | awk -F, '{print $1","$2}' | sort | uniq > Floors.csv 
    

    如何让awk使用逗号而忽略字段“”之间的逗号?或者,有人有更好的解决方案吗?

    根据提供的建议awk csv解析器的答案,我得到了解决方案:

    cat Buildings.csv | awk -f csv.awk | awk -F" -> 2|"  '{print $2}' | awk -F"|" '{print $2","$3}' | sort | uniq > floors.csv 
    

    我们想用这个 csv awk 程序,然后从那里我想使用“->2 |”,这是基于csv awk程序的格式化。print$2仅打印csv解析的内容,这是因为程序打印原始行,后跟“->#”,其中#是从csv解析的计数。从那里我可以把这个awk csv结果拆分到“|”上,它用逗号代替。然后排序,uniq和管道输出到一个文件并完成!

    谢谢你的帮助。

    7 回复  |  直到 9 年前
        1
  •  10
  •   Dennis Williamson    14 年前

    你得到的额外输出 csv.awk 来自演示代码。它的目的是使用脚本中的函数进行解析,然后根据需要输出它。

    结束时 csv.awk文件 { ... } 演示其中一个函数的循环。是那个代码输出了 -> 2| .

    大多数情况下,只需调用解析函数并执行 print csv[1], csv[2] .

    代码的那一部分看起来像:

    {
        num_fields = parse_csv($0, csv, ",", "\"", "\"", "\\n", 1);
        if (num_fields < 0) {
            printf "ERROR: %s (%d) -> %s\n", csverr, num_fields, $0;
        } else {
    #        printf "%s -> ", $0;
    #        printf "%s", num_fields;
    #        for (i = 0;i < num_fields;i++) {
    #            printf "|%s", csv[i];
    #        }
    #        printf "|\n";
            print csv[1], csv[2]
        }
    }
    

    另存为 your_script (例如)。

    chmod +x your_script .

    以及 cat 是不必要的。而且,你可以 sort -u 而不是 sort | uniq .

    然后,您的命令将如下所示:

    ./yourscript Buildings.csv | sort -u > floors.csv
    
        2
  •  43
  •   hemflit    10 年前
    gawk -vFPAT='[^,]*|"[^"]*"' '{print $1 "," $3}' | sort | uniq
    

    这是一个很棒的GNU Awk 4扩展,在这里您定义了一个字段模式而不是一个字段分隔符模式。为CSV创造奇迹。( docs )

    埃塔(谢谢密丘): 要删除周围的引号, gsub("^\"|\"$","",$3) ;如果有更多的字段 $3 要这样处理,只需循环它们。
    注意,这种简单的方法不能容忍格式错误的输入,也不能容忍引号之间的某些特殊字符覆盖所有这些字符,这超出了一行代码的范围。

        3
  •  6
  •   Vitalik Buterin    14 年前

    我的解决方法是使用以下命令从csv中删除逗号:

    decommaize () {
      cat $1 | sed 's/"[^"]*"/"((&))"/g' | sed 's/\(\"((\"\)\([^",]*\)\(,\)\([^",]*\)\(\"))\"\)/"\2\4"/g' | sed 's/"(("/"/g' | sed 's/"))"/"/g' > $2
    }
    

    也就是说,首先用“(”和“)”替换开头的引号,然后用“whateverwhere”替换“(”和“)”,然后将“(”和“)”的所有剩余实例改回”。

        4
  •  4
  •   Marcus Whybrow    14 年前

    你可以试试这个基于awkbased的csv paser:

    http://lorance.freeshell.org/csv/

        5
  •  2
  •   D Bro    12 年前

    您可以使用我编写的名为csvquote的脚本,让awk忽略被引用字段中的逗号。然后,命令将变为:

    csvquote Buildings.csv | awk -F, '{print $1","$2}' | sort | uniq | csvquote -u > Floors.csv
    

    切割可能比awk更容易:

    csvquote Buildings.csv | cut -d, -f1,2 | sort | uniq | csvquote -u > Floors.csv
    

    您可以在这里找到csvquote代码: https://github.com/dbro/csvquote

        6
  •  0
  •   Community CDub    8 年前

    成熟的CSV解析器,如Perl Text::CSV_XS 是专门用来处理这种怪事的。

    perl -MText::CSV_XS -lne 'BEGIN{$csv=Text::CSV_XS->new()} if($csv->parse($_)){ @f=$csv->fields(); print "$f[0],$f[1]" }' file

    输入行被分成数组 @f
    字段1是 $f[0] 因为Perl在0开始索引

    输出:

    u_floor,u_room
    0,00BDF
    0,0
    0,3
    0,5
    0,6
    0,7
    0,8
    0,9
    0,19
    0,20
    0,21
    0,25
    0,27
    0,29
    0,35
    0,45
    0,59
    0,60
    0,61
    0,63
    0,0006M
    0,0008A
    0,0008B
    0,0008C
    0,0008D
    0,0008E
    0,0008F
    0,0008G
    0,0008H
    

    我提供了更多的解释 文本::CSV XS 在我的回答中: parse csv file using gawk

        7
  •  0
  •   Community CDub    8 年前

    由于问题实际上是要区分CSV字段中的逗号和分隔字段的逗号,因此我们可以用其他内容替换第一种逗号,以便更易于进一步解析,例如:

    0,"00BDF","AIRPORT TEST            "
    0,0,"BRICKER HALL<comma> JOHN W    "
    

    这个gawk脚本(替换comma.awk)可以:

    BEGIN { RS = "(.)" } 
    RT == "\x022" { inside++; } 
    { if (inside % 2 && RT == ",") printf("<comma>"); else printf(RT); }
    

    它使用gawk特性,将实际的记录分隔符捕获到一个名为 RT . 它将每个字符拆分为一个记录,当我们阅读记录时,我们将替换引号中遇到的逗号( \x022 )与 <comma> .

    这个 FPAT 解决方案在一种特殊情况下失败,在这种情况下,转义引号和逗号都在引号内,但此解决方案在所有情况下都有效,即,

    § echo '"Adams, John ""Big Foot""",1' | gawk -vFPAT='[^,]*|"[^"]*"' '{ print $1 }'
    "Adams, John "
    § echo '"Adams, John ""Big Foot""",1' | gawk -f replace-comma.awk | gawk -F, '{ print $1; }'
    "Adams<comma> John ""Big Foot""",1
    

    作为一个简单的复制粘贴一行:

    gawk 'BEGIN { RS = "(.)" } RT == "\x022" { inside++; } { if (inside % 2 && RT == ",") printf("<comma>"); else printf(RT); }'