我对正则表达式和 GNU sed 都很陌生。我有以下数据的匿名样本。
RED: 13905 16356 17457 18164 18447 21063 26924 27684 30111 30205
CERISE: 6221 6524 18250 24367 24462 29014
CARMINE: 39 49 53 81 95 99 105 106 109 134 195 226 260 350 383 393 397 414 417 435 439 478 488 516 521 535 596 599 614 621 628 630 632 635 785 786 810 836 837 841 852 855 953 1029 1104 1121 1122 1137 1148
VERMILLION: 23029
我想用字符串开头的文本加上前缀(</>)替换第一个空格之后的每个空格,以便所需的输出是:
RED: 13905</>RED: 16356</>RED: 17457</>RED: 18164</>RED: 18447</>RED: 21063</>RED: 26924</>RED: 27684</>RED: 30111</>RED: 30205
CERISE: 6221</>CERISE: 6524</>CERISE: 18250</>CERISE: 24367</>CERISE: 24462</>CERISE: 29014
CARMINE: 39</>CARMINE: 49</>CARMINE: 53</>CARMINE: 81</>CARMINE: 95</>CARMINE: 99</>CARMINE: 105</>CARMINE: 106</>CARMINE: 109</>CARMINE: 134</>CARMINE: 195</>CARMINE: 226</>CARMINE: 260</>CARMINE: 350</>CARMINE: 383</>CARMINE: 393</>CARMINE: 397</>CARMINE: 414</>CARMINE: 417</>CARMINE: 435</>CARMINE: 439</>CARMINE: 478</>CARMINE: 488</>CARMINE: 516</>CARMINE: 521</>CARMINE: 535</>CARMINE: 596</>CARMINE: 599</>CARMINE: 614</>CARMINE: 621</>CARMINE: 628</>CARMINE: 630</>CARMINE: 632</>CARMINE: 635</>CARMINE: 785</>CARMINE: 786</>CARMINE: 810</>CARMINE: 836</>CARMINE: 837</>CARMINE: 841</>CARMINE: 852</>CARMINE: 855</>CARMINE: 953</>CARMINE: 1029</>CARMINE: 1104</>CARMINE: 1121</>CARMINE: 1122</>CARMINE: 1137</>CARMINE: 1148
VERMILLION: 23029
我尝试了下面的方法,它按字面意思解释了“ ^.*: ”并且与行首不匹配(我暂时忽略了第一个空格也会被替换的事实)。
sed 's/ /\<\\\>^.*: /g' inputfile
RED:<\>^.*: 13905<\>^.*: 16356<\>^.*: 17457<\>^.*: 18164<\>^.*: 18447<\>^.*: 21063<\>^.*: 26924<\>^.*: 27684<\>^.*: 30111<\>^.*: 30205
CERISE:<\>^.*: 6221<\>^.*: 6524<\>^.*: 18250<\>^.*: 24367<\>^.*: 24462<\>^.*: 29014
CARMINE:<\>^.*: 39<\>^.*: 49<\>^.*: 53<\>^.*: 81<\>^.*: 95<\>^.*: 99<\>^.*: 105<\>^.*: 106<\>^.*: 109<\>^.*: 134<\>^.*: 195<\>^.*: 226<\>^.*: 260<\>^.*: 350<\>^.*: 383<\>^.*: 393<\>^.*: 397<\>^.*: 414<\>^.*: 417<\>^.*: 435<\>^.*: 439<\>^.*: 478<\>^.*: 488<\>^.*: 516<\>^.*: 521<\>^.*: 535<\>^.*: 596<\>^.*: 599<\>^.*: 614<\>^.*: 621<\>^.*: 628<\>^.*: 630<\>^.*: 632<\>^.*: 635<\>^.*: 785<\>^.*: 786<\>^.*: 810<\>^.*: 836<\>^.*: 837<\>^.*: 841<\>^.*: 852<\>^.*: 855<\>^.*: 953<\>^.*: 1029<\>^.*: 1104<\>^.*: 1121<\>^.*: 1122<\>^.*: 1137<\>^.*: 1148<\>^.*:
VERMILLION:<\>^.*: 23029
那么正则表达式不能用于替换字符串吗?我还能用什么其他方法做到这一点?
谢谢,L
答案1
使用重复替换:
sed -E ':a; s/^(([A-Z:]+).*)([0-9]+) /\1\3<\/>\2 /; ta'
输出:
RED: 13905</>RED: 16356</>RED: 17457</>RED: 18164</>RED: 18447</>RED: 21063</>RED: 26924</>RED: 27684</>RED: 30111</>RED: 30205
CERISE: 6221</>CERISE: 6524</>CERISE: 18250</>CERISE: 24367</>CERISE: 24462</>CERISE: 29014
CARMINE: 39</>CARMINE: 49</>CARMINE: 53</>CARMINE: 81</>CARMINE: 95</>CARMINE: 99</>CARMINE: 105</>CARMINE: 106</>CARMINE: 109</>CARMINE: 134</>CARMINE: 195</>CARMINE: 226</>CARMINE: 260</>CARMINE: 350</>CARMINE: 383</>CARMINE: 393</>CARMINE: 397</>CARMINE: 414</>CARMINE: 417</>CARMINE: 435</>CARMINE: 439</>CARMINE: 478</>CARMINE: 488</>CARMINE: 516</>CARMINE: 521</>CARMINE: 535</>CARMINE: 596</>CARMINE: 599</>CARMINE: 614</>CARMINE: 621</>CARMINE: 628</>CARMINE: 630</>CARMINE: 632</>CARMINE: 635</>CARMINE: 785</>CARMINE: 786</>CARMINE: 810</>CARMINE: 836</>CARMINE: 837</>CARMINE: 841</>CARMINE: 852</>CARMINE: 855</>CARMINE: 953</>CARMINE: 1029</>CARMINE: 1104</>CARMINE: 1121</>CARMINE: 1122</>CARMINE: 1137</>CARMINE: 1148
VERMILLION: 23029
评论版本:
# Create a label we can jump to
:a
# This pattern will capture the front heading into group \2 and
# everything following that up-to number+space into group \1.
# The matched number is saved in group \3
s/^(([A-Z:]+).*)([0-9]+) /\1\3<\/>\2 /
# If the previous substitution was successful, repeat it
ta
答案2
这是一个 perl 解决方案:
perl -ane '@_=split/ /,$_;$x=shift@_;print "$x ",join "</>$x ",@_;' inputfile
RED: 13905</>RED: 16356</>RED: 17457</>RED: 18164</>RED: 18447</>RED: 21063</>RED: 26924</>RED: 27684</>RED: 30111</>RED: 30205
CERISE: 6221</>CERISE: 6524</>CERISE: 18250</>CERISE: 24367</>CERISE: 24462</>CERISE: 29014
CARMINE: 39</>CARMINE: 49</>CARMINE: 53</>CARMINE: 81</>CARMINE: 95</>CARMINE: 99</>CARMINE: 105</>CARMINE: 106</>CARMINE: 109</>CARMINE: 134</>CARMINE: 195</>CARMINE: 226</>CARMINE: 260</>CARMINE: 350</>CARMINE: 383</>CARMINE: 393</>CARMINE: 397</>CARMINE: 414</>CARMINE: 417</>CARMINE: 435</>CARMINE: 439</>CARMINE: 478</>CARMINE: 488</>CARMINE: 516</>CARMINE: 521</>CARMINE: 535</>CARMINE: 596</>CARMINE: 599</>CARMINE: 614</>CARMINE: 621</>CARMINE: 628</>CARMINE: 630</>CARMINE: 632</>CARMINE: 635</>CARMINE: 785</>CARMINE: 786</>CARMINE: 810</>CARMINE: 836</>CARMINE: 837</>CARMINE: 841</>CARMINE: 852</>CARMINE: 855</>CARMINE: 953</>CARMINE: 1029</>CARMINE: 1104</>CARMINE: 1121</>CARMINE: 1122</>CARMINE: 1137</>CARMINE: 1148
VERMILLION: 23029
重定向到输出文件:
perl -ane '@_=split/ /,$_;$x=shift@_;print "$x ",join "</>$x ",@_;' inputfile > outpufile
解释:
@_=split/ /,$_; # split each line on space
$x=shift@_; # remove the first element (i.e. "RED: ") and store it in variable $x
print # print on STDOUT
"$x " # value of $x and a space
,
join "</>$x ",@_ # all values of @_ joined with </> and content of $x
答案3
免责声明:我不是数学家,我的回答应该从实用的角度来理解,而不是从形式的角度。如果您有任何正式的更正或补充,请编辑或评论我的答案。我很乐意学到一些东西。
回答你的问题,不,正则表达式不能用于替换定义(或“替换字符串”);只有反向引用可以。
关于你对此的评论,简短地说明一下“超越 sed 的能力”,这超出了严格正则表达式的能力,但并不超出 sed 的能力。请继续阅读以了解原因。
现在,要解决你的问题,严格来说,你不能只使用正则表达式。正则表达式使用正则文法定义所需的处理,并且您的输入无法使用常规语法进行解析。事实上,正如维基百科链接所解释的那样,常规语法只允许处理由三种不同的转换组成:
- 非终结符号 → 终结符号
- 非终结符号 → 终结符号和非终结符号
- 非终结符号 → 空字符串
因此,处理过程中需要为输入的每个“单词”添加前缀的部分</>
可以通过正则表达式实现,因为它可以很容易地用 来表达(用sed
白话文,使用扩展语法)s,\w+ ,&</>,g
。这很简单明了。请注意,输出正在重用输入逐字,因此,单词后面的空格也会出现在输出中。如果要删除它,则必须在后续转换中执行此操作(即后续正则表达式)。
但是,处理过程需要将第一个单词存储在内存中,并且不是作为第二个单词的前缀,而是作为每个后续单词的前缀,这部分无法使用正则表达式来表达。为此,您需要一个图灵完备语言,因为您既需要条件分支(当前单词是否超出第二个单词?),也需要更改任意数量的内存(将第一个单词存储在内存中以供将来使用)。请注意,正如其他解决方案所示,最后一个要求并不是严格需要的:可以使用技巧来避免它,并且只使用循环。
你可以用几乎任何图灵完备的语言(包括sed
,如它支持loop
语句,并且有一个临时缓冲区awk
),如果您需要或想要保持简单(和 POSIX),我会推荐。
这是我的 awk 解决方案:
{ printf "%s %s", $1, $2; for (i=3; i <= NF; i++) printf "</>%s %s", $1, $i; printf "\n" }
注意:此解决方案旨在易于阅读和理解,但它并不能完美地将输入转换为相应的输出:如果输入末尾缺少换行符,则输出仍然会有一个换行符。如果这对您来说是个问题,请参阅其他解决方案。