Возможно ли избежать метасимволов регулярных выражений с помощью sed

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

 #!/bin/bash # Trying to replace one regex by another in an input file with sed search="/abc\n\t[az]\+\([^ ]\)\{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 не приведет никого в неправильное направление.

Кстати, обсуждение пришло сюда . Я думал, что это может быть хорошим местом для сбора решений и, возможно, для их разлома и / или разработки.

Заметка:

  • Если вы ищете готовые функции на основе методов, обсуждаемых в этом ответе:
    • Функции bash , обеспечивающие надежное экранирование даже в многострочных подстановках, можно найти в нижней части этой публикации (плюс решение perl которое использует встроенную поддержку perl для такого выхода).
    • @ Ответ ЭдМортона содержит инструмент (скрипт bash ), который надежно выполняет однострочные подстановки .
  • Все fragmentы предполагают bash как оболочку (возможны переформулировки, совместимые с POSIX):

Однолинейные решения


Вывод строкового литерала для использования в качестве регулярного выражения в sed :

Чтобы получить кредит, когда кредит должен быть: я нашел регулярное выражение, используемое ниже в этом ответе .

Предполагая, что строка поиска является однострочной строкой:

 search='abc\n\t[az]\+\([^ ]\)\{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' 
  • Каждый символ, кроме ^ , помещается в свой собственный набор символов [...] чтобы рассматривать его как литерал.
    • Заметим, что ^ - один символ. вы не можете представлять как [^] , потому что в этом месте (отрицание) оно имеет особое значение.
  • Тогда, ^ chars. экранируются как \^ .

Этот подход является надежным, но не эффективным.

Устойчивость исходит из того, что вы не пытаетесь предвидеть все специальные символы регулярных выражений, которые будут варьироваться в зависимости от диалектов регулярных выражений, но сосредоточиться только на двух функциях, общих для всех диалектов regex :

  • возможность указывать буквенные символы внутри набора символов.
  • умение бежать буквально как \^

Экранирование строкового литерала для использования в качестве замены в команде s/// sed :

Строка замены в команде sed s/// не является регулярным выражением, но она распознает заполнители, которые ссылаются либо на всю строку, сопоставляемую с выражением regex ( & ), либо с конкретными результатами группы захвата по индексу ( \1 , \2 ,. ..), поэтому они должны быть экранированы вместе с (обычным) разделителем регулярных выражений, / .

Предполагая, что строка замены является строкой одной строки:

 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 


Решения MULTI-line


Выключение строкового литерала MULTI-LINE для использования в качестве регулярного выражения в sed :

Примечание . Это имеет смысл только в том случае, если перед попыткой сопоставить несколько строк ввода (возможно, ВСЕ).
Поскольку такие инструменты, как sed и awk работают по одной строке за раз по умолчанию, необходимы дополнительные шаги, чтобы заставить их читать более одной строки за раз.

 # Define sample multi-line literal. search='/abc\n\t[az]\+\([^ ]\)\{2,3\}\3 /def\n\t[AZ]\+\([^ ]\)\{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" 
  • Новые строки в многострочных входных строках должны быть переведены в строки '\n' , а именно, как новые строки закодированы в регулярном выражении.
  • $!a\'$'\n''\\n' присоединяет строку '\n' к каждой выходной строке, но последняя (последняя новая строка игнорируется, поскольку она была добавлена <<< )
  • tr -d '\n затем удаляет все фактические строки новой строки из строки ( sed добавляет каждый раз, когда он печатает свое пространство шаблонов), эффективно заменяя все строки новой строки на входе строками '\n' .
  • -e ':a' -e '$!{N;ba' -e '}' является совместимой с POSIX формой sed идиомы, которая считывает все входные строки цикла, поэтому оставляя последующие команды для работы во всех входных строках в один раз.

    • Если вы используете GNU sed (только), вы можете использовать его опцию -z чтобы упростить чтение всех входных строк одновременно:
      sed -z "s/$searchEscaped/foo/" <<<"$search"

Вывод строкового литерала MULTI-LINE для использования в качестве замены строки в команде s/// :

 # 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" 
  • Новые строки во входной строке должны сохраняться как фактические символы новой строки, но \ -escaped.
  • -e ':a' -e '$!{N;ba' -e '}' является совместимой с POSIX формой sed идиомы, которая считывает все входные строки цикла.
  • 's/[&/\]/\\&/g экранирует все & , \ и / экземпляры, как в однострочном решении.
  • s/\n/\\&/g' то \ -префикс всех фактических строк перевода.
  • IFS= read -d '' -r используется для чтения вывода команды sed как есть (чтобы избежать автоматического удаления конечных строк, которые могла бы выполнять замена команд ( $(...) )).
  • ${REPLY%$'\n'} затем удаляет одну конечную новую строку, которую <<< неявно добавляет к вводу.


