一次替換多個字串

一次替換多個字串

我正在尋找一種方法,使用常見的 Unix 工具(bash、sed、awk,也許是 perl)用具體值替換模板檔案中的佔位符字串。重要的是,替換是在單遍中完成的,也就是說,已經掃描/替換的內容不得考慮進行另一次替換。例如,這兩次嘗試都失敗了:

echo "AB" | awk '{gsub("A","B");gsub("B","A");print}'
>> AA

echo "AB" | sed 's/A/B/g;s/B/A/g'
>> AA

這種情況下正確的結果當然是BA。

一般來說,該解決方案應該相當於從左到右掃描輸入以查找與給定替換字串之一的最長匹配,並且對於每個匹配,執行替換並從輸入中的該點繼續(沒有已讀取的輸入或執行的替換都應被視為匹配)。實際上,細節並不重要,只是替換的結果永遠不會被考慮用於另一次替換,無論是全部還是部分。

筆記我只是在尋找正確的通用解決方案。請不要提出對某些輸入(輸入檔案、搜尋和替換對)失敗的解決方案,無論它們看起來多麼不可能。

答案1

好的,通用解決方案。以下 bash 函數需要2k參數;每對由一個佔位符和一個替換符組成。您可以適當地引用字串以將它們傳遞到函數中。如果參數數量為奇數,則會新增隱式空參數,這將有效刪除最後一個佔位符的出現。

佔位符和替換都不能包含 NUL 字符,但您可以使用標準的 C\轉義符,例如\0如果您需要s (因此如果您想要 a ,則NUL需要編寫)。\\\

它需要標準建置工具,這些工具應該存在於類別 posix 系統(lex 和 cc)上。

replaceholder() {
  local dir=$(mktemp -d)
  ( cd "$dir"
    { printf %s\\n "%option 8bit noyywrap nounput" "%%"
      printf '"%s" {fputs("%s", yyout);}\n' "${@//\"/\\\"}"
      printf %s\\n "%%" "int main(int argc, char** argv) { return yylex(); }"
    } | lex && cc lex.yy.c
  ) && "$dir"/a.out
  rm -fR "$dir"
}

我們假設\參數中如有必要,已經轉義,但我們需要轉義雙引號(如果存在)。這就是第二個 printf 的第二個參數的作用。由於lex預設操作是ECHO,所以我們不需要擔心它。

運行範例(對於持懷疑態度的人來說,它只是一個廉價的商用筆記型電腦):

$ time echo AB | replaceholder A B B A
BA

real    0m0.128s
user    0m0.106s
sys     0m0.042s
$ time printf %s\\n AB{0000..9999} | replaceholder A B B A > /dev/null

real    0m0.118s
user    0m0.117s
sys     0m0.043s

對於較大的輸入,向 提供最佳化標誌可能會很有用cc,並且對於當前的 Posix 相容性,最好使用c99。更雄心勃勃的實現可能會嘗試快取生成的可執行文件,而不是每次都生成它們,但生成它們並不完全昂貴。

編輯

如果你有TCC,您可以避免建立臨時目錄的麻煩,並享受更快的編譯時間,這將有助於正常大小的輸入:

treplaceholder () { 
  tcc -run <(
  {
    printf %s\\n "%option 8bit noyywrap nounput" "%%"
    printf '"%s" {fputs("%s", yyout);}\n' "${@//\"/\\\"}"
    printf %s\\n "%%" "int main(int argc, char** argv) { return yylex(); }"
  } | lex -t)
}

$ time printf %s\\n AB{0000..9999} | treplaceholder A B B A > /dev/null

real    0m0.039s
user    0m0.041s
sys     0m0.031s

答案2

printf 'STRING1STRING1\n\nSTRING2STRING1\nSTRING2\n' |
od -A n -t c -v -w1 |
sed 's/ \{1,3\}//;s/\\$/&&/;H;s/.*//;x
     /\nS\nT\nR\nI\nN\nG\n1/s//STRING2/
     /\nS\nT\nR\nI\nN\nG\n2/s//STRING1/
     /\\n/!{x;d};s/\n//g;s/./\\&/g' |
     xargs printf %b

###OUTPUT###

STRING2STRING2

STRING1STRING2
STRING1

像這樣的東西總是只會替換目標字串的每次出現一次,因為它們出現在sed流中,每行一次。這是我能想到的最快的方法。話又說回來,我不寫C。如果您願意,可以可靠地處理空分隔符號。看這個答案了解它是如何工作的。這對於任何包含的特殊 shell 字元或類似字元都沒有問題 - 但它特定於 ASCII 語言環境,或者換句話說,od不會在同一行上輸出多字節字符,並且只會輸出一個。如果這是一個問題,您需要添加iconv.

答案3

一個perl辦法。即使有人說這是不可能的,我也找到了一個,但一般來說,簡單的匹配和替換是不可能的,甚至由於 NFA 的回溯而變得更糟,結果也可能是意外的。

一般來說,必須指出的是,該問題會產生不同的結果,這取決於替換元組的順序和長度。 IE:

A B
AA CC

輸入AAA結果為BBBCCB

這裡是程式碼:

#!/usr/bin/perl

$v='if (0) {} ';
while (($a,$b)=split /\s+/, <DATA>) {
  $k.=$a.'|';
  $v.='elsif ($& eq \''.$a.'\') {print \''.$b.'\'} ';
}
$k.='.';
$v.='else {print $&;}';

eval "
while (<>) {
  \$_ =~ s/($k)/{$v}/geco;
}";  
print "\n";


__DATA__
A    B
B    A
abba baab
baab abbc
abbc aaba

棋盤兔:

$ echo 'ABBabbaBBbaabAAabbc'|perl script
$ BAAbaabAAabbcBBaaba

相關內容