当被数值包围时替换空格,但不用字母字符包围



在仅包含字母数字字符的固定宽度文件中,我想替换字母字符和数字字段(包括有符号十进制,但不包括科学记数法(以及数字和数字字段之间的空格,同时在字母字符值之间保留空格。

我知道使用awkFIELDWIDTHS选项,但是我拥有的文件类型有太多字段,具有太多的唯一结构,无法进行概括。

下面是一个玩具示例:

708 447 4797 JOHN SMITH 18000 

需要格式化如下:

708|447|4797|JOHN SMITH|18000 

寻找任何使用sedperlawk等的便携式解决方案。

编辑:

为了澄清问题并概括以获得更好的整体可用性,这里有更多行来测试解决方案。请继续假设任何具有空格的字母字符确实应该保持在一起(即假设没有出现Bob Jones Chuck Smith(。

708 447 4797 JOHN SMITH 18000
708 447 4797 JOHN SMITH    18000
708  447  4797  JOHN SMITH  18000
708 -3.00 4797 JOHN SMITH 18000

应导致:

708|447|4797|JOHN SMITH|18000
708|447|4797|JOHN SMITH|18000
708|447|4797|JOHN SMITH|18000
708|-3.00|4797|JOHN SMITH|18000

使用 sed

sed -r 's/([^[:alpha:]]) +| +([^[:alpha:]])/1|2/g' file
708|447|4797|JOHN SMITH|18000
708|447|4797|JOHN SMITH|18000
708|447|4797|JOHN SMITH|18000
708|-3.00|4797|JOHN SMITH|18000

编辑:使用gnu-awk

awk -v OFS='|' 'BEGIN { 
  FPAT="[^[:alpha:] ]+[[:alpha:]]+( +[[:alpha:]]+)*"
} {$1=$1} 1' file
708|447|4797|JOHN SMITH|18000
708|447|4797|JOHN SMITH|18000
708|447|4797|JOHN SMITH|18000
708|-3.00|4797|JOHN SMITH|18000

这就是所有必要的

use strict;
use warnings;
use 5.010;
my $s = '708 447 4797 JOHN SMITH 18000';
$s =~ s/ (?<=d) h+ | h+ (?=d) /|/axg;
say $s;

输出

708|447|4797|JOHN SMITH|18000

有了这个正则表达式:

(?<=d)[[:blank:]]+(?!$)|[[:blank:]]+(?=d)

演示

Perl 演示:

$ cat /tmp/nums.txt
708 447 4797 JOHN SMITH 18000
708 447 4797 JOHN SMITH    18000
708  447  4797  JOHN SMITH  18000
708 -3.00 4797 JOHN SMITH 18000
$ perl -pe 's/(?<=d)[[:blank:]]+(?!$)|[[:blank:]]+(?=d)/|/g' /tmp/nums.txt
708|447|4797|JOHN SMITH|18000
708|447|4797|JOHN SMITH|18000
708|447|4797|JOHN SMITH|18000
708|-3.00|4797|JOHN SMITH|18000

通过 Perl 的其他一些方式,

$ echo '708 447 4797 JOHN SMITH 18000' | perl -pe 's/(?<=[A-Za-z])h+(?=[A-Za-z])(*SKIP)(*F)|h/|/g' 
708|447|4797|JOHN SMITH|18000

$ echo '708 447 4797 JOHN SMITH 18000' | perl -pe 's/(?<![A-Za-z])h+|h+(?![A-Za-z])/|/g' 
708|447|4797|JOHN SMITH|18000

虽然我喜欢 anubhava 的 sed 解决方案,但对我来说,将所有空格转换为新的分隔符,然后确定需要切换回来的内容似乎更明显。 以下内容从样本数据中生成所需的输出,并且还适应了 Ed Morton 对处理附近 alpha 字段的关注:

sed -r 's/ +/|/g; s/([[:alpha:]])|([[:alpha:]])/1 2/g'

它的优点是更短,更易于阅读。 (好吧,不容易。毕竟,它仍然是sed。

一个可能的问题是,这不会在文本字段中保留空格。 也就是说,JOHN SMITH将被转换为 JOHN SMITH .

避免这种情况的一种方法是:

sed -r 's/([[:digit:]]) +/1|/g; s/ +([[:digit:]])/|1/g'

我相信这与 anubhava 的解决方案几乎等同,只是它符合您在数字内容周围分隔字段而不是围绕非字母内容分隔字段的要求。

你可能会认为这种事情在awk中也很容易,但事实证明,awk 的sub()gsub()不支持反向引用。 但是,如果您碰巧正在使用gawk,则gensub()函数可能会起作用:

gawk '{gsub(/ +/,"|"); print gensub(/([[:alpha:]])|([[:alpha:]])/, "\1 \2", "g", $0);}

gawk '{print gensub(/([[:digit:]]) +/,"\1|","g",gensub(/ +([[:digit:]])/,"|\1","g",$0));}'

这就是我突然想到的,这是一个公认的快速懒惰的刺痛:

perl -pe 's/(d)h+|h+(d)/$1|$2/g' <<< "123 49 5440 G.  Cito 1967 23456" 
123|49|5440|G.  Cito|1967|23456

我读得如下:"替换一个数字,后跟一个以上的水平空间或一个以上的水平空间,后跟一个数字;带有原始数字和 |"。 它将在字符串的字母部分保留多个空格,但如果在这种情况下在123之前有空格,则会在开头放置"|"。

注意:此回复中的上述快速/简单方法存在问题 - 请参阅鲍罗丁对我关于他/她的解决方案的问题的回答。解决方法是使用(如鲍罗丁所指出的((?<=) (?=)零宽度环顾四周,允许(d(内部的表达式作为"边界"工作,并且不包含在匹配中,因此不需要$1$212,只有水平空间被替换为|

perl -pe 's/(?<=d)h+|h+(?=d)/|/g' <<<"9 AAA 9 AAA 54 G. Cito 1967 123"
9|AAA|9|AAA|54|G. Cito|1967|123

谢谢@Borodin!

最新更新