bash на основе вышеизложенного (для sed ):

  • quoteRe() кавычки (escapes) для использования в регулярном выражении
  • quoteSubst() для использования в строке подстановки вызова s/// .
  • оба правильно управляют многострочным входом
    • Обратите внимание, что поскольку sed по умолчанию использует по одной строке по умолчанию, использование quoteRe() с многострочными строками имеет смысл только в командах sed которые явно читают сразу несколько (или всех) строк.
    • Кроме того, использование подстановок ( $(...) ) для вызова функций не будет работать для строк, имеющих завершающие символы новой строки; в этом случае используйте что-то вроде IFS= read -d '' -r escapedValue <(quoteSubst "$value")
 # SYNOPSIS # quoteRe  quoteRe() { sed -e 's/[^^]/[&]/g; s/\^/\\^/g; $!a\'$'\n''\\n' <<<"$1" | tr -d '\n'; } 
 # SYNOPSIS # quoteSubst  quoteSubst() { IFS= read -d '' -r < <(sed -e ':a' -e '$!{N;ba' -e '}' -e 's/[&/\]/\\&/g; s/\n/\\&/g' <<<"$1") printf %s "${REPLY%$'\n'}" } 

Пример:

 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" 

Обратите внимание на использование -e ':a' -e '$!{N;ba' -e '}' для чтения всего ввода сразу, так что работает многострочная подстановка.



раствор perl :

Perl имеет встроенную поддержку для экранирования произвольных строк для использования в регулярном выражении: quotemeta() или ее эквивалентная \Q...\E котировка .
Такой подход одинаковый как для одно-, так и для многострочных строк; например:

 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" 
  • Обратите внимание на использование -0777 для чтения всего ввода сразу, так что работает многострочная подстановка.

  • Параметр -s позволяет размещать -= -стильные определения переменных Perl, следующие за скриптом, перед любыми операндами имен файлов.

Основываясь на ответе @ mklement0 в этом streamе, следующий инструмент заменит любую строку с одной строкой (в отличие от regexp) с любой другой однострочной строкой с использованием 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" 

Чтобы проиллюстрировать необходимость этого инструмента, подумайте о том, чтобы попытаться заменить 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 

или используйте вышеуказанный инструмент:

 $ sedstr 'a.*/b{2,}\nc' 'd&e\1f' file d&e\1f axx/bb\nc 

Причина, по которой это полезно, заключается в том, что при использовании слов-разделителей для замены слов может быть легко добавлено слово, например, в синтаксисе GNU sed :

 sed "s/\<$escOld\>/$escNew/g" "$file" 

тогда как инструменты, которые фактически работают с строками (например, index() awk index() ), не могут использовать разделители слов.

  • Как удалить конечные пробелы с sed?
  • Как извлечь текст из строки с помощью sed?
  • Как обращаться к ошибке «bash:! D»: событие не найдено »в подстановке команды Bash
  • Удалить все строки, начинающиеся с # из файла
  • Поиск файлов с похожим именем в папку
  • Почему sed требует 3 обратных косых черт для регулярной обратной косой черты?
  • Заменить слово несколькими строками с помощью sed?
  • Исключить строку для шаблона замены sed
  • Извлечение данных из простого XML-файла
  • Sed только печатать согласованное выражение
  • Удалить строку, если поле дублируется
  • Interesting Posts

    Как добавить флаг компоновщика или компиляции в файл CMake?

    Как я могу получить список всех атрибутов css элемента с помощью jQuery?

    Возможно включение и выключение загрузки

    Объединить данные панели для получения данных балансной панели

    Соглашения о кодировании – перечисление имен

    Одновременно переключайте теги на один экран при настройке нескольких мониторов в 3.5?

    Как ограничить JFileChooser каталогом?

    Изменение значений столбцов в MySQL

    Что эквивалентно корню в Windows 8.1 и как я могу открыть оболочку в этом контексте?

    Любые гарантированные минимальные размеры для типов в C?

    Группировка медиа-запросов вместо нескольких запросов разбросанных медиа, которые соответствуют

    Как обмениваться пользовательскими данными между приложениями iPhone?

    Прекратить работу node.js

    Почему массив не присваивается Iterable?

    Как создать подписанный файл APK с использованием интерфейса командной строки Cordova?

    Давайте будем гением компьютера.