Так як ніхто не дав прямої відповіді на питання , яке було задане , я зроблю це.
Відповідь полягає в тому, що з POSIX grep
неможливо буквально задовольнити цей запит:
grep "<Regex for 'doesn't contain hede'>" input
Причина полягає в тому, що POSIX grep
потрібен лише для роботи з базовими регулярними виразами , які просто недостатньо потужні для виконання цього завдання (вони не здатні розбирати звичайні мови через відсутність чергування та дужок).
Однак GNU grep
реалізує розширення, які це дозволяють. Зокрема, \|
це оператор чергування у впровадженні BRE з GNU \(
та \)
є дужкою. Якщо ваш механізм регулярних виразів підтримує чергування, негативні вирази дужок, круглі дужки та зірку Клейна і здатний прив’язуватися до початку та кінця рядка, це все, що вам потрібно для цього підходу. Однак зауважте, що негативні набори [^ ... ]
є дуже зручними на додаток до цих, тому що в іншому випадку вам потрібно замінити їх виразом форми, (a|b|c| ... )
яка перераховує кожного символу, який не знаходиться в наборі, що є надзвичайно стомлюючим і занадто довгим, тим більше, якщо весь набір символів - Unicode.
Що grep
стосується GNU , відповідь буде приблизно такою:
grep "^\([^h]\|h\(h\|eh\|edh\)*\([^eh]\|e[^dh]\|ed[^eh]\)\)*\(\|h\(h\|eh\|edh\)*\(\|e\|ed\)\)$" input
(знайдено з Грааль та деякі подальші оптимізації, зроблені вручну).
Ви також можете скористатися інструментом, який реалізує розширені регулярні вирази , як egrep
, наприклад , для позбавлення від косої риски:
egrep "^([^h]|h(h|eh|edh)*([^eh]|e[^dh]|ed[^eh]))*(|h(h|eh|edh)*(|e|ed))$" input
Ось сценарій для його тестування (зауважте, він генерує файл testinput.txt
у поточному каталозі):
#!/bin/bash
REGEX="^\([^h]\|h\(h\|eh\|edh\)*\([^eh]\|e[^dh]\|ed[^eh]\)\)*\(\|h\(h\|eh\|edh\)*\(\|e\|ed\)\)$"
# First four lines as in OP's testcase.
cat > testinput.txt <<EOF
hoho
hihi
haha
hede
h
he
ah
head
ahead
ahed
aheda
ahede
hhede
hehede
hedhede
hehehehehehedehehe
hedecidedthat
EOF
diff -s -u <(grep -v hede testinput.txt) <(grep "$REGEX" testinput.txt)
У моїй системі він друкує:
Files /dev/fd/63 and /dev/fd/62 are identical
як і очікувалося.
Для тих, хто цікавиться деталями, застосовується техніка, щоб перетворити регулярний вираз, який відповідає слову, в кінцевий автомат, потім перевернути автомат, змінивши кожен стан прийняття на неприйняття і навпаки, а потім перетворити отриманий FA назад в регулярний вираз.
Нарешті, як усі зауважили, якщо ваш механізм регулярних виразів підтримує негативний пошук, це значно спрощує завдання. Наприклад, з GNU grep:
grep -P '^((?!hede).)*$' input
Оновлення: Нещодавно я знайшов чудову бібліотеку FormalTheory Kendall Hopkins , написану на PHP, яка забезпечує функціональність, подібну Grail. Використовуючи його та спрощений власним спрощувачем, я зміг написати онлайн-генератор негативних регулярних виразів із заданою вхідною фразою (підтримуються лише буквено-цифрові та пробільні символи): http://www.formauri.es/personal/ pgimeno / різне / невідповідне-регулярне вираження /
Для hede
цього виводиться:
^([^h]|h(h|e(h|dh))*([^eh]|e([^dh]|d[^eh])))*(h(h|e(h|dh))*(ed?)?)?$
що рівнозначно вище.
([^h]*(h([^e]|$)|he([^d]|$)|hed([^e]|$)))*
? Ідея проста. Продовжуйте відповідати, доки не побачите початок небажаного рядка, тоді збігайтеся лише у випадках N-1, коли рядок не закінчений (де N - довжина рядка). Ці випадки N-1 - це "h, за яким йде не-e", "за ним слідує non-d" і "hed, за яким слідує non-e". Якщо вам вдалося передати ці випадки N-1, ви успішно не відповідали небажаній рядку, тому можете почати шукати[^h]*
знову