我想知道是否有可能编写一个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
我知道有更好的工具可以处理固定字符串而不是模式,例如awk
、perl
或python
。我只想证明sed
是否可行。我想说,让我们专注于基本的POSIX正则表达式,以获得更多乐趣!:)
我已经尝试了很多东西,但任何时候我都可以找到一个输入,打破了我的尝试。我认为保持它抽象为script to escape
不会导致任何人进入错误的方向。
顺便说一句,讨论到了这里。我想这可能是一个收集解决方案的好地方,可能会打破和/或阐述它们。
4条答案
按热度按时间kqlmhetl1#
注:
bash
函数能够实现健壮的转义**,甚至在*多行 * 替换中也是如此,可以在本文的底部找到**(另外还有一个perl
解决方案,它使用perl
内置的转义支持)。bash
脚本),可以健壮地执行*单行 * 替换。sed
命令的 * 改进 * 版本,并在calestyo's answer中进行了更正,如果您希望转义字符串常量,以便与 * 其他 * 正则表达式处理工具(如awk
和perl
)一起使用,则需要使用该命令。* * 对于跨工具使用,\
必须转义为\\
而不是[\]
,这意味着:而不是sed 's/[^^]/[&]/g; s/\^/\\^/g'
命令,则必须使用x1米11米1x
bash
为shell(可以进行符合POSIX的重构):单线解决方案
转义字符串文字以用作
sed
中的 * regex *:表扬:给予应得的表扬:我在this answer中找到了下面使用的正则表达式。
假设搜索字符串是 * single * 行字符串:
^
之外的每个字符都放置在其自己的字符集[...]
表达式中,以将其视为文本。^
是一个字符,您 * 不能 * 表示为[^]
,因为它在该位置具有特殊含义(求反)。^
字符转义为\^
。\
来转义它,因为这可能会将一个文字字符转换为元字符,例如,\<
和\b
在某些工具中是单词边界,\n
是换行符,\{
是RE间隔(如\{1,3\}
)的开始,等等。该方法是稳健的,但效率不高。
^
转义为\^
功能转义字符串文字以用作
sed
的s///
命令中的 * 替换字符串 *:sed
s///
命令中的替换字符串不是正则表达式,但它可以识别 * 占位符 *,这些占位符表示与正则表达式匹配的整个字符串(&
)或按索引列出的特定捕获组结果(\1
、\2
、...),因此必须将它们与(常用的)正则表达式分隔符/
一起转义。假设替换字符串是 * single * 行字符串:
多线解决方案
转义MULTI-LINE字符串文字以用作
sed
中的 * regex *:由于
sed
和awk
之类的工具在默认情况下一次只能操作 * 一行 *,因此需要额外的步骤才能使它们一次读取多行。'\n'
* strings *,这是正则表达式中换行符的编码方式。$!a\'$'\n''\\n'
将 * string *'\n'
附加到除最后一行之外的所有输出行(最后一个换行符被忽略,因为它是由<<<
添加的)tr -d '\n
从字符串中删除所有的 * actual * 换行符(sed
在打印其模式空间时加一),有效地用'\n'
字符串替换输入中的所有换行符。-e ':a' -e '$!{N;ba' -e '}'
是sed
习惯用法的POSIX兼容形式,该习惯用法在一个循环中读取 * 所有 * 输入行,因此让后续命令同时在所有输入行上操作。sed
(仅限),您可以使用其-z
选项来简化一次读取所有输入行的过程:x1米50英寸
转义MULTI-LINE字符串文字以用作
sed
的s///
命令中的 * 替换字符串 *:\
-转义。-e ':a' -e '$!{N;ba' -e '}'
是sed
习惯用法的POSIX兼容形式,它在一个循环中读取 * 所有 * 输入行。's/[&/\]/\\&/g
转义所有&
、\
和/
示例,与单行解决方案中一样。s/\n/\\&/g'
,然后\
-前缀所有实际换行符。IFS= read -d '' -r
用于 * 按原样 * 读取sed
命令的输出(以避免自动删除命令替换($(...)
)将执行的尾随换行符)。${REPLY%$'\n'}
删除一个 * single * 尾随换行符,<<<
已将其隐式附加到输入中。*
bash
函数 * 基于以上内容(对于sed
):quoteRe()
引号(转义)用于 * regex *s///
引号,用于s///
调用的 * 替换字符串 *。两者都能正确处理 * 多行 * 输入
请注意,由于
sed
在默认情况下一次只读取 * 一行 *,因此将quoteRe()
与多行字符串一起使用仅在一次显式读取多行(或所有)的sed
命令中有意义。此外,使用命令替换(
$(...)
)调用函数对于具有 * trailing * 换行符的字符串不起作用;在这种情况下,请使用类似IFS= read -d '' -r escapedValue <(quoteSubst "$value")
代码注意,使用
-e ':a' -e '$!{N;ba' -e '}'
一次读取所有输入,以便多行替换工作。perl
溶液:quotemeta()
function或其等效\Q...\E
。对于单线和多线串,方法是相同的;例如:
-0777
一次读取所有输入,以便多行替换工作。-s
选项允许将-<var>=<val>
样式的Perl变量定义放在--
之后,脚本之后,任何文件名操作数之前。qhhrdooz2#
基于@mklement0在本线程中的回答,以下工具将使用
sed
和bash
将任何单行字符串(与regexp相反)替换为任何其他单行字符串:为了说明此工具的必要性,考虑尝试通过直接调用
sed
将a.*/b{2,}\nc
替换为d&e\1f
:或使用上述工具:
这是有用的原因是,如果需要,它可以很容易地扩展到使用单词分隔符来替换单词,例如在GNU
sed
语法中:而实际操作字符串的工具(例如X1 M6 N1 X的X1 M7 N1 X)不能使用单词分隔符。
注意:不将
\
Package 在括号表达式中的原因是,如果您使用的工具接受[\]]
作为括号表达式中的文字]
(例如perl和大多数awk实现)来执行实际的最终替换(即代替sed "s/$escOld/$escNew/g"
),那么您不能使用以下方法:通过将
\
包含在[]
中来转义\
,因为\x
将变为[\][x]
,即\ or ] or [ or x
。因此,虽然
[\]
可能适用于当前所有的sed实现,但我们知道\\
将适用于所有的sed、awk、perl等实现,因此使用这种形式的转义。z0qdvdin3#
需要注意的是,在this和that one中,上述一些答案中使用的正则表达式:
似乎是错的
s/\^/\\^/g
,然后执行s/\\/\\\\/g
是错误的,因为任何先转义到\^
的^
将再次转义其\
。更好的办法似乎是:
's/[^\^]/[&]/g; s/[\^]/\\&/g;'
.[^^\\]
应仅为[^\^]
(或[^^\]
)。\
在方括号表达式中没有特殊含义,无需加引号。muk1a3rh4#
Bash参数扩展可用于转义用作Sed替换字符串的字符串:
在bash 5.2+中,它可以进一步简化:
将其封装在bash函数中: