我正在尝试逃避用户提供的搜索字符串,该字符串可以包含任意字符并将其提供给sed,但无法弄清楚如何使其安全地供sed使用。在sed中,我们做s/search/replace/
,我想在没有sed解释的情况下搜索搜索字符串中的字符(例如,'my / path'中的'/'不会关闭sed表达式。)< / p>
我读了this related question关于如何逃避替换术语的问题。我本以为你会对搜索做同样的事情,但显然不是因为sed抱怨。
这是一个示例程序,用于创建名为“my_searches”的文件。然后它读取该文件的每一行并执行搜索并使用sed替换。
#!/bin/bash
# The contents of this heredoc will be the lines of our file.
read -d '' SAMPLES << 'EOF'
/usr/include
P@$$W0RD$?
"I didn't", said Jane O'Brien.
`ls -l`
~!@#$%^&*()_+-=:'}{[]/.,`"\|
EOF
echo "$SAMPLES" > my_searches
# Now for each line in the file, do some search and replace
while read line
do
echo "------===[ BEGIN $line ]===------"
# Escape every character in $line (e.g., ab/c becomes \a\b\/\c). I got
# this solution from the accepted answer in the linked SO question.
ES=$(echo "$line" | awk '{gsub(".", "\\\\&");print}')
# Search for the line we read from the file and replace it with
# the text "replaced"
sed 's/'"$ES"'/replaced/' < my_searches # Does not work
# Search for the text "Jane" and replace it with the line we read.
sed 's/Jane/'"$ES"'/' < my_searches # Works
# Search for the line we read and replace it with itself.
sed 's/'"$ES"'/'"$ES"'/' < my_searches # Does not work
echo "------===[ END ]===------"
echo
done < my_searches
运行程序时,如果文件的最后一行用作“搜索”字词,则会得到sed: xregcomp: Invalid content of \{\}
,而不是“替换”字词。我已在上面# Does not work
标记了出现此错误的行。
------===[ BEGIN ~!@#$%^&*()_+-=:'}{[]/.,`"| ]===------
sed: xregcomp: Invalid content of \{\}
------===[ END ]===------
如果你没有转义$line
中的字符(即sed 's/'"$line"'/replaced/' < my_searches
),则会收到此错误,因为sed会尝试解释各种字符:
------===[ BEGIN ~!@#$%^&*()_+-=:'}{[]/.,`"| ]===------
sed: bad format in substitution expression
sed: No previous regexp.
------===[ END ]===------
那么如何转义sed的搜索词,以便用户可以提供任何搜索的任意文本?或者更确切地说,我可以在代码中替换ES=
行,以便sed命令适用于文件中的任意文本?
我正在使用sed,因为我只限于busybox中包含的一部分实用程序。虽然我可以使用其他方法(如C程序),但很高兴知道是否有解决此问题的方法。
答案 0 :(得分:1)
这是一个相对着名的问题 - 给定一个字符串,产生一个只匹配该字符串的模式。某些语言比其他语言更容易,sed
是令人讨厌的语言之一。我的建议是避免sed
并用其他语言编写自定义程序。
您可以使用标准库函数strstr
编写自定义C程序。如果这还不够快,您可以使用Google可以找到的任何Boyer-Moore字符串匹配器 - 它们将使搜索速度极快(次线性时间)。
您可以在Lua:
中轻松地写出来local function quote(s) return (s:gsub('%W', '%%%1')) end
local function replace(first, second, s)
return (s:gsub(quote(first), second))
end
for l in io.lines() do io.write(replace(arg[1], arg[2], l), '\n') end
如果速度不够快,只需将quote
应用于arg[1]
一次,然后将内联语replace
应用于{{1}},就可以加快速度。
答案 1 :(得分:0)
这个:echo "$line" | awk '{gsub(".", "\\\\&");print}'
转义$line
中的每个角色,这是错误的!之后执行echo $ES
,$ ES似乎是\/\u\s\r\/\i\n\c\l\u\d\e
。然后当你转到下一个sed时,(下面)
sed 's/'"$ES"'/replaced/' my_searches
,它不起作用,因为没有模式\/\u\s\r\/\i\n\c\l\u\d\e
的行。正确的方法是:
$ sed 's|\([@$#^&*!~+-={}/]\)|\\\1|g' file
\/usr\/include
P\@\$\$W0RD\$?
"I didn't", said Jane O'Brien.
\`ls -l\`
\~\!\@\#\$%\^\&\*()_\+-\=:'\}\{[]\/.,\`"\|
将所有要转义的字符放在[]
内,并为不属于您的字符类的sed选择合适的分隔符,例如我选择“|”。然后使用“g”(全局)标志。
告诉我们你真正要做的是什么,即你要解决的实际问题。
答案 2 :(得分:0)
正如ghostdog所提到的,awk '{gsub(".", "\\\\&");print}'
是不正确的,因为它逃脱了非特殊字符。你真正想做的事情可能是:
awk 'gsub(/[^[:alpha:]]/, "\\\\&")'
这将逃避非字母字符。由于某种原因,我还没有确定,即使我的代码正确地将其转义为
,我仍然无法替换"I didn't", said Jane O'Brien.
\"I\ didn\'t\"\,\ said\ Jane\ O\'Brien\.
这很奇怪,因为这很好用
$ echo "\"I didn't\", said Jane O'Brien." | sed s/\"I\ didn\'t\"\,\ said\ Jane\ O\'Brien\./replaced/
replaced`
答案 3 :(得分:0)
这似乎适用于FreeBSD sed:
# using FreeBSD & Mac OS X sed
ES="$(printf "%q" "${line}")"
ES="${ES//+/\\+}"
sed -E s$'\777'"${ES}"$'\777'replaced$'\777' < my_searches
sed -E s$'\777'Jane$'\777'"${line}"$'\777' < my_searches
sed -E s$'\777'"${ES}"$'\777'"${line}"$'\777' < my_searches
答案 4 :(得分:0)
FreeBSD sed的-E选项用于打开扩展正则表达式。
分别通过-r或--regexp-extended选项可以用于GNU sed。
有关基本和扩展正则表达式之间的差异,请参阅:
http://www.gnu.org/software/sed/manual/sed.html#Extended-regexps
也许你可以使用FreeBSD兼容minised而不是GNU sed?
# example using FreeBSD-compatible minised,
# http://www.exactcode.de/site/open_source/minised/
# escape some punctuation characters with printf
help printf
printf "%s\n" '!"#$%&'"'"'()*+,-./:;<=>?@[\]^_`{|}~'
printf "%q\n" '!"#$%&'"'"'()*+,-./:;<=>?@[\]^_`{|}~'
# example line
line='!"#$%&'"'"'()*+,-./:;<=>?@[\]^_`{|}~ ... and Jane ...'
# escapes in regular expression
ES="$(printf "%q" "${line}")" # escape some punctuation characters
ES="${ES//./\\.}" # . -> \.
ES="${ES//\\\\(/(}" # \( -> (
ES="${ES//\\\\)/)}" # \) -> )
# escapes in replacement string
lineEscaped="${line//&/\&}" # & -> \&
minised s$'\777'"${ES}"$'\777'REPLACED$'\777' <<< "${line}"
minised s$'\777'Jane$'\777'"${lineEscaped}"$'\777' <<< "${line}"
minised s$'\777'"${ES}"$'\777'"${lineEscaped}"$'\777' <<< "${line}"
答案 5 :(得分:0)
为了避免潜在的反斜杠混淆,我们可以(或者更应该)使用反斜杠变量,如下所示:
backSlash='\\'
ES="${ES//${backSlash}(/(}" # \( -> (
ES="${ES//${backSlash})/)}" # \) -> )
(通过这种方式使用变量似乎是解决参数扩展问题的好方法......)
答案 6 :(得分:0)
...或完成反斜杠混淆...
backSlash='\\'
lineEscaped="${line//${backSlash}/${backSlash}}" # double backslashes
lineEscaped="${lineEscaped//&/\&}" # & -> \&
答案 7 :(得分:0)
如果你有bash,并且你只是在进行模式替换,那么只需在bash中进行本地化。 Bash中的${parameter/pattern/string}
扩展对你来说非常有效,因为你可以使用一个变量来代替“pattern”和替换“string”,变量的内容将不受单词扩展的影响。正是这个词的扩展使管道变得如此麻烦。 :)
无论如何,它都要比分叉子工艺和管道更快。您已经知道如何处理整个while read line
事件,因此创造性地应用Bash现有参数扩展文档中的功能可以帮助您重现您使用sed可以执行的任何操作。查看bash手册页以开始......