如何基於列表進行 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].

相關內容