%EF%BC%9F%E6%88%91%E9%9C%80%E8%A6%81%E5%B0%87%E5%A4%9A%E5%80%8B%E5%96%AE%E5%AD%97%E8%88%87%E5%85%B6%E4%BB%96%E5%B0%8D%E6%87%89%E7%9A%84%E5%96%AE%E5%AD%97%E4%BA%A4%E6%8F%9B.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].