Сохранить часть совпадающего шаблона с переменной

Я хочу извлечь подстроку, соответствующую шаблону, и сохранить его в файл. Пример строки:

Apr 12 19:24:17 PC_NMG kernel: sd 11:0:0:0: [sdf] Attached SCSI removable disk

Я хочу извлечь часть между скобками, в этом случае [sdf].

Я попытался сделать что-то вроде grep -e '[$subtext]', чтобы сохранить текст в скобках в переменной. Конечно, это не работает, но я ищу способ, подобный этому. Было бы очень элегантно включать переменную в регулярное выражение, подобное этому. Что я могу сделать лучше всего?

Спасибо!

Ответ 1

Вероятно, лучший способ использовать только bash, но:

echo 'Apr 12 19:24:17 PC_NMG kernel: sd 11:0:0:0: [sdf] Attached SCSI removable disk' \
| sed -s 's/.*\[\(.*\)\].*/\1/'

Как указывает Юрген, это соответствует не совпадающим линиям. Если вы не хотите выводить строки, отличные от мигания, используйте '-n', чтобы он не выводил шаблон, а '/p' выводил шаблон, когда он соответствует.

| sed -n 's/.*\[\(.*\)\].*/\1/p'

Ответ 2

BASH_REMATCH - это массив, содержащий группы, сопоставленные оболочке.

$ line='Apr 12 19:24:17 PC_NMG kernel: sd 11:0:0:0: [sdf] Attached SCSI removable disk'
$ [[ $line =~ \[([^]]+)\] ]]; echo "${BASH_REMATCH[1]}"
sdf

Если вы хотите поместить это в цикл, вы можете это сделать; вот пример:

while read -r line; do
  if [[ $line =~ \[([^]]+)\] ]] ; then
    drive="${BASH_REMATCH[1]}"
    do_something_with "$drive"
  fi
done < <(dmesg | egrep '\[([hsv]d[^]]+)\]')

Этот подход не вызывает внешних вызовов в цикле - поэтому для запуска внешних программ, таких как sed или grep, оболочке не требуется fork и exec. Таким образом, он, возможно, значительно чище, чем другие предлагаемые здесь методы.

Кстати, ваш первоначальный подход (с использованием grep) был далеко не таким; с помощью grep -o выводится только соответствующая подстрока:

$ subtext=$(egrep -o "\[[^]]*\]" <<<"$line")

... хотя это включает скобки внутри захвата и, следовательно, не на 100% правильнее.

Ответ 3

Совпадение с регулярным выражением, замена с помощью группировки и печать только при регулярном выражении:

sed -n "s/.*\[\(.*\)\].*/\1/p"

Ответ 4

sed жадный, поэтому ответы sed будут пропускать некоторые данные, если в ваших данных больше пар []. Используйте решение grep + tr или вы можете использовать awk

$ cat file
[sss]Apr 12 19:24:17 PC_NMG kernel: sd 11:0:0:0: [sdf] Attached SCSI removable disk [tag] blah blah

$ awk -F"[" '{for(i=2;i<=NF;i++){if($i~/\]/){sub("].*","",$i)};print $i}}' file
sss
sdf
tag