是否有可能使用sed可靠地转义正则expression式元字符
我想知道是否有可能编写一个100%可靠的sed命令来转义inputstring中的任何正则expression式元字符,以便它可以在随后的sed命令中使用。 喜欢这个: 
 #!/bin/bash # Trying to replace one regex by another in an input file with sed search="/abc\n\t[az]\+\([^ ]\)\{2,3\}\3" replace="/xyz\n\t[0-9]\+\([^ ]\)\{2,3\}\3" # Sanitize input search=$(sed 'script to escape' <<< "$search") replace=$(sed 'script to escape' <<< "$replace") # Use it in a sed command sed "s/$search/$replace/" input 
 我知道有更好的工具可以使用固定string而不是模式,例如awk , perl或python 。 我只想certificate是否有可能与sed 。 我会说让我们专注于基本的POSIX正则expression式,以获得更多的乐趣!  🙂 
 我已经尝试了很多东西,但是随时可以find一个打破我的尝试的input。 我认为把它抽象为script to escape不会导致任何人走错方向。 
顺便说一句, 这里的讨论来了。 我认为这可能是一个收集解决scheme的好地方,可能会打破和/或阐述它们。
注意:
-  如果您正在寻找基于此答案中讨论的技术的预先打包的function :
-   bash函数可以在多行replace中实现健壮的转义 ,可以在这篇文章的底部find(另外还有一个使用perl内置的perl解决scheme来支持这种转义)。
-   @ EdMorton的答案包含一个工具 ( bash脚本),强大的执行单行replace 。
 
-   
-  所有的片段都假设bash是shell(POSIX兼容的重新configuration是可能的):
单线解决scheme
 转义string文字作为正则expression式在sed : 
在信用到期时给予信用:我在下面的答案中find了正则expression式。
假设searchstring是一个单行string:
 search='abc\n\t[az]\+\([^ ]\)\{2,3\}\3' # sample input containing metachars. searchEscaped=$(sed 's/[^^]/[&]/g; s/\^/\\^/g' <<<"$search") # escape it. sed -n "s/$searchEscaped/foo/p" <<<"$search" # if ok, echoes 'foo' 
-  除了^以外的每个字符都放置在它自己的字符集expression式中,以将其视为文字。-  请注意, ^是一个字符。 你不能表示为[^],因为它在那个位置有特殊的含义(否定)。
 
-  请注意, 
-  然后, ^字符。 逃脱为\^。
该方法是强大的,但效率不高。
稳健性来自于不试图预测所有特殊的正则expression式字符 – 这些字符在正则expression式方面会有所不同 – 但只关注所有正则expression式方言共享的 2个特征 :
- 在字符集内指定文字字符的能力。
-  能够将文字^转义为\^
 转义string文字作为sed s///命令中的replacestring : 
  sed s///命令中的replacestring不是一个正则expression式,但它可以识别引用由正则expression式( & )匹配的整个string或索引( \1 , \2 ,…)的特定捕获组结果的占位符 。 ..),所以这些必须和(习惯的)正则expression式分隔符一起被转义。 
假设replacestring是一个单行string:
 replace='Laurel & Hardy; PS\2' # sample input containing metachars. replaceEscaped=$(sed 's/[&/\]/\\&/g' <<<"$replace") # escape it sed -n "s/\(.*\) \(.*\)/$replaceEscaped/p" <<<"foo bar" # if ok, outputs $replace as is 
多线解决scheme
 转义多行string文字作为正则expression式在sed : 
  注意 :如果在尝试匹配之前已经读取了多个input行 (可能是ALL),则这是唯一有意义的。 
 由于像sed和awk这样的工具默认一次只能在一行上操作,所以需要额外的步骤来使它们一次读取多行。 
 # Define sample multi-line literal. search='/abc\n\t[az]\+\([^ ]\)\{2,3\}\3 /def\n\t[AZ]\+\([^ ]\)\{3,4\}\4' # Escape it. searchEscaped=$(sed -e 's/[^^]/[&]/g; s/\^/\\^/g; $!a\'$'\n''\\n' <<<"$search" | tr -d '\n') #' # Use in a Sed command that reads ALL input lines up front. # If ok, echoes 'foo' sed -n -e ':a' -e '$!{N;ba' -e '}' -e "s/$searchEscaped/foo/p" <<<"$search" 
-  多行inputstring中的换行符必须转换为'\n'string ,这就是换行符中的换行符。
-   $!a\'$'\n''\\n'将string'\n'$!a\'$'\n''\\n'附加到每个输出行,但是最后一个换行符被忽略,因为它是由<<<添加的。
-   tr -d '\n然后从string中删除所有实际的换行符(sed在打印模式空间时会添加一个换行符),用'\n'string有效地replaceinput中的所有换行符。
-   -e ':a' -e '$!{N;ba' -e '}'是符合POSIX标准的sed语言forms,可以读取所有input行循环,因此在随后的命令一旦。
 转义多行string文字作为sed s///命令中的replacestring : 
 # Define sample multi-line literal. replace='Laurel & Hardy; PS\2 Masters\1 & Johnson\2' # Escape it for use as a Sed replacement string. IFS= read -d '' -r < <(sed -e ':a' -e '$!{N;ba' -e '}' -e 's/[&/\]/\\&/g; s/\n/\\&/g' <<<"$replace") replaceEscaped=${REPLY%$'\n'} # If ok, outputs $replace as is. sed -n "s/\(.*\) \(.*\)/$replaceEscaped/p" <<<"foo bar" 
