Несколько совпадений в строке с использованием регулярного выражения в bash

Искал более продвинутую информацию о регулярных выражениях для регулярных выражений с bash и не нашел много информации об этом.

Вот концепция с простой строкой:

myString="DO-BATCH BATCH-DO"

if [[ $myString =~ ([[:alpha:]]*)-([[:alpha:]]*) ]]; then
 echo ${BASH_REMATCH[1]} #first perens
 echo ${BASH_REMATCH[2]} #second perens
 echo ${BASH_REMATCH[0]} #full match
fi

outputs:
BATCH
DO
DO-BATCH

Так хорошо, что он выполняет первое совпадение (BATCH-DO), но как мне получить второе совпадение (DO-BATCH)? Я просто рисую здесь пробел и не могу найти много информации о регулярном выражении bash.


person pn1 dude    schedule 19.07.2012    source источник
comment
Непонятно, о чем вы спрашиваете, поскольку DO-BATCH не встречается в вашей строке. Однако вы имеете в виду, что хотели бы также иметь ${BASH_REMATCH[3]} равным BATCH и т. д.?   -  person chepner    schedule 19.07.2012
comment
К сожалению, вы правы ... Отредактировано OP. И да, это правильно -› ${BASH_REMATCH[3]} == BATCH и ${BASH_REMATCH[4]} == DO   -  person pn1 dude    schedule 19.07.2012


Ответы (5)


Итак, один из способов, которым я это сделал, - поместить его в цикл for:

myString="DO-BATCH BATCH-DO"
for aString in ${myString[@]}; do
    if [[ ${aString} =~ ([[:alpha:]]*)-([[:alpha:]]*) ]]; then
     echo ${BASH_REMATCH[1]} #first perens
     echo ${BASH_REMATCH[2]} #second perens
     echo ${BASH_REMATCH[0]} #full match
    fi
done

which outputs:
DO
BATCH
DO-BATCH
BATCH
DO
BATCH-DO

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

person pn1 dude    schedule 19.07.2012
comment
perl поддерживает понятие повторного сопоставления через флаг g своего оператора сопоставления m//, но, насколько мне известно, bash не имеет эквивалента. - person chepner; 19.07.2012

В вашем ответе myString не является массивом, но вы используете ссылку на массив для доступа к нему. Это работает в Bash, потому что на 0-й элемент массива можно ссылаться только по имени переменной и наоборот. Это означает, что вы можете использовать:

for aString in $myString; do

чтобы получить тот же результат в этом случае.

В своем вопросе вы говорите, что вывод включает «BATCH-DO». Я получаю «DO-BATCH», поэтому я предполагаю, что это опечатка.

Единственный способ получить дополнительные строки без использования цикла for — использовать более длинное регулярное выражение. Кстати, я рекомендую помещать регулярные выражения Bash в переменную. Это значительно упрощает использование определенных типов (например, тех, которые содержат пробелы или специальные символы).

pattern='(([[:alpha:]]*)-([[:alpha:]]*)) +(([[:alpha:]]*)-([[:alpha:]]*))'
[[ $myString =~ $pattern ]]
declare -p BASH_REMATCH    #dump the array

Выходы:

declare -ar BASH_REMATCH='([0]="DO-BATCH BATCH-DO" [1]="DO-BATCH" [2]="DO" [3]="BATCH" [4]="BATCH-DO" [5]="BATCH" [6]="DO")'

Дополнительный набор круглых скобок необходим, если вы хотите захватить отдельные подстроки, а также фразы через дефис. Если вам не нужны отдельные слова, вы можете удалить внутренние наборы скобок.

Обратите внимание, что вам не нужно использовать if, если вам нужно только извлечь подстроки. Вам нужно только if, чтобы выполнить условное действие на основе совпадения.

Также обратите внимание, что ${BASH_REMATCH[0]} будет сильно отличаться от более длинного регулярного выражения, поскольку оно содержит полное совпадение.

person Dennis Williamson    schedule 19.07.2012
comment
Да, я отредактировал опечатку и забыл сделать вывод. Спасибо. Да, myString не является массивом. Сначала я сделал его одним, но обнаружил, что он не нужен для цикла for. Я немного повозился и в итоге работал с read -a, чтобы установить массив в переменную. Я не уверен, что дал бы мне declare -p BASH_REMATCH, кроме списка того, что находится в массиве. - person pn1 dude; 20.07.2012
comment
@pn1dude: Да, declare -p BASH_REMATCH — это просто удобный способ выгрузить содержимое массива, например, при тестировании. - person Dennis Williamson; 20.07.2012

Согласно сообщению @Dennis Williamson, я бездельничал и получил следующее:

myString="DO-BATCH BATCH-DO" 
pattern='(([[:alpha:]]*)-([[:alpha:]]*)) +(([[:alpha:]]*)-([[:alpha:]]*))'

[[ $myString =~ $pattern ]] && { read -a myREMatch <<< ${BASH_REMATCH[@]}; }

echo "\${myString} -> ${myString}" 
echo "\${#myREMatch[@]} -> ${#myREMatch[@]}"

for (( i = 0; i < ${#myREMatch[@]}; i++ )); do   
  echo "\${myREMatch[$i]} -> ${myREMatch[$i]}" 
done

Это отлично работает, за исключением того, что myString должно иметь 2 значения. Поэтому я публикую это, потому что это довольно интересно, и мне было весело возиться с этим. Но чтобы сделать это более общим и обратиться к любому количеству парных групп (например, DO-BATCH), я собираюсь использовать модифицированную версию моего исходного ответа:

myString="DO-BATCH BATCH-DO" 
myRE="([[:alpha:]]*)-([[:alpha:]]*)"

read -a myString <<< $myString

for aString in ${myString[@]}; do   
  echo "\${aString} -> ${aString}"  
  if [[ ${aString} =~ ${myRE} ]]; then
    echo "\${BASH_REMATCH[@]} -> ${BASH_REMATCH[@]}"
    echo "\${#BASH_REMATCH[@]} -> ${#BASH_REMATCH[@]}"
    for (( i = 0; i < ${#BASH_REMATCH[@]}; i++ )); do
      echo "\${BASH_REMATCH[$i]} -> ${BASH_REMATCH[$i]}"
    done
  fi
done

Мне бы хотелось, чтобы perlre как множественное совпадение, но это работает нормально.

person pn1 dude    schedule 19.07.2012

Хотя это вопрос летней давности (без принятого ответа), можно ли упростить шаблон регулярного выражения до:

myRE="([[:alpha:]]*-[[:alpha:]]*)"

удалив внутреннюю скобку, чтобы найти меньший (более краткий) набор слов DO-BATCH и BATCH-DO?

Это работает для меня, когда вы отвечаете в 18:10. ${BASH_REMATCH[0]} и ${BASH_REMATCH[1]} приводят к найденным 2 словам.

person David    schedule 25.05.2013

Если вы на самом деле не знаете, сколько матчей будет заранее, вы можете использовать это:

#!/bin/bash

function handle_value {
  local one=$1
  local two=$2

  echo "i found ${one}-${two}"
}

function match_all {
  local current=$1
  local regex=$2
  local handler=$3

  while [[ ${current} =~ ${regex} ]]; do
    "${handler}" "${BASH_REMATCH[@]:1}"

    # trim off the portion already matched
    current="${current#${BASH_REMATCH[0]}}"
  done
}

match_all \
  "DO-BATCH BATCH-DO" \
  '([[:alpha:]]*)-([[:alpha:]]*)[[:space:]]*' \
  'handle_value'
person Lucas    schedule 12.01.2021