【问题标题】:How can I replace all instances of a character between the Nth and Kth instance of that character?如何替换该字符的第 N 个和第 K 个实例之间的所有字符实例?
【发布时间】:2019-06-13 10:00:17
【问题描述】:

我正在处理大量 CSV 文件,并且在其中一列中,字段本身包含逗号。不幸的是,此列没有用引号引起来,因此导致将 CSV 文件加载到外部应用程序时出现问题。

我的 CSV 文件如下所示:

col1, col2, col3, co,,,l4, col5, col6
col1, col2, col3, co,,,,,l4, col5, col6
col1, col2, col3, co,,l4, col5, col6

我需要删除此特定列中的所有逗号,但我不确定如何去做。不幸的是,用引号正确括起来的有问题的列重写文件不是一种选择。

这些有问题的逗号总是出现在第三个和倒数第二个逗号之间,但我没有足够的 bash 专业知识来编写删除它们的脚本。

输入文件:

col1, col2, col3, co,,,l4, col5, col6
col1, col2, col3, co,,,,,l4, col5, col6
col1, col2, col3, co,,l4, col5, col6

预期输出:

col1, col2, col3, col4, col5, col6
col1, col2, col3, col4, col5, col6
col1, col2, col3, col4, col5, col6

【问题讨论】:

    标签: bash csv awk sed


    【解决方案1】:

    我会提出以下建议:

    awk '{ match($0,/^[^,]*,[^,]*,[^,],/); p1=RLENGTH+1
           match($0,/,[^,]*,[^,]*$/);    ; p2=RSTART
           s=substr($0,p1,p2-p1); gsub(/,/,"",s)
           print substr($0,1,p1-1) s substr($0,p2)
         }' file.csv
    

    awk 'BEGIN{FS=OFS=","}
         { s=""; for(i=4;i<NF-1;++i) s=s $i }
         { print $1,$2,$3,s,$(NF-1),$NF }' file.csv
    

    这些解决方案假定 col1、col2、col3、col5 和 col6 中没有出现,

    如果您在其他列中有逗号,但根据 CSV 标准正确引用了这些列,那么您可以使用基于 What's the most robust way to efficiently parse CSV using awk? 的类似方法

    awk -v FPAT='[^,]*|"[^"]+"' 'BEGIN{OFS=","}
         { s=""; for(i=4;i<NF-1;++i) s=s $i }
         { print $1,$2,$3,s,$(NF-1),$NF }' file.csv
    

    更一般地,回答标题问题:

    如何替换该字符的第 N 个和第 K 个最后一个实例之间的所有字符实例?

    假设c是字符:

    awk 'BEGIN{FS=OFS="c"; n=3; k=}
         { s=""; for(i=1; i <= n   ;++i) s = $i OFS 
                 for(   ; i <= NF-k;++i) s=s $i 
                 for(   ; i <= NF  ;++i) s = OFS $i }
         { print s }' file.csv
    

    【讨论】:

    • 啊,原来有些,确实出现在其他列中,而且它们需要保留。这些解决方案是否可以轻松调整以便仅影响第 4 列?谢谢
    • @isaaclangley 如果其他列中有 ,则答案是否定的,除非每一行中的逗号数量始终相同。
    • @isaaclangley 是双引号之间的其他逗号吗?
    • @isaaclangley 我添加了一个解决方案,假设引用其他逗号可能会解决它。
    • 唉,不,其他列中的逗号没有被引用,并且每行有可变数量的逗号。
    【解决方案2】:

    如果您真的只是想删除该字段中的逗号,那么使用 GNU awk 将第三个参数匹配():

    awk 'match($0,/(([^,]*,){3})(.*)((,[^,]*){2})/,a){gsub(/,/,"",a[3]); $0=a[1] a[3] a[4]} 1' file
    col1, col2, col3, col4, col5, col6
    col1, col2, col3, col4, col5, col6
    col1, col2, col3, col4, col5, col6
    

    但否则我只会将麻烦的字段用双引号括起来,然后像对待任何其他 CSV 一样对待它(例如,参见 What's the most robust way to efficiently parse CSV using awk?):

    $ awk 'match($0,/(([^,]*,){3})(.*)((,[^,]*){2})/,a){$0=a[1] "\"" a[3] "\"" a[4]} 1' file
    col1, col2, col3," co,,,l4", col5, col6
    col1, col2, col3," co,,,,,l4", col5, col6
    col1, col2, col3," co,,l4", col5, col6
    
    $ awk '
        BEGIN { FPAT="[^,]*|\"[^\"]+\"" }
        match($0,/(([^,]*,){3})(.*)((,[^,]*){2})/,a) { $0=a[1] "\"" a[3] "\"" a[4] }
        { for (i=1; i<=NF; i++) print NR, NF, i, $i }
    ' file
    1 6 1 col1
    1 6 2  col2
    1 6 3  col3
    1 6 4 " co,,,l4"
    1 6 5  col5
    1 6 6  col6
    2 6 1 col1
    2 6 2  col2
    2 6 3  col3
    2 6 4 " co,,,,,l4"
    2 6 5  col5
    2 6 6  col6
    3 6 1 col1
    3 6 2  col2
    3 6 3  col3
    3 6 4 " co,,l4"
    3 6 5  col5
    3 6 6  col6
    

    或者只是用 sed 做引用部分:

    $ sed -E 's/(([^,]*,){3})(.*)((,[^,]*){2})/\1"\3"\4/' file
    col1, col2, col3," co,,,l4", col5, col6
    col1, col2, col3," co,,,,,l4", col5, col6
    col1, col2, col3," co,,l4", col5, col6
    

    以上要求-E 需要 GNU 或 BSD/OSX sed。使用任何 POSIX sed 都是:

    $ sed 's/\(\([^,]*,\)\{3\}\)\(.*\)\(\(,[^,]*\)\{2\}\)/\1"\3"\4/' file
    col1, col2, col3," co,,,l4", col5, col6
    col1, col2, col3," co,,,,,l4", col5, col6
    col1, col2, col3," co,,l4", col5, col6
    

    【讨论】:

      猜你喜欢
      • 2017-12-01
      • 2013-08-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-20
      • 1970-01-01
      • 2010-09-13
      • 1970-01-01
      相关资源
      最近更新 更多