我有一个名为swap
:
$ cat swap
aardvark BILL CLINTON dog
eternal CLINTON BILL forever
BILL good housekeeping BILL
我想更改所有出现的BILL
toCLINTON
并同时更改所有出现的CLINTON
to BILL
,结果输出为:
aardvark CLINTON BILL dog
eternal BILL CLINTON forever
CLINTON good housekeeping CLINTON
我知道经典的解决方案:
sed -e 's/BILL/temp/g' -e 's/CLINTON/BILL/g' -e 's/temp/CLINTON/g'
但有人向我提出挑战,作为练习,要求我不要使用第三个词。
我正在使用以下命令,但它仅在第一行起作用:
$ sed 's/\(BILL\) \(CLINTON\)/\2 \1/g' swap
aardvark CLINTON BILL dog
eternal CLINTON BILL forever
BILL good housekeeping BILL
注意:我使用的是 Solaris 10 操作系统。
在观察这种人为约束的同时,如何获得所需的输出?
行为
MORBILLIFORM OVERBILLED
TOM, BILL, AND HARRISON
未指定。
答案1
perl -pe 's/(BILL)|CLINTON/$1 ? "CLINTON" : "BILL"/eg' swap
解释:
-p
选项使得Perl
读取文件 la awk
,逐行并且当前记录在应用所有转换后自动打印。
该s///
命令在当前行起作用。模式/eg
修饰符是:/g
将在当前行全局应用转换,而不仅仅是将其自身限制为第一行(如果您没有提到/g
.是-uate/e
修饰符eval
,它使 的RHS
被s///
视为Perl
代码,并且在评估后其结果的值被视为 RHS。
因此,s/(BILL)|CLINTON/.../ 将从左侧开始扫描,在当前记录中查找 BILL 或 CLINTON。当找到 BILL 时,$1 就被设置,因此 Perl 表达式 $1 ? "CLINTON" : "BILL" 将计算为 CLINTON,这是当前记录中 BILL 被替换的内容。但由于修饰符,我们已经完成了/g
。同样,如果找到 CLINTON,则 $1 为空,因此 Perl 表达式 $1 ? "CLINTON" : "BILL" 被计算为 BILL,这就是 CLINTON 在这个记录一直持续到当前达到的末尾,此时由于该-p
选项,它被打印到 STDOUT。
sed -e 's/BILL\|CLINTON/\n&/g;s/\nBILL/CLINTON/g;s/\nCLINTON/BILL/g' swap
答案2
awk解决方案:
awk '{ for(i=1;i<=NF;i++) { if($i=="BILL") $i="CLINTON"; else if($i=="CLINTON") $i="BILL" } }1' swap
输出:
CLINTON BILL
BILL CLINTON
CLINTON CLINTON
for(i=1;i<=NF;i++)
- 迭代所有字段(awk将空格视为默认字段分隔符)if($i=="BILL") $i="CLINTON"
- 如果字段值等于BILL
- 为其分配CLINTON
else if($i=="CLINTON") $i="BILL"
- 否则,如果字段值等于CLINTON
- 将其分配给BILL
答案3
另一个 awk 解决方案
awk '{
while (match($0, /BILL|CLINTON/)) {
printf "%s", substr($0, 1, RSTART-1);
$0 = substr($0, RSTART);
printf "%s", /^BILL/ ? "CLINTON" : "BILL";
$0 = substr($0, 1+RLENGTH)
}
print
}' swap