【问题标题】:Remove last column from a csv file in Perl从 Perl 中的 csv 文件中删除最后一列
【发布时间】:2019-11-15 10:30:11
【问题描述】:

我有一个 Perl 脚本,它接受逗号分隔的 csv 文件作为输入。 我想丢弃最后一列(列号是预先知道的)。

问题是最后一列可能包含带逗号的引号字符串,在这种情况下我想剪切整个字符串。

例子:

colA,colB,colC
1,2,3
4,5,"6,6"

我想结束的是:

colA,colB
1,2
4,5

我目前的解决方案是通过以下方式使用 Linux cut 命令:

cat $file | cut -d ',' -f 3 --complement

输出如下:

colA,colB
1,2
4,5,6"

除非最后一列是带逗号的带引号的字符串,否则效果很好。

我只能使用原生 Perl/Linux 命令来解决这个问题。

感谢您的帮助

【问题讨论】:

  • 如果正确的方法是调用像 cut、sed 或 awk 这样的外部工具,那么 perl 将是一个非常可悲的小工具。

标签: linux perl


【解决方案1】:

使用Text::CSV作为脚本将STDIN处理成STDOUT:

use strict;
use warnings;
use Text::CSV 'csv';

my $csv = csv(in => \*STDIN, keep_headers => \my @headers,
  auto_diag => 2, encoding => 'UTF-8');

pop @headers;

csv(in => $csv, out => \*STDOUT, headers => \@headers,
  auto_diag => 2, encoding => 'UTF-8');

这种方法的明显好处是自动处理所有常见的边缘情况。

【讨论】:

  • 这是最干净、最简单的解决方案。不要重新发明轮子,尤其是不可读的正则表达式。
【解决方案2】:

我相信sungtm 的答案是正确的,需要一些解释:

awk -v FPAT='([^,]+)|(\"[^\"]+\")'  -v OFS=',' '{print $1,$2}'

相当于:

script.awk

BEGIN {
    FPAT = "([^,]+)|(\"[^\"]+\")"; # gnu awk specific: FPAT is RegEx pattern to identify the field's content
    # [^,]+ ------ RegEx pattern to match all chars not ","
    #"[^\"]+\" ------ RegEx pattern to match all quated chars including the quotes
    #()|() ------ RegEx optional groups selector
    OFS = ","; # Output field separator
}
{ # for each input line/record
    print $1, $2; # print "1st field" OFS value "2nd field"
}

跑步

awk -f scirpt.awk input.txt

【讨论】:

    【解决方案3】:

    根据awk-regex试试这个:

    awk -v FPAT='([^,]+)|(\"[^\"]+\")'  -v OFS=',' '{print $1,$2}' ${file}
    

    示例

    echo '"4,4",5,"6,6"' | awk -v FPAT='([^,]+)|(\"[^\"]+\")'  -v OFS=',' '{print $1,$2}'
    "4,4",5
    

    参考

    【讨论】:

    • 感谢您的评论。这适用于单行,但会在完整文件上删除不必要的数据。用更详尽的示例更新我的帖子
    • 这样更好,但是如果第一列或第二列包含引用数据,即 colA,colB,colC 1,2,3 "4,4",5,"6, 6"
    【解决方案4】:

    如果用逗号引用的字符串是您面临的唯一麻烦,您可以使用:

    $ sed -E 's/,"[^"]*"$|,[^,]*$//' ip.txt
    colA,colB
    1,2
    4,5
    
    • ,"[^"]*"$ 将匹配 , 后跟 " 后跟非 " 字符后跟 " 在行尾
    • ,[^,]*$ 将匹配 , 后跟非 , 字符在行尾

    双引号列将匹配字符串中较早的列,因此被完全删除

    perl 等价于 perl -lpe 's/,"[^"]*"$|,[^,]*$//' ip.txt

    【讨论】:

      【解决方案5】:

      将脚本保存在任何文件中,比如 script.pl 按提示执行>perl script.pl /opt/filename.csv

      • "1","2,3",4,"test, test" ==> "1","2,3",4
      • 1,"2,3,4","5 , 6","7,8" ==> 1,"2,3,4","5 , 6"
      • 0,0,0,"test" ==> 0,0,0

      处理以上情况

      use strict;
      if (scalar(@ARGV) != 1 ) {
          print "usage: perl script.pl absolute_file_path";
          exit;
      }
      my $filename = $ARGV[0]; # complete file path here
      open(DATA, '<', $filename)
          or die "Could not open file '$filename' $!";
      
      my @lines = <DATA>;
      close(DATA);
      
      my $counter=0;
      open my $fo, '>', $filename;
      
      
      
      foreach my $line(@lines) {
          chomp($line);
      
          my @update = split '(?:^|,)(\"(?:[^\"]+|\"\")*\"|[^,]*)' , $line;
          my @update2;
          foreach (@update) {
             if($_=~/\w+/) {
                 push(@update2,$_);
             }
          }
          pop(@update2);
          print @update2;
          my $str = join(',',@update2);
          print $fo "$str";
          unless (++$counter == scalar(@lines)) {
              print $fo "\n";
          }
      }
      close $fo;
      

      【讨论】:

        【解决方案6】:

        这个案例很有趣 - 请在下面查看我的解决方案。

        您可以更改 $debug = 1; 以查看会发生什么以及此机制如何工作

        use strict;
        use warnings;
        
        my $debug = 0;
        
        while( <DATA> ) {
            print "IN:  $_" if $debug;
            chomp;
        
            s/"(.+?)"/replace($1)/ge;   # do magic replacement , -> ___ in block of interest
        
            print "REP: $_\n" if $debug;
        
            my @data = split /,/;       # split into array
            pop @data;                  # pop last element of array
        
            my $line = join ',', @data; # merge array into a string
        
            $line =~ s/___/,/g;         # do unmagic replacement
            $line =~ s/\|/"/g;          # restore | -> "
        
            printf "%s$line\n", $debug ? "OUT: " : '';  # print result
        }
        
        sub replace {
            my $line = shift;
        
            $line =~ s/,/___/g;         # do magic replacement in our block
        
            return "|$line|";           # put | arount block of interest
        }
        
        __DATA__
        colA,colB,colC
        1,2,3
        4,5,"6,6"
        8,3,"1,2",37,82
        64,12,"1,2,3,4",42,56
        "3,4,7,8",2,8,"8,7,6,5,4",2,8
        "3,4,7,8",2,8,"8,7,6,5,4",2,8,"2,8,4,1"
        "3,4,7,8",2,8,"8,7,6,5,4",2,8,"2,8,4,1",3,4
        

        【讨论】:

          【解决方案7】:

          感谢您的帮助。以下是我最终使用的解决方案:

          cat file.csv | perl -MText::ParseWords -nle '@f = parse_line(",",2, $_); tr/,/$/d for @f; print join ",", @f' | cut -d ',' -f 3 --complement | tr $ , ;
          

          这将替换由引号包围的字段中的逗号到 $ 符号,以在丢弃最后一个不需要的列后重新替换。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-01-03
            • 1970-01-01
            • 2014-07-27
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多