- inputstring中的换行符必须保留为实际换行符,但是转义。
-   -e ':a' -e '$!{N;ba' -e '}'是一个sed习惯用法的符合POSIX的forms,它读取所有input行循环。
-   's/[&/\]/\\&/g像在单行解决scheme中那样转义所有&,\和/instances。
-   s/\n/\\&/g'g'then\-prefixes all actual newlines。
-   IFS= read -d '' -r用于按原样读取sed命令的输出(以避免自动删除命令replace($(...))将执行的尾随换行符)。
-   ${REPLY%$'\n'}然后删除一个尾部的换行符,<<<已隐式地附加到input。
 基于上述(对于sed ) bash函数 : 
-   quoteRe()引号(转义)在正则expression式中使用
-   quoteSubst()引号用于s///调用的replacestring 。
-  都能正确处理多行input
-  请注意,因为sed在默认情况下会一次读取一行,所以在多行string中使用quoteRe()只在同时显式读取多个(或全部)行的sed命令中才有意义。
-  此外,使用命令replace( $(...))来调用函数将不适用于具有尾随换行符的string; 在这种情况下,使用类似IFS= read -d '' -r escapedValue <(quoteSubst "$value")
 
-  请注意,因为
 # SYNOPSIS # quoteRe <text> quoteRe() { sed -e 's/[^^]/[&]/g; s/\^/\\^/g; $!a\'$'\n''\\n' <<<"$1" | tr -d '\n'; } 
 # SYNOPSIS # quoteSubst <text> quoteSubst() { IFS= read -d '' -r < <(sed -e ':a' -e '$!{N;ba' -e '}' -e 's/[&/\]/\\&/g; s/\n/\\&/g' <<<"$1") printf %s "${REPLY%$'\n'}" } 
例:
 from=$'Cost\(*):\n$3.' # sample input containing metachars. to='You & I'$'\n''eating A\1 sauce.' # sample replacement string with metachars. # Should print the unmodified value of $to sed -e ':a' -e '$!{N;ba' -e '}' -e "s/$(quoteRe "$from")/$(quoteSubst "$to")/" <<<"$from" 
 请注意使用-e ':a' -e '$!{N;ba' -e '}'来一次读取所有input,以便多行replace工作。 
  perl解决scheme: 
  Perl内置了对在正则expression式中使用的任意string的转义支持 : quotemeta()函数或其等价的\Q...\E引用 。 
 单行和多行string的方法是一样的; 例如: 
 from=$'Cost\(*):\n$3.' # sample input containing metachars. to='You owe me $1/$& for'$'\n''eating A\1 sauce.' # sample replacement string w/ metachars. # Should print the unmodified value of $to. # Note that the replacement value needs NO escaping. perl -s -0777 -pe 's/\Q$from\E/$to/' -- -from="$from" -to="$to" <<<"$from" 
- 
请注意使用 -0777来一次读取所有input,以便多线replace工作。
- 
-s选项允许在脚本之后,在任何文件名操作数之前放置-<var>=<val>样式的Perlvariables定义。
 build立在@ mklement0在这个线程中的答案 ,下面的工具将使用sed和bashreplace任何其他单行string的任何单行string(而不是正则expression式): 
 $ cat sedstr #!/bin/bash old="$1" new="$2" file="${3:--}" escOld=$(sed 's/[^^]/[&]/g; s/\^/\\^/g' <<< "$old") escNew=$(sed 's/[&/\]/\\&/g' <<< "$new") sed "s/$escOld/$escNew/g" "$file" 
 为了说明这个工具的必要性,可以考虑直接调用sed用d&e\1f代替a.*/b{2,}\nc : 
 $ cat file a.*/b{2,}\nc axx/bb\nc $ sed 's/a.*/b{2,}\nc/d&e\1f/' file sed: -e expression #1, char 16: unknown option to `s' $ sed 's/a.*\/b{2,}\nc/d&e\1f/' file sed: -e expression #1, char 23: invalid reference \1 on `s' command's RHS $ sed 's/a.*\/b{2,}\nc/d&e\\1f/' file a.*/b{2,}\nc axx/bb\nc # .... and so on, peeling the onion ad nauseum until: $ sed 's/a\.\*\/b{2,}\\nc/d\&e\\1f/' file d&e\1f axx/bb\nc 
或者使用上面的工具:
 $ sedstr 'a.*/b{2,}\nc' 'd&e\1f' file d&e\1f axx/bb\nc 
 这很有用的原因是,如果需要,可以使用单词分隔符来replace单词,例如在GNU sed语法中,它可以很容易地被扩充: 
 sed "s/\<$escOld\>/$escNew/g" "$file" 
 而实际上对string进行操作的工具(例如awk的index() )不能使用单词分隔符。