bash тестирует встроенное словесное регулярное выражение с помощью =~ operator

#linux #string #bash

#linux #строка #bash

Вопрос:

Как я могу это сделать в bash? И можно ли изменить [[ $ 1 =~ apple amp;amp; $ 1 =~ banana ]] на что-то вроде [[ $ 1 = ~ apple amp;amp;banana ]] ?

 testme() { 
  if [[ $1 =~ apple amp;amp; $1 =~ banana ]]; then
    echo 'contains both words'
  fi
}

testme 'apple banana' #should echo 'contains both words'
testme ' apple banana' #should echo 'contains both words'
testme 'apple banana '#should echo 'contains both words'
testme ' applebanana' #should echo nothing
testme 'apple cherry
banana 
pear' #separated by newline should echo 'contains both words'
testme 'apples bananas' #should echo nothing
 

Комментарии:

1. а как насчет banana apple ?

2. Замедляйтесь, по одному вопросу за раз — ваша проблема в том, что регулярное выражение не выполняет то, что вы хотите? Все строки содержат подстроки «apple» и «banana», поэтому теоретически все они должны возвращать «содержит оба слова»

3. Технически, с оригинальным (состоящим из 2 частей) тестом «bananapple» также будет соответствовать.

4. Что касается размещения амперсандов, нет, к сожалению, амперсанды разделяют полные предложения — тест bash ‘banana’ даст True, предлагаемое вами изменение будет эквивалентно чему-то вроде ‘if [[$ 1 = ~ apple ]] amp;amp; [[ banana ]]`

5. Похоже, вам нужны границы слов, которые не поддерживаются расширенными регулярными выражениями POSIX, используемыми bash.

Ответ №1:

Как комментирует @Shawn, регулярные выражения bash не поддерживают границы слов. grep выполняет:

 testme() { grep -qFzw -e apple -e banana <<<"$1" amp;amp; echo "contains both words"; }
 

Это проходит все ваши тесты, а также banana, предшествующий apple.


Если вы хотите придерживаться bash, вот не совсем ужасный способ

 word_re() {
  local nonword='[^[:alnum:]_]'
  printf '(^|%s)%s($|%s)' "$nonword" "$1" "$nonword"
}

testme() {
  if [[ $1 =~ $(word_re apple) amp;amp; $1 =~ $(word_re banana) ]]; then
    echo "contains both words"
  fi
}
 

или, если у вас есть список слов:

 testme() {
  for word in apple banana orange kumquat; do
    [[ $1 =~ $(word_re "$word") ]] || return
  done
  echo "contains ALL words"
}
 

Комментарии:

1. Я собирался нажать post для того, который использует perl, но мне больше нравится подход grep.

2. Да, добавьте достаточно опций в grep, и что-то застрянет.

3. Хорошо, большое вам спасибо! Я хотел обойти дополнительный вызов grep и перенаправление stdin, но, к сожалению, bash =~ не поддерживает это.

4. Просто хочу отметить, что для работы вам нужно дважды вызвать grep

5. grep -qFzw -e ‘apple’ <<< «$1» amp;amp; grep -qFzw -e ‘банан’ <<< «$1» amp;amp; echo «содержит оба слова»

Ответ №2:

Хотя bash собственные регулярные выражения не поддерживают границы слов, как вы хотите (без каких-либо более уродливых, чем обычно, обручей), если вы можете использовать zsh вместо этого, вы можете сделать это исключительно в этой оболочке без внешних программ, таких как grep :

 #!/usr/bin/env zsh

zmodload -F zsh/pcre 
setopt REMATCH_PCRE

testme() { [[ $1 =~ \bapple\b amp;amp; $1 =~ \bbanana\b ]] amp;amp; echo "contains both words"; }

testme 'apple banana' #should echo 'contains both words'
testme ' apple banana' #should echo 'contains both words'
testme 'apple banana '#should echo 'contains both words'
testme ' applebanana' #should echo nothing
testme 'apple cherry
banana 
pear' #separated by newline should echo 'contains both words'
testme 'apples bananas' #should echo nothing
 

Это позволяет zsh использовать библиотеку PCRE для сопоставления регулярных выражений на диалекте perl вместо расширенного разрешения Posix по умолчанию. (Ваша версия zsh , должно быть, была скомпилирована с поддержкой PCRE; я не знаю, есть ли все распространенные zsh пакеты ОС; Ubuntu есть).

b perl повторный синтаксис для ‘совпадения по границе слова’ (обратите внимание на необходимость удвоить обратную косую черту, чтобы хорошо играть с оболочкой).


И для полноты, версии testme функции, которые используют другие языки для сопоставления (хотя я думаю grep , что версия Гленна превосходит обе в этом случае).

perl :

 testme() { 
    perl -0777 -nE 'say "contains both words" if /bappleb/ and /bbananab/' <<<"$1"
}
 

и чтобы проиллюстрировать еще один вариант регулярного выражения tcl (который используется m для привязок начала слова и M конца слова):

 testme() { 
    STR="$1" tclsh <<<'
    if {[regexp {mappleM} $env(STR)] amp;amp; [regexp {mbananaM} $env(STR)]} {
      puts "contains both words"
    }'
}