Подтвердить что ты не робот

Поддерживает ли bash регулярные выражения границ слов?

Я пытаюсь сопоставить наличие слова в списке, прежде чем снова добавить это слово (чтобы избежать дублирования). Я использую Bash 4.2.24 и пытаюсь ниже:

[[  $foo =~ \bmyword\b ]]

также

[[  $foo =~ \<myword\> ]]

Однако ни один из них не работает. Они упоминаются в примере документации bash: http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_04_01.html.

Я предполагаю, что я делаю что-то не так, но я не уверен, что.

4b9b3361

Ответ 1

Да, все перечисленные расширения регулярных выражений поддерживаются, но вам лучше будет поместить шаблон в переменную перед его использованием. Попробуй это:

re=\\bmyword\\b
[[ $foo =~ $re ]]

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

Примечание редактора: связанный вопрос не объясняет проблему ОП;это просто объясняет, как начинать с регулярных выражений Bash версии 3.2 (или, по крайней мере, специальных символов регулярных выражений) по умолчанию не следует заключать в кавычки, чтобы рассматривать их как таковые - именно это и пытался OP.
Однако обходные пути в этом ответе эффективны.

Вам, вероятно, придется переписать свои тесты, чтобы использовать временную переменную для регулярных выражений, или использовать режим совместимости 3.1:

shopt -s compat31

Ответ 2

ТЛ; др

  • Чтобы быть в безопасности, не используйте литерал регулярных выражений с =~.
    Вместо этого используйте:

  • Поддерживаются ли \b и \</\> вообще, зависит от платформы хоста, а не от Bash:

    • они работают на Linux,
    • но НЕ на платформах на базе BSD, таких как macOS; вместо этого используйте [[:<:]] и [[:>:]], которые в контексте литерала регулярного выражения без кавычек должны быть экранированы как [[:\<:]] и [[:\>:]]; следующее работает, как и ожидалось, но только на BSD/macOS:
      • [[ ' myword ' =~ [[:\<:]]myword[[:\>:]] ]] && echo YES # OK
  • Проблема не возникнет - на любой платформе - если вы ограничите свое регулярное выражение конструкциями в спецификации POSIX ERE (расширенное регулярное выражение).

    • К сожалению, POSIX ERE не поддерживают проверки границ слов, хотя вы можете их эмулировать - см. Последний раздел.

    • Как и в macOS, конструкции \ -prefixed не поддерживаются, поэтому удобные ярлыки символьного класса, такие как \s и \w, также недоступны.

    • Однако положительным моментом является то, что такие ERE-совместимые регулярные выражения тогда переносимы (например, работают как на Linux, так и на macOS)

Если вы хотите узнать больше, читайте дальше.


В Bash v3. 2+ (если не shopt опция compat31 shopt), правый операнд оператора =~ должен быть compat31 shopt, чтобы быть распознанным как регулярное выражение (если вы указали правильный операнд, =~ выполняет обычное сравнение строк вместо).

Точнее, по крайней мере, специальные символы и последовательности регулярных выражений должны быть заключены в кавычки, поэтому хорошо и полезно заключать в кавычки те подстроки, которые следует понимать буквально;например, [[ ' ab' =~ ^' ab' ]] соответствует, потому что ^ не заключен в кавычки и, таким образом, правильно распознается как якорь начала строки.

Однако в bash 3.x, по-видимому, имеется ограничение дизайна, которое не позволяет использовать конструкции регулярных выражений \ -prefixed (например, \<, \>, \b, \s, \w ,...) в буквальном аргументе RHS =~; проблемное поведение по-прежнему применяется в bash 4.4.19 и затрагивает * Linux, тогда как версии BSD/macOS не затрагиваются, поскольку принципиально не поддерживают какие-либо конструкции \ -prefixed regex:

# Linux only:
# PROBLEM (see details further below): 
#   Seen by the regex engine as: <word>
#   The shell eats the '\' before the regex engine sees them.
[[ ' word ' =~ \<word\> ]] && echo MATCHES # !! DOES NOT MATCH
#   Causes syntax error, because the shell considers the < unquoted.
#   If you used \\bword\\b, the regex engine would see that as-is.
[[ ' word ' =~ \\<word\\> ]] && echo MATCHES # !! BREAKS
#   Using the usual quoting rules doesn't work either:
#   Seen by the regex engine as: \\<word\\> instead of \<word\>
[[ ' word ' =~ \\\<word\\\> ]] && echo MATCHES # !! DOES NOT MATCH

