数字值包围时替换空格,但不包括字母字符

时间:2015-02-09 17:14:02

标签: regex perl awk sed

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

我知道使用FIELDWIDTHS的{​​{1}}选项,但是我所拥有的文件类型包含太多具有太多独特结构的字段以进行泛化。

这是一个玩具示例:

awk

需要格式化如下:

708 447 4797 JOHN SMITH 18000 

使用708|447|4797|JOHN SMITH|18000 sedperl等寻找任何便携式解决方案。

编辑:

为了澄清问题并概括更好的整体可用性,这里有更多的线来测试解决方案。请继续假设任何具有空格的字母字符确实要保持在一起(即假设没有出现awk)。

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

6 个答案:

答案 0 :(得分:3)

使用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

答案 1 :(得分:2)

这就是必要的

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

答案 2 :(得分:2)

这个正则表达式:

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

DEMO

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

答案 3 :(得分:1)

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

OR

$ 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

答案 4 :(得分:1)

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

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

它的优点是更短,更容易阅读。 (好吧,没那么容易了。毕竟,它仍然很好。)

一个可能的问题是,这不会在文本字段中保留空格。也就是说,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));}'

答案 5 :(得分: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

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

NB :此回复中的快速/轻松方法存在问题 - 请参阅Borodin对我的问题有关他/她的解决方案的回复。修复方法是使用(如Borodin所说)(?<=) (?=) zero-width look around,它允许内部(\d)内的表达式作为&#34;边界&#34;并且不会包含在匹配中,因此不需要$1$2\1\2,只有水平空格会被|替换。

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!