【问题标题】:How can I remove leading and trailing whitespace from all columns but one in a CSV?如何从除 CSV 中的一列之外的所有列中删除前导和尾随空格?
【发布时间】:2014-07-24 16:36:46
【问题描述】:

我有一个如下所示的 CSV:

things,ID,hello_field,more things
stuff,123  ,hello ,more stuff
stuff,123 ,hello ,more stuff
stuff ,123  ,hello ,more stuff
stuff,123  ,hello ,more stuff
stuff ,123,hello ,more stuff
stuff,123,hello ,more stuff
stuff ,123,hello ,more stuff

如何从除第二列 (ID) 之外的所有列中删除前导和尾随空格?最终输出如下所示:

things,ID,hello_field,more things
stuff,123  ,hello,more stuff
stuff,123 ,hello,more stuff
stuff,123  ,hello,more stuff
stuff,123  ,hello,more stuff
stuff,123,hello,more stuff
stuff,123,hello,more stuff
stuff,123,hello,more stuff

我尝试使用以下正则表达式,但它会从 所有 字段中删除空格,包括 ID 列中的空格。

s/( +,|, +)/,/gi;

【问题讨论】:

    标签: regex perl


    【解决方案1】:

    拆分,选择性修剪,重新加入

    perl -F, -lane 's/^\s+|\s+$//g for @F[0,2..$#F]; print join ",", @F' file.csv
    

    说明:

    开关

    • -F/pattern/split() 模式用于-a 开关(// 是可选的)
    • -l: 启用行尾处理
    • -a:在空间上拆分行并将它们加载到数组中@F
    • -n:为输入文件中的每一行创建一个 while(<>){...} 循环。
    • -e:告诉perl 在命令行上执行代码。

    代码

    • EXPR for @F[0,2..$#F]: 遍历数组切片(跳过第二个字段)
    • s/^\s+|\s+$//g:从字段中删除前导和尾随空格
    • print join ",", @F:打印结果

    【讨论】:

    • 所以@miller 你提供的语法 "@F[0,2..$#F]" 将在第一个执行替换,跳过'1',转到'2'并那么“$#F”在文件结束后的所有字段上执行它?
    • 我想你已经明白了——让我们看看@Miller 怎么说:-) 同时查看perlrun POD——特别是自动拆分部分。干杯
    • @miller,还有一件事......它运作良好,但它没有返回分隔符,而是返回其中之一......SCALAR(0x1dc19b4) 有什么方法可以解决这个问题?
    • 一个有趣的事实:在 perl 5.20 中使用 -F 会自动打开 -an,因此您可以只使用 -F, -le - 另一个升级 perls 的理由!
    • 我很高兴你发布了这个@Miller。很长一段时间以来,我一直认为s/\A\s+|\s+\z//g for @a 是一个比do { s/\A\s+//; s/\s+\z//g } for @a 更慢的选项。我刚刚做了板凳,交替实际上比双重操作快 50%(即第一个在第二个去除两个字符串的时候去除三个字符串)所以正则表达式引擎在这方面有很多的爱和关注区域,或者我梦见了我最初的假设。两者皆有可能
    【解决方案2】:

    使用awk:

    awk -F, -v OFS=, '{ for (i = 1; i <= NF; ++i) if (i != 2) { sub(/^[ \t]+/, "", $i); sub(/[ \t]+$/, "", $i) } } 1' file
    

    输出:

    things,ID,hello_field,more things
    stuff,123  ,hello,more stuff
    stuff,123 ,hello,more stuff
    stuff,123  ,hello,more stuff
    stuff,123  ,hello,more stuff
    stuff,123,hello,more stuff
    stuff,123,hello,more stuff
    stuff,123,hello,more stuff
    

    它的作用:

    • 将字段分隔符和输出字段分隔符设置为,
    • 遍历字段值。如果字段编号不是 2,则去掉前导和尾随空格。
    • 打印。

    【讨论】:

      【解决方案3】:

      您可以指定替换中的每个字段:

      #! /usr/bin/env perl
      use warnings;
      use strict;
      use feature qw(say);
      
      for my $line ( <DATA> ) {
          chomp $line;
          $line =~ s/^\s*(\S+)\s*,   # Things: trim off the spaces
              (.+?),                # ID: Leave alone
              \s*(\S+)\s*,          # Hello Field: trim off spaces
              \s*(\S+)\s*           # More things: trim off spaces
              /$1,$2,$3,$4/x;
          say $line;
      }
      
      __DATA__
      things,ID,hello_field,more things
      stuff,123  ,hello ,more stuff
      stuff,123 ,hello ,more stuff
      stuff ,123  ,hello ,more stuff
      stuff,123  ,hello ,more stuff
      stuff ,123,hello ,more stuff   
      stuff,123,hello ,more stuff
      stuff ,123,hello ,more stuff
      

      在这里,我在正则表达式的末尾使用了x,它允许我将表达式分成多行。

      这会产生:

      things,ID,hello_field,morethings
      stuff,123  ,hello,morestuff
      stuff,123 ,hello,morestuff
      stuff,123  ,hello,morestuff
      stuff,123  ,hello,morestuff
      stuff,123,hello,morestuff   
      stuff,123,hello,morestuff
      stuff,123,hello,morestuff
      

      我正在考虑使用命名捕获组。如果您要四处移动并且有很多捕获组,它们会很好。但是,在这种情况下,我认为它不会让事情变得更容易阅读:

      #! /usr/bin/env perl
      use warnings;
      use strict;
      use feature qw(say);
      
      for my $line ( <DATA> ) {
          chomp $line;
          $line =~ s/^\s*(?<things>\S+)\s*,       # Things: trim off the spaces
              (?<id>.+?),                         # ID: Leave alone
              \s*(?<hello_field>\S+)\s*,          # Hello Field: trim off spaces
              \s*(?<more_things>\S+)\s*           # More things: trim off spaces
              /$+{things},$+{id},$+{hello_field},$+{more_things}/x;
          say $line;
      }
      
      __DATA__
      things,ID,hello_field,more things
      stuff,123  ,hello ,more stuff
      stuff,123 ,hello ,more stuff
      stuff ,123  ,hello ,more stuff
      stuff,123  ,hello ,more stuff
      stuff ,123,hello ,more stuff   
      stuff,123,hello ,more stuff
      stuff ,123,hello ,more stuff
      

      【讨论】:

        【解决方案4】:

        我更喜欢@Miller 的答案,它使用正则表达式作为 OP 要求的,但在需要时也有 Text::Trim

        perl -MText::Trim -F, -anE 'trim for @F[0,2..$#F]; say join ",", @F' test.csv

        或:

        use Text::Trim;
        for (<>){
          my @line = split(/,/);
          trim for @line[0,2..$#line];
          print join",", @line, "\n";
        }
        

        我希望我没有劫持线程,但我试图向自己解释为什么Text::Trim 在这里有效,而String::Util qw/trim/ 无效。而且,对于 OP 的问题,为什么一个工作就像将 s// (即表达式)应用于迭代值而另一个不工作。我认为这与修改字符串的原始值有关。 trimString::Util 版本更类似于使用帖子5.14“非破坏性替换标志”又名"/r" s/^\s+|\s+$//rg 其中Text::Trim 修剪更直接...

        无论如何Text::Trim 使用这个正则表达式:

        s/\A\s+//; s/\s+\z// ;    
        

        (连同wantarray 等)其中String::Utiltrim sub 是不同的……也许这在这里很有用;-)

        【讨论】:

          【解决方案5】:

          虽然我已经将这些东西存储在变量中,但是你可以随意使用它。所以,试试这个:

          #!/usr/bin/perl
          use strict;
          use Data::Dumper;
          
          my $str="things,ID,hello_field,more things
          stuff,123  ,hello ,more stuff
          stuff,123 ,hello ,more stuff
          stuff ,123  ,hello ,more stuff
          stuff,123  ,hello ,more stuff
          stuff ,123,hello ,more stuff
          stuff,123,hello ,more stuff
          stuff ,123,hello ,more stuff";
          
          $str=join("\n",map{my ($a,$b,$c)=($1,$2,$3) if($_=~/(.*?),(.*?),(.*)/is);$a=~s/^\s*|\s$//sg;$c=~s/\s*,\s*/,/sg;$_=join(",",$a,$b,$c);$_} split /\n/i,$str);
          
          print $str;
          

          输出:

          things,ID,hello_field,more things
          stuff,123  ,hello,more stuff
          stuff,123 ,hello,more stuff
          stuff,123  ,hello,more stuff
          stuff,123  ,hello,more stuff
          stuff,123,hello,more stuff
          stuff,123,hello,more stuff
          stuff,123,hello,more stuff
          

          【讨论】:

            猜你喜欢
            • 2017-04-30
            • 2018-10-06
            • 1970-01-01
            • 2019-01-26
            • 2020-08-20
            • 2017-03-16
            • 2012-02-28
            • 2021-09-13
            • 2015-02-15
            相关资源
            最近更新 更多