# WORKAROUNDS
  # Aux. viarable.  
re='\<word\>'; [[ ' word ' =~ $re ]] && echo MATCHES # OK
  # Command substitution
[[ ' word ' =~ $(printf %s '\<word\>') ]] && echo MATCHES # OK

  # Change option compat31, which then allows use of '...' as the RHS
  # CAVEAT: Stays in effect until you reset it, may have other side effects.
  #         Using (...) around  the command confines the effect to a subshell.
(shopt -s compat31; [[ ' word ' =~ '\<word\>' ]] && echo MATCHES) # OK

Эта проблема:

Кончик шляпы Fólkvangr за его вклад.

Буквальное значение RHS =~ по своему дизайну анализируется иначе, чем токены без кавычек в качестве аргументов, в попытке позволить пользователю сосредоточиться на экранировании символов только для регулярного выражения, и при этом не нужно беспокоиться об обычных правилах экранирования оболочки в токенах без кавычек.

Например,

[[ 'a[b' =~ a\[b ]] && echo MATCHES  # OK

совпадает, потому что \ передается _ в движок регулярных выражений (то есть движок регулярных выражений тоже видит литерал a\[b), тогда как если вы используете тот же токен без кавычек в качестве обычного аргумента, обычные расширения оболочки, применяемые к токенам без кавычек, "есть" \, потому что он интерпретируется как escape-символ оболочки:

$ printf %s a\[b
a[b  # '\' was removed by the shell.

Однако в контексте =~ это исключительное прохождение \ применяется только перед символами, которые сами по себе являются метасимволами регулярных выражений, как это определено спецификацией POSIX ERE (расширенные регулярные выражения) (чтобы избежать их для регулярного выражения, чтобы они рассматриваются как литералы:
\ ^ $ [ {.? * + ( ) |
И наоборот, эти метасимволы регулярных выражений могут в исключительных случаях использоваться без кавычек - и действительно должны быть оставлены без кавычек, чтобы иметь свое особое значение регулярного выражения - даже если большинству из них обычно требуется \ -escaping в кавычках без кавычек для предотвращения интерпретации их оболочкой.
Тем не менее, поднабор метасимволов оболочки все еще нуждается в экранировании, ради оболочки, чтобы не нарушать синтаксис условного выражения [[... ]]:
&; < > space

Для любого другого символа, которому предшествует \, оболочка удаляет \ перед отправкой строки в механизм регулярных выражений (как это происходит при обычном расширении оболочки).

Чтобы добавить путаницу, это также относится к символам, которые оболочка не считает особенными;например, b (в отличие от < или >) не требует \ -escaping от оболочки, но экранирование все еще поддерживается, так что \b передается в механизм регулярных выражений как просто b.

Поэтому в настоящее время невозможно использовать конструкцию регулярного выражения в форме \<char> (например, \<, \>, \b, \s, \w, \d ,...) в литерале без кавычек =~ RHS потому что никакая форма экранирования не может гарантировать, что эти конструкции будут видны движком регулярных выражений как таковым после анализа оболочкой:

Поскольку ни <, >, ни b являются метасимволами регулярных выражений, оболочка удаляет \ из \<, \>, \b (как это происходит при обычном расширении оболочки). Следовательно, передача \<word\>, например, заставляет механизм регулярных выражений видеть <word>, что не является намерением:

  • [[ '<word>' =~ \<word\> ]] && echo YES совпадает, потому что механизм регулярных выражений видит <word>.
  • [[ 'boo' =~ ^\boo ]] && echo YES совпадает, потому что механизм регулярных выражений видит ^boo.

Попытка \\<word\\> нарушает команду, потому что оболочка обрабатывает каждый \\ как экранированный \, что означает, что метасимвол < считается не заключенным в кавычки, что вызывает синтаксическую ошибку.

  • [[ ' word ' =~ \\<word\\> ]] && echo YES вызывает синтаксическую ошибку.
  • Этого не произойдет с \\b, но \\b пропущено (из-за \ предшествующего метачару регулярных выражений, \), что также не работает:
    • [[ '\boo' =~ ^\\boo ]] && echo YES совпадает, потому что механизм регулярных выражений видит \\boo, что соответствует литералу \boo.

Попытка \\\<word\\\> - которая по обычным правилам расширения оболочки приводит к \<word\> (попробуйте printf %s \\\<word\\\>) - также не работает:

  • Что происходит, так это то, что оболочка ест \ in \> (то же самое для \b и других \ -prefixed последовательностей), а затем передает предыдущий \\ через обработчик регулярных выражений как есть (опять же, потому что \ сохраняется до регулярное выражение metachar):

  • [[ ' \<word\> ' =~ \\\<word\\\> ]] && echo YES совпадает, потому что механизм регулярных выражений видит \\<word\\>.

Короче:

  • Bash-разбор литералов =~ RHS был разработан с учетом односимвольных метасимволов регулярных выражений и не поддерживает многосимвольные конструкции, начинающиеся с \, такие как \<.

    • Поскольку POSIX ERE не поддерживают такие конструкции, =~ работает как задумано, если вы ограничиваете себя такими регулярными выражениями.
    • Тем не менее, даже в рамках этого ограничения дизайн несколько неловкий, из-за необходимости смешивать связанные с регулярными выражениями и оболочки \ -escaping (цитата).
  • Все эти проблемы синтаксического анализа исчезают, если строка, которую должен видеть механизм регулярных выражений, предоставляется через переменную или через выходные данные подстановки команд, как показано выше.


Кроссплатформенная поддержка:

=~ это редкий случай (единственный случай?) встроенной функции bash которая зависит от платформы: она использует библиотеки регулярных выражений той платформы, на которой работает, что приводит к различным разновидностям регулярных выражений на разных платформах.

Например, во FreeBSD/macOS \</\> и \b НЕ поддерживаются, но [[:<:]] и [[:>:]] поддерживаются. В Linux все наоборот.

Таким образом, это, как правило, нетривиально и требует особой осторожности при написании переносимого кода, в котором используется оператор =~.

См. Нижний раздел для переносимой эмуляции утверждений о границе слов.


Необязательное чтение: переносимая эмуляция утверждений о границе слов с PREIX-совместимыми ERE (расширенными регулярными выражениями):

  • (^|[^[:alpha:][:digit:]_]) вместо \</[[:<:]]

  • ([^[:alpha:][:digit:]_]|$) вместо \>/[[:>:]]

Примечание: \b нельзя эмулировать с помощью ОДНОГО выражения - используйте вышеупомянутое в соответствующих местах.

Возможное предостережение заключается в том, что вышеприведенные выражения также будут захватывать сопоставляемые несловесные символы, тогда как истинные утверждения, такие как \</[[:<:]] - нет.

$foo = 'myword'
[[ $foo =~ (^|[^[:alpha:][:digit:]_])myword([^[:alpha:][:digit:]_]|$) ]] && echo YES

Вышеуказанные совпадения, как и ожидалось.

Ответ 3

В принятом ответе основное внимание уделяется использованию вспомогательных переменных для рассмотрения синтаксических нечетностей регулярных выражений в выражениях Bash [[ ... ]]. Очень хорошая информация.

Однако реальный ответ:

\b \< и \> не работают с OS X 10.11.5 (El Capitan) с Bash версией 4.3.42 (1) -release (x86_64-apple-darwin15.0.0).

Вместо этого используйте [[:<:]] и [[:>:]].

Ответ 4

Не совсем "\ b", но для меня более удобочитаемый (и переносимый), чем другие предложения:

[[  $foo =~ (^| )myword($| ) ]]

Ответ 5

Тангенциально относится к вашему вопросу, но если вы можете использовать egrep в своем script:

if [ `echo $foo | egrep -c "\b${myword}\b"` -gt 0 ]; then

В итоге я использовал это после разгона с помощью bash =~

Как ясно указывает mklement0, мы можем просто полагаться на статус выхода egrep и писать:

if egrep -q "\b${myword}\b" <<<$foo; then

Ответ 6

Это сработало для меня

bar='\<myword\>'
[[ $foo =~ $bar ]]

Ответ 7

Вы можете использовать grep, который более переносимый, чем bash regexp, как это:

if echo $foo | grep -q '\<myword\>'; then 
    echo "MATCH"; 
else 
    echo "NO MATCH"; 
fi

Ответ 8

Я использовал следующее, чтобы сопоставить границы слов в старых системах. Ключ состоит в том, чтобы обернуть $foo пробелами, так как [^[:alpha:]] не будет соответствовать словам в начале или конце списка.

[[ " $foo " =~ [^[:alpha:]]myword[^[:alpha:]] ]]

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