Bash

在 sed 中使用正則表達式 (regex)

  • June 2, 2020

這是我未能掌握的一般主題的具體範例。

多年來,我使用 regex 和 sed 遞歸地查找/替換目錄中所有文件中所有出現的字元串,使用如下:

#FIND $GLOBALS['timechecks'] and REPLACE with completely_different_string
shopt -s globstar dotglob;
for file in /var/www/**/*; do
 if [[ -f $file ]] && [[ -w $file ]]; then
   sed -i -- 's/\$GLOBALS\['\''timechecks'\''\]/completely_different_string/g' "$file"
 fi
done

問題是,在我不知道的情況下,在 bash 中使用 Regex 有一些基本的東西。結果,我無法找出特定範例的解決方案。

我被卡住的目標字元串

$GLOBALS['timechecks']=addTimeCheck_sparky($GLOBALS['timechecks'], number_format(microtime(true),6,'.',''), __LINE__, basename(__FILE__));

我想出的正則表達式不起作用

這只是我的腳本中的 sed 行以及我想出的搜尋正則表達式,但無濟於事。

\$GLOBALS\['\''timechecks'\''\]=addTimeCheck_sparky[(]$GLOBALS\['\''timechecks'\''\][,][ ]number_format[(]microtime[(]true[)][,]6[,]'\''\.'\''[,]'\'''\''[)][,][ ]__LINE__[],[ ]basename[(]__FILE__[)][)][;]

正則表達式調試器

我在這個例子中使用了一個正則表達式調試器,它顯示了正則表達式找到了我的目標字元串,但它對我不起作用。調試器位於此連結。這是它顯示找到我的目標字元串的正則表達式:

\$GLOBALS\['timechecks\'\]=addTimeCheck_sparky\(\$GLOBALS\[\'timechecks\'\], number_format\(microtime\(true\),6,\'\.\',''\), __LINE__, basename\(__FILE__\)\)

正則表達式調試器的輸出問題:

首先,我在 de 中嘗試了我的正則表達式

  1. 我不知道為什麼調試器的正則表達式在我在那裡執行時起作用,但在我的 bash 腳本中卻沒有。
  2. 與我在 bash 中使用 sed 學習的正則表達式相比,正則表達式看起來“錯誤”
  3. 當我將調試器中的正則表達式插入用於執行此任務的腳本時,它不起作用。
  4. 由於我不明白,我無法修復它

我認為我對將有效正則表達式從調試器轉換為在 bash/sed 中工作一無所知的基本問題。

我搜尋了“如何在 bash 中將正則表達式與 sed 一起使用”,但沒有找到對這甚至是潛在問題的解釋。

相關問題:為什麼沒有生成器接受目標字元串作為輸入並提供可以找到它的正則表達式?

\$GLOBALS\['\''timechecks'\''\]=addTimeCheck_sparky[(]$GLOBALS
                                                     ^

那裡有一個未逃脫的$

\['\''timechecks'\''\][,][ ]number_format[(]microtime[(]true[)]
[,]6[,]'\''\.'\''[,]'\'''\''[)][,][ ]__LINE__[],[ ]basename[(]__FILE__[)][)][;]
                                             ^^

那應該是[,]

不轉義$甚至都不重要(至少對於 GNU sed),但那[],[ ]是括號表達式,[],裡面有空格。這是一個有效的正則表達式,只是不是你想要的,所以它不會產生任何錯誤。

但實際上,引用是如此痛苦。有時最好避免它。

讓我們將模式和替換字元串與測試文件一起放入一些文件中:

$ cat pat 
$GLOBALS['timechecks']=addTimeCheck_sparky($GLOBALS['timechecks'], number_format(microtime(true),6,'.',''), __LINE__, basename(__FILE__));
$ cat repl
hello!
$ cat test.txt
foo
$GLOBALS['timechecks']=addTimeCheck_sparky($GLOBALS['timechecks'], number_format(microtime(true),6,'.',''), __LINE__, basename(__FILE__));
bar

然後,用 Perl 替換字元串:

$ pat=$(< pat) repl=$(< repl) perl -i.bak -pe 's/\Q$ENV{pat}/$ENV{repl}/' test.txt
$ cat test.txt
foo
hello!
bar

從文件中讀取字元串時,無需在 shell 命令行上引用。此外,當模式來自變數並被\Q使用時,無需轉義模式中的特殊字元。在這裡,我通過環境將字元串傳遞給 Perl,因為它-i比命令行參數更有效。-p使perl行為有點像sed它為每個輸入行執行給定的腳本,-i.bak就像seds -i

相關問題:為什麼沒有生成器接受目標字元串作為輸入並提供可以找到它的正則表達式?

出色地。通常正則表達式與旨在匹配多個字元串的模式一起使用,並且程序可能很難知道哪些部分可以變化。儘管如果您一直在尋找固定字元串,那麼轉義特殊字元會有些簡單。但是,您實際上首先不需要正則表達式引擎。只是它們在常見的 Unix 工具中相當普遍。

您在評論中提到:

想一想,如果一行匹配這個字元串,這就是我需要知道的全部替換它:$GLOBALS['timechecks']=addTimeCheck_sparky

就像是

sed -- -e 's/^.*GLOBALS..timechecks..=addTimeCheck_sparky.*$/hello/' 

可用於匹配並替換整行。當然,這也將匹配#GLOBALS_atimecheckses=addTimeCheck_sparky和相關的變體,因為我作弊並將所有特殊字元替換為.. 但你明白了。

此外,如果您先備份原始文件,則始終可以備份副本,然後執行diff original.txt processed.txt以查看任何更改。

為我工作:

sed -- 's/\$GLOBALS\['\''timechecks'\''\]/completely_different_string/g' <<'END'
foo
$GLOBALS['timechecks']=addTimeCheck_sparky($GLOBALS['timechecks'], number_format(microtime(true),6,'.',''), __LINE__, basename(__FILE__));
bar
END
foo
completely_different_string=addTimeCheck_sparky(completely_different_string, number_format(microtime(true),6,'.',''), __LINE__, basename(__FILE__));
bar

這適用於 Mac 上的預設 BSD sed 和 GNU sed。


一個術語問題:沒有“bash sed”。bash 是您的互動式 shell,它也是一種程式語言。sed 是一種不同的程式語言。從 bash 的角度來看,sed 只是在 $PATH 中找到的另一個命令,例如lsor grepor …

引用自:https://unix.stackexchange.com/questions/589594