Как вы избегаете пользовательского поискового запроса, который вы не хотите оценивать для sed? - PullRequest
4 голосов
/ 25 февраля 2010

Я пытаюсь избежать предоставленной пользователем строки поиска, которая может содержать любой произвольный символ, и передать его в sed, но не могу понять, как сделать его безопасным для использования в sed. В sed мы делаем s/search/replace/, и я хочу искать точно символы в строке поиска, не интерпретируя их с помощью sed (например, '/' в 'my / path' не закрывает выражение sed).

Я прочитал этот связанный вопрос о том, как избежать термина replace . Я бы подумал, что вы сделаете то же самое с поиском , но, очевидно, не потому, что 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 ]

1 голос
/ 25 февраля 2010

Это довольно известная проблема - учитывая строку, создайте шаблон, который соответствует только этой строке. В некоторых языках это проще, чем в других, и sed - один из раздражающих. Мой совет - избегать sed и писать собственные программы на каком-то другом языке.

  • Вы можете написать собственную программу на C, используя стандартную библиотечную функцию strstr. Если это не достаточно быстро, вы можете использовать любой из сопоставителей строк Бойера-Мура, который вы можете найти в Google - они сделают поиск чрезвычайно быстрым (сублинейное время).

  • Вы можете написать это достаточно легко в 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.

0 голосов
/ 12 ноября 2010

Если у вас есть bash, и вы просто делаете замену шаблона, просто сделайте это изначально в bash. Расширение ${parameter/pattern/string} в Bash будет работать очень хорошо для вас, так как вы можете просто использовать переменную вместо «pattern» и замещающей «string», и ее содержимое будет защищено от расширения слова. И это то расширение слова, которое заставляет обуздывать такие неприятности. :)

Это будет быстрее, чем разветвление дочернего процесса и все равно пускание в седь. Вы уже знаете, как все это сделать while read line, поэтому творческое применение возможностей существующей документации расширения параметров Bash может помочь вам воспроизвести практически все, что вы можете сделать с помощью sed. Посетите страницу руководства bash, чтобы начать ...

0 голосов
/ 03 марта 2010

... или чтобы завершить путаницу с обратной косой чертой ...

backSlash='\\'
lineEscaped="${line//${backSlash}/${backSlash}}"   # double backslashes
lineEscaped="${lineEscaped//&/\&}"                 # & -> \&
0 голосов
/ 27 февраля 2010

Чтобы избежать путаницы с обратной косой чертой, мы могли бы (или, скорее, должны) использовать переменную с обратной косой чертой, например:

backSlash='\\'
ES="${ES//${backSlash}(/(}"    # \( -> (              
ES="${ES//${backSlash})/)}"    # \) -> )

(Кстати, использование переменных таким способом кажется хорошим подходом для решения проблем расширения параметров ...)

0 голосов
/ 26 февраля 2010

Параметр -E в FreeBSD sed используется для включения расширенных регулярных выражений.

То же самое доступно для GNU sed через опции -r или --regexp-extended соответственно.

Различия между основными и расширенными регулярными выражениями см., Например:

http://www.gnu.org/software/sed/manual/sed.html#Extended-regexps

Может быть, вы можете использовать совместимый с FreeBSD минимизированный вместо 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}"
0 голосов
/ 25 февраля 2010

Это похоже на работу с 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
0 голосов
/ 25 февраля 2010

Как уже упоминалось в 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`
0 голосов
/ 25 февраля 2010

this: echo "$line" | awk '{gsub(".", "\\\\&");print}' экранирует каждый символ в $line, что неправильно !. сделайте echo $ES после этого, и $ ES будет \/\u\s\r\/\i\n\c\l\u\d\e. Затем, когда вы переходите к следующему седу, (ниже)

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» (глобальный).

расскажите нам, что вы на самом деле пытаетесь сделать, т.е. фактическую проблему, которую вы пытаетесь решить.

...