如何基于列表进行 sed 替换 (s///g)?我需要将多个单词与其他对应的单词交换

如何基于列表进行 sed 替换 (s///g)?我需要将多个单词与其他对应的单词交换

我认为以前没有人问过这个问题,所以我不知道是否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]

这是我的伪代码尝试,但我对此了解不够sedtr无法正确执行:

 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

awkperl可以有效地做与这里相同的事情简单一点,尽管 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例如该文件的最后一行$1is3$2is source-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].

相关内容