我想知道是否有可能编写一个100%可靠的sed命令来转义输入字符串中的任何正则表达式元字符,以便它可以在后续的sed命令中使用.像这样:
#!/bin/bash
# Trying to replace one regex by another in an input file with sed
search="/abc\n\t[a-z]\+\([^ ]\)\{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
Run Code Online (Sandbox Code Playgroud)
我知道有更好的工具可以使用固定字符串而不是模式,例如awk,perl或python.我想证明是否有可能sed.我会说让我们专注于基本的POSIX正则表达式,以获得更多乐趣!:)
我已经尝试了很多东西,但任何时候我都能找到一个打破了我的尝试的输入.我认为保持它是抽象的,因为script to escape不会导致任何人走错方向.
顺便说一下,讨论在这里提出来了.我认为这可能是收集解决方案的好地方,可能会破坏和/或详细说明.
mkl*_*nt0 72
注意:
bash即使在多行替换中也能实现强大转义的函数可以在本文的底部找到(加上一个使用内置支持进行这种转义的解决方案).perlperlbash脚本).bash为shell(符合POSIX标准的重构):sed:在信用到期时给予信用:我在此答案中找到了下面使用的正则表达式.
假设搜索字符串是单行字符串:
search='abc\n\t[a-z]\+\([^ ]\)\{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'
Run Code Online (Sandbox Code Playgroud)
^都放在它自己的字符集[...]表达式中,以将其视为文字.
^是一个char.你不能代表[^],因为它在那个位置有特殊的意义(否定).^chars.逃脱了\^.该方法很稳健,但效率不高.
在稳健性来自不试图预测所有特殊字符的正则表达式 -这将横跨正则表达式方言不同-但只集中在2功能被所有的正则表达式方言共享:
\如\<\b的\n命令:在替换字符串\{ \{1,3\}命令不是一个正则表达式,但它承认占位符是指通过正则表达式匹配(或整个字符串^通过索引()或特定捕获组的结果\^,sed...),所以这些必须进行转义,与(习惯的)正则表达式分隔符一起,s///.
假设替换字符串是单行字符串:
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
Run Code Online (Sandbox Code Playgroud)
sed:注意:只有在尝试匹配之前已读取多个输入行(可能是ALL)时才有意义.
由于工具,如s///与&上一个操作单个线在默认情况下的时候,都需要额外的步骤,使他们一次读取多行.
# Define sample multi-line literal.
search='/abc\n\t[a-z]\+\([^ ]\)\{2,3\}\3
/def\n\t[A-Z]\+\([^ ]\)\{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"
Run Code Online (Sandbox Code Playgroud)
\1 字符串,这是在正则表达式中对换行符进行编码的方式.\2将字符串 附加/到每个输出行但是最后一个(最后一个新行被忽略,因为它被添加了sed)sed然后从字符串中删除所有实际换行符(awk每当打印其模式空间时添加一行),用'\n'字符串有效地替换输入中的所有换行符.$!a\'$'\n''\\n'是符合POSIX标准的'\n'成语形式,它将所有输入行读取为循环,因此后续命令会立即对所有输入行进行操作.
<<<(仅限),您可以使用其tr -d '\n选项来简化一次读取所有输入行:sed'\n'的-e ':a' -e '$!{N;ba' -e '}'命令:# 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"
Run Code Online (Sandbox Code Playgroud)
sed.sed是符合POSIX的形式的-z习惯用法,它将所有输入行读取为循环.sed -z "s/$searchEscaped/foo/" <<<"$search"所有逸出sed,s///和\实例,如在单行溶液.-e ':a' -e '$!{N;ba' -e '}'然后 - sed预先确定所有实际换行符.'s/[&/\]/\\&/g用于按原样读取&命令的输出(以避免自动删除命令substitution()将执行的尾随换行).\/然后删除一个单个换行符,其s/\n/\\&/g'已经隐含地附加到输入.\基于以上(for IFS= read -d '' -r)的函数:sed用于正则表达式的引号(转义)$(...)用于呼叫的替换字符串的引号${REPLY%$'\n'}.<<<读一个单一的时间默认为线,采用bash多线串才有意义,在sed明确读取多个(或全部)在一次行命令.quoteRe())来调用函数将不适用于具有尾随换行符的字符串; 在那种情况下,使用类似的东西quoteSubst()# SYNOPSIS
# quoteRe <text>
quoteRe() { sed -e 's/[^^]/[&]/g; s/\^/\\^/g; $!a\'$'\n''\\n' <<<"$1" | tr -d '\n'; }
Run Code Online (Sandbox Code Playgroud)
# 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'}"
}
Run Code Online (Sandbox Code Playgroud)
例:
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"
Run Code Online (Sandbox Code Playgroud)
注意使用s///一次读取所有输入,以便多行替换工作.
sed 解:Perl内置支持在正则表达式中转义任意字符串以供字面使用:quoteRe()函数或其等效sed引用.
单线和多线串的方法相同; 例如:
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"
Run Code Online (Sandbox Code Playgroud)
注意使用$(...)一次读取所有输入,以便多行替换工作.
该IFS= read -d '' -r escapedValue <(quoteSubst "$value")选项允许在脚本之后,在任何文件名操作数之前放置-e ':a' -e '$!{N;ba' -e '}'-style Perl变量定义perl.
Ed *_*ton 15
对构建@ mklement0的回答在这个线程,下面的工具将取代任何单一行字符串使用任何其他单行字符串(而不是正则表达式)sed和bash:
$ 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"
Run Code Online (Sandbox Code Playgroud)
为了说明这个工具的必要性,认为试图取代a.*/b{2,}\nc与d&e\1f通过调用sed直接:
$ 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
Run Code Online (Sandbox Code Playgroud)
或使用上述工具:
$ sedstr 'a.*/b{2,}\nc' 'd&e\1f' file
d&e\1f
axx/bb\nc
Run Code Online (Sandbox Code Playgroud)
这很有用的原因是它可以很容易地扩充,以便在必要时使用单词分隔符替换单词,例如在GNU sed语法中:
sed "s/\<$escOld\>/$escNew/g" "$file"
Run Code Online (Sandbox Code Playgroud)
而实际操作字符串(例如awk's index())的工具不能使用字分隔符.