如何逃避用户提供的不希望评估sed的搜索词?

时间:2010-02-25 01:10:40

标签: bash sed escaping

我正在尝试逃避用户提供的搜索字符串,该字符串可以包含任意字符并将其提供给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程序),但很高兴知道是否有解决此问题的方法。

8 个答案:

答案 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手册页以开始......