【问题标题】:Using awk or sed to print column of CSV file enclosed in double quotes使用 awk 或 sed 打印用双引号括起来的 CSV 文件的列
【发布时间】:2016-02-15 03:58:08
【问题描述】:

我正在处理一个类似下面的 csv 文件,以逗号分隔,每个单元格都用双引号括起来,但其中一些包含双引号和/或双引号内的逗号。实际文件包含大约 300 列和 200,000 行。

"Column1","Column2","Column3","Column4","Column5","Column6","Column7"
"abc","abc","this, but with "comma" and a quote","18"" inch TV","abc","abc","abc"
"cde","cde","cde","some other, "cde" here","cde","cde","cde"

我需要删除一些除非列,并合并最后几列,而不是在它们之间添加",",我需要</br>。并将第二列移动到最后。单元格内的任何内容都应该相同,双引号和逗号作为原始文件。下面是我需要的输出示例。

"Column1","Column4","Column5","Column2"
"abc","18"" inch TV","abc</br>abc</br>abc","abc"
"cde","some other, "cde" here","cde</br>cde</br>cde","cde"

在此示例中,我想删除 column3 并合并第 5、6、7 列。

下面是我尝试使用的代码,但它读取的是双引号和/或逗号,这与我预期的不同。

awk -vFPAT='([^,]*)|("[^"]+")' -vOFS=, '{print $1,$4,$5"</br>"$6"</br>"$7",$2}' inputfile.csv

sed -i 's@"</br>"@</br>@g' inputfile.csv

sed 用于删除单元格的开始和结束双引号。

我现在得到的输出文件,如果前一个字段包含双引号,它会认为这是一个单元格的开头,所以后面的值经常被推上一列。

我使用的其他代码将每个逗号都视为单元格的开头,因此效果不佳。

awk -F',' 'BEGIN{OFS=",";} {print $1,$4,$5"</br>"$6"</br>"$7",$2}' inputfile.csv

sed -i 's@"</br>"@</br>@g' inputfile.csv

非常感谢任何帮助。谢谢!

【问题讨论】:

  • "this, but with "comma" and a quote" 是单个单元格值吗?为什么内部" 没有转义?
  • 生成“CSV”文件的任何工具都已损坏。有多种可接受的 CSV 格式,它们都不支持字段中的非转义引号 ("this, but with "comma" and a quote")。它应该是"this, but with ""comma"" and a quote""this, but with \"comma\" and a quote"。您能否修复生成此非 CSV 文件的工具?

标签: bash csv awk sed


【解决方案1】:

CSV 是一种松散的格式。格式可能会有细微的变化。您的特定格式可能会或可能不会用常规语法/正则表达式表达。 (有关此问题的讨论,请参阅 this question。)即使您的特定格式可以用正则表达式表示,从现有库中抽出解析器可能更容易。

它不是您可能想要或需要的 bash/awk/sed 解决方案,但 Python 有一个用于解析 CSV 文件的 csv 模块。有许多选项可以调整格式。试试这样的:

#!/usr/bin/python

import csv

with open('infile.csv', 'r') as infile, open('outfile.csv', 'wb') as outfile:
    inreader = csv.reader(infile)
    outwriter = csv.writer(outfile, quoting=csv.QUOTE_ALL)
    for row in inreader:
        # Merge fields 5,6,7 (indexes 4,5,6) into one
        row[4] = "</br>".join(row[4:7])
        del row[5:7]

        # Copy second field to the end
        row.append(row[1])

        # Remove second and third fields
        del row[1:3]

        # Write manipulated row
        outwriter.writerow(row)

请注意,在 Python 中,索引从 0 开始(例如,row[1] 是第二个字段)。切片的第一个索引是包含的,最后一个是独占的(row[1:3] 仅是 row[1]row[2])。您的格式似乎需要在每个字段周围加上引号,因此是 quoting=csv.QUOTE_ALLDialects and Formatting Parameters 有更多选择。

以上代码产生以下输出:

"Column1","Column4","Column5</br>Column6</br>Column7","Column2"
"abc","18"" inch TV","abc</br>abc</br>abc","abc"
"cde","some other, cde"" here""","cde</br>cde</br>cde","cde"

这有两个问题:

  • 它不会对第一行有任何不同的处理,因此第 5、6 和 7 列的标题会像其他行一样合并。

  • 您的输入 CSV 包含 "some other, "cde" here"(第三行第四列),cde 周围有非转义引号。在第 2 行还有另一种情况,但由于它在第 3 列中而被删除。结果包含不正确的引号。

如果这些引号被正确转义,您的示例输入 CSV 文件将变为

infile.csv (转义引号)

"Column1","Column2","Column3","Column4","Column5","Column6","Column7"
"abc","abc","this, but with ""comma"" and a quote","18"" inch TV","abc","abc","abc"
"cde","cde","cde","some other, ""cde"" here","cde","cde","cde"

现在考虑这个修改后的 Python 脚本,它不会合并第一行的列:

#!/usr/bin/python

import csv

with open('infile.csv', 'r') as infile, open('outfile.csv', 'wb') as outfile:
    inreader = csv.reader(infile)
    outwriter = csv.writer(outfile, quoting=csv.QUOTE_ALL)
    first_row = True
    for row in inreader:
        if first_row:
            first_row = False
        else:
            # Merge fields 5,6,7 (indexes 4,5,6) into one
            row[4] = "</br>".join(row[4:7])
        del row[5:7]

        # Copy second field (index 1) to the end
        row.append(row[1])

        # Remove second and third fields
        del row[1:3]

        # Write manipulated row
        outwriter.writerow(row)

输出outfile.csv

"Column1","Column4","Column5","Column2"
"abc","18"" inch TV","abc</br>abc</br>abc","abc"
"cde","some other, ""cde"" here","cde</br>cde</br>cde","cde"

这是您的示例输出,但已正确转义 "some other, ""cde"" here"

这可能不是您想要的,不是 sed 或 awk 解决方案,但我希望它仍然有用。处理更复杂的格式可能证明更复杂的工具是合理的。使用现有库还可以消除一些犯错的机会。

【讨论】:

    【解决方案2】:

    这可能是对问题的过度简化,但这对我的测试数据有用:

    cat /tmp/inputfile.csv | sed 's@\"\,\"@|@g' | sed 's@"</br>"@</br>@g' | awk 'BEGIN {FS="|"} {print $1 "," $4 "," $5 "</br>" $6 "</br>" $7 "," $2}'
    

    请不要说我在 Mac 上,这可能是我必须将 AWK 脚本中的逗号括在引号中的原因。

    【讨论】:

    • 嗨 Marton,谢谢你的帮助,但是“|”也用于文件的其他地方,因为它是一个巨大的文件,我相信我可以在键盘上找到的每个符号/字符都在其中使用,这就是为什么我不能简单地更改分隔符。
    • 好吧@rrxcrossing 你仍然可以将它提高三倍左右。我知道这不是一个好的解决方案,但至少又快又脏 :) 祝你好运。
    猜你喜欢
    • 2011-02-14
    • 1970-01-01
    • 2020-03-01
    • 2011-09-27
    • 2020-09-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多