![如何基于列表进行 sed 替换 (s///g)?我需要将多个单词与其他对应的单词交换](https://linux22.com/image/94953/%E5%A6%82%E4%BD%95%E5%9F%BA%E4%BA%8E%E5%88%97%E8%A1%A8%E8%BF%9B%E8%A1%8C%20sed%20%E6%9B%BF%E6%8D%A2%20(s%2F%2F%2Fg)%EF%BC%9F%E6%88%91%E9%9C%80%E8%A6%81%E5%B0%86%E5%A4%9A%E4%B8%AA%E5%8D%95%E8%AF%8D%E4%B8%8E%E5%85%B6%E4%BB%96%E5%AF%B9%E5%BA%94%E7%9A%84%E5%8D%95%E8%AF%8D%E4%BA%A4%E6%8D%A2.png)
我认为以前没有人问过这个问题,所以我不知道是否sed
有能力做到这一点。
假设我在一个句子中有一堆数字,需要将其扩展为单词,一个实际的例子是将典型论文中的编号引文交换为 MLA 格式:
essay.txt
:
Sentence 1 [1]. sentence two [1][2]. Sentence three[1][3].
Key.txt
(这是一个制表符分隔的文件):
1 source-one
2 source-two
3 source-three
...etc
预期的Result.txt
:
Sentence 1 [source-one]. sentence two [source-one][source-two]. Sentence three[source-one][source-three]
这是我的伪代码尝试,但我对此了解不够sed
或tr
无法正确执行:
cat essay.txt | sed s/$(awk {print $1} key.txt)/$(awk {print $2} key.txt)/g
PS:如果 notepad++ 中有一个使用多个术语进行批量查找和替换的技巧,那就太好了。事实上,查找和替换似乎一次只适用于一个术语,但我需要一种方法来同时对多个术语进行集体操作。
答案1
你应该使用perl
:
$ perl -ne '
++$nr;
if ($nr == $.) {
@w = split;
$k{$w[0]} = $w[1];
}
else {
for $i (keys %k) {
s/(\[)$i(\])/$1.$k{$i}.$2/ge
}
print;
}
close ARGV if eof;
' key.txt essay.txt
Sentence 1 [source-one]. sentence two [source-one][source-two]. Sentence three[source-one][source-three]
答案2
awk
perl
可以有效地做与这里相同的事情简单一点,尽管 GNU 以外的实现可能会浪费一点 CPU 时间来不必要地分割(大?)文本文件:
awk 'NR==FNR{a["\\["$1"\\]"]="["$2"]";next} {for(k in a) gsub(k,a[k]);print}' key.txt essay.txt
既然你要求了解释:
awk
通过采用由模式-动作对组成的“脚本”进行操作,然后一次读取一个或多个文件(或标准输入)一个“记录”,默认情况下每条记录都是一行,并且对于每条记录将其拆分为字段默认为空白(包括制表符),并通过依次(除非另有指示)测试每个模式(通常查看当前记录和/或其字段)以及它是否匹配执行操作(通常会执行某些操作来应用脚本)或与所述记录和/或字段一起)。这里我指定了两个文件,key.txt essay.txt
因此它按该顺序逐行读取这两个文件。剧本能放在文件中而不是放在命令行中,但在这里我选择不这样做。第一个模式是
NR==FNR
.NR
是一个内置变量,它是正在处理的记录的编号;FNR
类似地,是当前输入文件中的记录号。对于第一个文件 (key.txt
),这些是相等的;对于第二个文件(以及任何其他文件),它们不相等第一个动作是
{a["\\["$1"\\]"]="["$2"]";next}
。awk
具有“关联”或“散列”数组;arrayname[subexpr]
其中subexpr
是字符串值表达式,读取或设置数组的元素。$number
例如$1 $2
等引用字段,并$0
引用整个记录。根据上面的内容,此操作仅对 in 中的行执行,key.txt
例如该文件的最后一行$1
is3
和$2
issource-three
,并且它存储一个下标为\[3\]
、内容为 的数组条目[source-three]
;请参阅下文了解我选择这些值的原因。 and"\\["
是"\\]"
使用转义符的字符串文字,其实际值为 ,\[
而\]
while"[" "]"
只是[ ]
,并且它们之间没有运算符的字符串操作数被连接。最后执行此操作,next
这意味着跳过此记录的脚本的其余部分,只需返回到循环顶部并开始下一条记录。第二个模式为空,因此它匹配第二个文件中的每一行并执行操作
{for(k in a) gsub(k,a[k]);print}
。该for(k in a)
构造创建了一个循环,与 Bourne 类型 shell 在 中所做的非常相似for i in this that other; do something with $i; done
,只是这里的值k
是下标数组的a
.对于每个这样的值,它执行gsub
(全局替换),查找给定正则表达式的所有匹配项并将它们替换为给定字符串;我选择了数组中的下标和内容(上面),因此,例如\[3\]
是一个与文本字符串匹配的正则表达式[3]
,并且[source-three]
是您想要替换每个此类匹配的文本字符串。默认情况下gsub
对当前记录进行操作$0
。对其中的所有值进行此替换后,默认情况下a
会执行当前print
的输出$0
,并完成所有所需的替换。
注意:GNU awk (gawk) 在 Linux 上很常见,但并不通用,它有一个优化,如果执行的模式或操作中没有任何内容需要字段值,它实际上不会执行字段分割。在其他实现中,可能会浪费少量的 CPU 时间,而 cuonglm 的perl
方法避免了这种情况,但除非您的文件很大,否则这种情况可能不会被注意到。
答案3
bash$ sed -f <( sed -rn 's#([0-9]+)\s+(.*)#s/\\[\1]/[\2]/g#p' key.txt ) essay.txt
Sentence 1 [source-one]. sentence two [source-one][source-two]. Sentence three[source-one][source-three].
答案4
您可以在循环内使用就地 sed 替换来实现此目的:
$ cp essay.txt Result.txt
$ while read n k; do sed -i "s/\[$n\]/\[$k\]/g" Result.txt; done < key.txt
$ cat Result.txt
Sentence 1 [source-one]. sentence two [source-one][source-two]. Sentence three[source-one][source-three].