Поскольку никто другой не дал прямого ответа на заданный вопрос , я сделаю это.
Ответ в том, что с POSIX grep
невозможно буквально удовлетворить этот запрос:
grep "<Regex for 'doesn't contain hede'>" input
Причина в том, что POSIX grep
требуется только для работы с базовыми регулярными выражениями , которые просто недостаточно мощны для выполнения этой задачи (они не способны анализировать обычные языки из-за отсутствия чередования и скобок).
Тем не менее, GNU grep
реализует расширения, которые позволяют это. В частности, \|
оператор Чередование в реализации проекта GNU в Бре, а \(
и \)
являются круглые скобки. Если ваш механизм регулярных выражений поддерживает чередование, выражения с отрицательными скобками, круглые скобки и звездочку Клини и может привязывать начало и конец строки, это все, что вам нужно для этого подхода. Тем не менее, обратите внимание, что отрицательные множества [^ ... ]
очень удобны в дополнение к тем, потому что в противном случае вам нужно заменить их выражением формы, в (a|b|c| ... )
котором перечислены все символы, которых нет в наборе, что является чрезвычайно утомительным и чрезмерно длинным, особенно если весь набор символов Unicode.
С GNU grep
ответом будет что-то вроде:
grep "^\([^h]\|h\(h\|eh\|edh\)*\([^eh]\|e[^dh]\|ed[^eh]\)\)*\(\|h\(h\|eh\|edh\)*\(\|e\|ed\)\)$" input
(найдено с Grail и некоторыми дополнительными оптимизациями, сделанными вручную).
Вы также можете использовать инструмент, который реализует расширенные регулярные выражения , например 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 Кендалла Хопкинса , написанную на 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, сопровождаемый не-е», «он следует, не-d», и «hed, сопровождаемый не-e». Если вам удалось пропустить эти случаи N-1, вы успешно не сопоставили нежелательную строку, поэтому вы можете начать поиск[^h]*
снова