Ponieważ nikt inny nie udzielił bezpośredniej odpowiedzi na zadane pytanie , zrobię to.
Odpowiedź jest taka, że w POSIX grep
niemożliwe jest dosłownie spełnienie tego żądania:
grep "<Regex for 'doesn't contain hede'>" input
Powodem jest to, że POSIX grep
jest wymagany tylko do pracy z podstawowymi wyrażeniami regularnymi , które po prostu nie są wystarczające do wykonania tego zadania (nie są w stanie analizować zwykłych języków z powodu braku naprzemienności i nawiasów).
Jednak GNU grep
implementuje rozszerzenia, które na to pozwalają. W szczególności \|
jest operatorem naprzemiennym w implementacji BREU przez GNU \(
i \)
jest nawiasami. Jeśli silnik wyrażeń regularnych obsługuje naprzemiennie, wyrażenia z nawiasami ujemnymi, nawiasy i gwiazdę Kleene i jest w stanie zakotwiczyć na początku i na końcu łańcucha, to wszystko, czego potrzebujesz do tego podejścia. Zauważ jednak, że zestawy ujemne [^ ... ]
są bardzo wygodne oprócz tych, ponieważ w przeciwnym razie musisz je zastąpić wyrażeniem formy, (a|b|c| ... )
która zawiera listę wszystkich znaków, których nie ma w zestawie, co jest niezwykle żmudne i zbyt długie, tym bardziej, jeśli cały zestaw znaków to Unicode.
W przypadku GNU grep
odpowiedzią byłoby:
grep "^\([^h]\|h\(h\|eh\|edh\)*\([^eh]\|e[^dh]\|ed[^eh]\)\)*\(\|h\(h\|eh\|edh\)*\(\|e\|ed\)\)$" input
(znaleziono z Graalem i kilkoma dalszymi optymalizacjami wykonanymi ręcznie).
Możesz także użyć narzędzia, które implementuje Rozszerzone wyrażenia regularne , na przykład egrep
, aby pozbyć się odwrotnych ukośników:
egrep "^([^h]|h(h|eh|edh)*([^eh]|e[^dh]|ed[^eh]))*(|h(h|eh|edh)*(|e|ed))$" input
Oto skrypt do jego przetestowania (pamiętaj, że generuje plik testinput.txt
w bieżącym katalogu):
#!/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)
W moim systemie drukuje:
Files /dev/fd/63 and /dev/fd/62 are identical
zgodnie z oczekiwaniami.
Dla zainteresowanych szczegółami zastosowana technika polega na konwersji wyrażenia regularnego pasującego do słowa na automat skończony, a następnie odwróceniu automatu poprzez zmianę każdego stanu akceptacji na brak akceptacji i odwrotnie, a następnie konwersję wynikowego FA z powrotem na wyrażenie regularne.
W końcu, jak wszyscy zauważyli, jeśli silnik wyrażeń regularnych obsługuje negatywne spojrzenie, to znacznie upraszcza to zadanie. Na przykład z GNU grep:
grep -P '^((?!hede).)*$' input
Aktualizacja: Niedawno znalazłem doskonałą bibliotekę FormalTheory Kendalla Hopkinsa , napisaną w PHP, która zapewnia funkcjonalność podobną do Graala. Używając go i napisanego przeze mnie prostownika, byłem w stanie napisać internetowy generator negatywnych wyrażeń regularnych, podając frazę wejściową (obecnie obsługiwane są tylko znaki alfanumeryczne i spacje): http://www.formauri.es/personal/ pgimeno / misc / non-match-regex /
Do hede
wyjścia:
^([^h]|h(h|e(h|dh))*([^eh]|e([^dh]|d[^eh])))*(h(h|e(h|dh))*(ed?)?)?$
co jest równoważne z powyższym.
([^h]*(h([^e]|$)|he([^d]|$)|hed([^e]|$)))*
:? Pomysł jest prosty. Kontynuuj dopasowywanie, aż zobaczysz początek niechcianego ciągu, a następnie dopasuj tylko w przypadkach N-1, w których łańcuch jest niedokończony (gdzie N jest długością łańcucha). Te przypadki N-1 to „h, po którym następuje nie-e”, „on następuje po nie-d”, i „hed, po którym następuje nie-e”. Jeśli udało Ci się zaliczyć te przypadki N-1, nie udało Ci się dopasować niechcianego ciągu, więc możesz zacząć szukać[^h]*
ponownie