Шукайте та замінюйте в bash, використовуючи регулярні вирази


161

Я бачив цей приклад:

hello=ho02123ware38384you443d34o3434ingtod38384day
echo ${hello//[0-9]/}

З чого випливає цей синтаксис: ${variable//pattern/replacement}

На жаль, patternполе, схоже, не підтримує синтаксис повного регексу (якщо я використовую .або \s, наприклад, намагається відповідати буквальним символам).

Як я можу шукати / замінювати рядок, використовуючи повний синтаксис регулярних виразів?


Знайдено пов'язаний з цим питання тут: stackoverflow.com/questions/5658085 / ...
jheddings

2
FYI, \sне входить до стандартного синтаксису регулярного вираження, визначеного POSIX (ні BRE, ні ERE); це розширення PCRE, і він здебільшого не доступний із оболонки [[:space:]]є більш універсальним еквівалентом.
Чарльз Даффі

1
\sможе бути замінений [[:space:]], до речі, з .допомогою ?і extglob розширення мови шаблонів базової оболонки може використовуватися для таких речей , як додаткові підгрупи, повторюваних груп, тощо.
Чарльз Даффі


Я використовую це у bash версії 4.1.11 на Solaris ... echo $ {hello // [0-9]} Зауважте відсутність остаточної косої риски.
Даніель Лістон

Відповіді:


175

Використовуйте sed :

MYVAR=ho02123ware38384you443d34o3434ingtod38384day
echo "$MYVAR" | sed -e 's/[a-zA-Z]/X/g' -e 's/[0-9]/N/g'
# prints XXNNNNNXXXXNNNNNXXXNNNXNNXNNNNXXXXXXNNNNNXXX

Зауважте, що наступні -eобробляються в порядку. Також gпрапор для виразу буде відповідати всім явищам вхідних даних.

Ви також можете вибрати свій улюблений інструмент, використовуючи цей метод, наприклад, perl, awk, наприклад:

echo "$MYVAR" | perl -pe 's/[a-zA-Z]/X/g and s/[0-9]/N/g'

Це може дозволяти вам робити більш креативні поєдинки ... Наприклад, у фрагменті, наведеному вище, заміна чисел не буде використана, якщо б не було збігу за першим виразом (через ледачу andоцінку). І звичайно, ви маєте повну мовну підтримку Perl, щоб зробити свої ставки ...


Наскільки я можу сказати, це лише одна заміна. Чи є спосіб, щоб він замінив усі види шаблону, як, наприклад, код, який я розмістив?
Ланару

Я оновив свою відповідь, щоб продемонструвати декілька замін, а також глобальну відповідність шаблону. Дайте мені знати, якщо це допомагає.
jheddings

Дуже дякую! З цікавості, чому ви перейшли з однорядкової версії (у початковій відповіді) на дворівневу?
Ланару

9
Використання sedабо інших зовнішніх інструментів є дорогим через час ініціалізації процесу. Я особливо шукав рішення all-bash, тому що виявив, що використання замін bash є втричі швидшим, ніж виклик sedкожного елемента в моєму циклі.
rr-

6
@CiroSantilli 六四 事件 法轮功 纳米比亚 威 视, правда, це загальна мудрість, але це не робить його мудрим. Так, bash повільний, незважаючи ні на що, але добре написаний баш, який дозволяє уникнути підзарядки, це буквально на порядок швидше, ніж bash, який викликає зовнішні інструменти для кожної крихітної задачі. Крім того, добре написані сценарії оболонки отримають користь від швидших інтерпретаторів (наприклад, ksh93, який має продуктивність нарівні з awk), тоді як з погано написаними сценаріями нічого робити.
Чарльз Даффі

133

Це насправді можна зробити в чистому стилі:

hello=ho02123ware38384you443d34o3434ingtod38384day
re='(.*)[0-9]+(.*)'
while [[ $hello =~ $re ]]; do
  hello=${BASH_REMATCH[1]}${BASH_REMATCH[2]}
done
echo "$hello"

... урожайність ...

howareyoudoingtodday

2
Що - то підказує мені , що ви будете любити цей: stackoverflow.com/questions/5624969 / ... =)
nickl-

=~є ключовим. Але трохи незграбно, враховуючи перепризначення в циклі. Рішення @jheddings за два роки до цього - ще один хороший варіант - зателефонувати на sed або perl).
Brent Faust

3
Виклик sedабо perlрозумний, якщо використовувати кожне виклик для обробки більше одного рядка введення. Викликати такий інструмент із внутрішньої сторони циклу, на відміну від використання циклу для обробки його вихідного потоку, є нерозумним.
Чарльз Даффі

2
FYI, в zsh, це просто $matchзамість $BASH_REMATCH. (Ви можете змусити його поводитись, як б'ють setopt bash_rematch.)
Маріан

Як це не дивно - оскільки zsh не намагається бути оболонкою POSIX, це, мабуть, слід за літером вказівки POSIX про змінні all-caps, що використовуються для визначених POSIX (оболонок чи системних) цілей, а променливі регістри зарезервовані для використання додатків. Але оскільки zsh - це щось, що запускає програми, а не сам додаток, таке рішення використовувати простір імен змінної програми, а не простору імен системи, здається жахливим.
Чарльз Даффі

95

Ці приклади також працюють у bash, не потрібно використовувати sed:

#!/bin/bash
MYVAR=ho02123ware38384you443d34o3434ingtod38384day
MYVAR=${MYVAR//[a-zA-Z]/X} 
echo ${MYVAR//[0-9]/N}

ви також можете використовувати вирази дужок класу символів

#!/bin/bash
MYVAR=ho02123ware38384you443d34o3434ingtod38384day
MYVAR=${MYVAR//[[:alpha:]]/X} 
echo ${MYVAR//[[:digit:]]/N}

вихід

XXNNNNNXXXXNNNNNXXXNNNXNNXNNNNXXXXXXNNNNNXXX

Що хотів дізнатися @Lanaru, якщо я правильно розумію питання, це те, чому "повні" розширення PCRE \s\S\w\W\d\Dтощо не працюють як підтримувані в php ruby ​​python і т.д. може не бути сумісним з іншими формами регулярних виразів оболонок.

Вони не працюють:

#!/bin/bash
hello=ho02123ware38384you443d34o3434ingtod38384day
echo ${hello//\d/}


#!/bin/bash
hello=ho02123ware38384you443d34o3434ingtod38384day
echo $hello | sed 's/\d//g'

вихід із усіма буквальними символами "d"

ho02123ware38384you44334o3434ingto38384ay

але наступне працює як слід

#!/bin/bash
hello=ho02123ware38384you443d34o3434ingtod38384day
echo $hello | perl -pe 's/\d//g'

вихід

howareyoudoingtodday

Сподіваємось, що щось прояснить трохи більше, але якщо вас все ще не бентежить, чому б не спробувати це на Mac OS X, на якому увімкнено прапор REG_ENHANCED:

#!/bin/bash
MYVAR=ho02123ware38384you443d34o3434ingtod38384day;
echo $MYVAR | grep -o -E '\d'

У більшості ароматів * nix ви побачите лише такий вихід:

d
d
d

nJoy!


6
Пробачте? ${foo//$bar/$baz}- це не синтаксис POSIX.2 BRE або ERE - це відповідність шаблону стилю fnmatch ().
Чарльз Даффі

8
... тож, тоді як ${hello//[[:digit:]]/}роботи, якби ми хотіли відфільтрувати лише цифри, які передували букві o, ${hello//o[[:digit:]]*}мали б зовсім іншу поведінку, ніж очікувалося (оскільки в шаблонах fnmatch *відповідає всім символам, а не змінювати безпосередньо попередній елемент, який буде 0 або більше).
Чарльз Даффі

1
Дивіться pubs.opengroup.org/onlinepubs/9699919799/utilities/… (і все, що вона містить посилання) для повної специфікації на fnmatch.
Чарльз Даффі

1
man bash: Доступний додатковий бінарний оператор, = ~, з тим самим пріоритетом, що і == та! =. Коли він використовується, рядок праворуч від оператора вважається розширеним регулярним виразом і відповідно відповідає (як у регулярному виразі (3)).
nickl-

1
@aderchox ви праві, для цифр ви можете використовувати [0-9]або[[:digit:]]
nickl-

13

Якщо ви здійснюєте неодноразові дзвінки і переймаєтесь їх продуктивністю, цей тест виявляє, що метод BASH на ~ 15 разів швидший, ніж призупинення до sed і, ймовірно, будь-який інший зовнішній процес.

hello=123456789X123456789X123456789X123456789X123456789X123456789X123456789X123456789X123456789X123456789X123456789X

P1=$(date +%s)

for i in {1..10000}
do
   echo $hello | sed s/X//g > /dev/null
done

P2=$(date +%s)
echo $[$P2-$P1]

for i in {1..10000}
do
   echo ${hello//X/} > /dev/null
done

P3=$(date +%s)
echo $[$P3-$P2]

1
Якщо вас цікавить спосіб зменшення вил, шукайте слово newConnector у цій відповіді на
Ф. Хаурі

8

Використовуйте [[:digit:]](зверніть увагу на подвійні дужки) як візерунок:

$ hello=ho02123ware38384you443d34o3434ingtod38384day
$ echo ${hello//[[:digit:]]/}
howareyoudoingtodday

Просто хотілося узагальнити відповіді (особливо https://stackoverflow.com/a/22261334/2916086 ) @ nickl- ).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.