Uma vez que ninguém deu uma resposta direta à pergunta que foi feita , eu vou fazê-lo.
A resposta é que, com o POSIX grep
, é impossível literalmente atender a essa solicitação:
grep "<Regex for 'doesn't contain hede'>" input
O motivo é que o POSIX grep
é necessário apenas para trabalhar com expressões regulares básicas , que simplesmente não são poderosas o suficiente para realizar essa tarefa (elas não são capazes de analisar idiomas regulares, devido à falta de alternância e parênteses).
No entanto, o GNU grep
implementa extensões que permitem isso. Em particular, \|
é o operador de alternância na implementação de BREs pelo GNU \(
e \)
são os parênteses. Se seu mecanismo de expressão regular suportar alternância, expressões entre colchetes negativos, parênteses e a estrela Kleene, e conseguir ancorar no início e no final da string, é tudo o que você precisa para essa abordagem. Observe, no entanto, que conjuntos negativos [^ ... ]
são muito convenientes além desses, porque, caso contrário, é necessário substituí-los por uma expressão do formulário (a|b|c| ... )
que lista todos os caracteres que não estão no conjunto, o que é extremamente tedioso e excessivamente longo, ainda mais se todo o conjunto de caracteres é Unicode.
Com o GNU grep
, a resposta seria algo como:
grep "^\([^h]\|h\(h\|eh\|edh\)*\([^eh]\|e[^dh]\|ed[^eh]\)\)*\(\|h\(h\|eh\|edh\)*\(\|e\|ed\)\)$" input
(encontrado com o Graal e algumas otimizações adicionais feitas à mão).
Você também pode usar uma ferramenta que implementa expressões regulares estendidas , como egrep
, para se livrar das barras invertidas:
egrep "^([^h]|h(h|eh|edh)*([^eh]|e[^dh]|ed[^eh]))*(|h(h|eh|edh)*(|e|ed))$" input
Aqui está um script para testá-lo (observe que ele gera um arquivo testinput.txt
no diretório atual):
#!/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)
No meu sistema, ele imprime:
Files /dev/fd/63 and /dev/fd/62 are identical
como esperado.
Para os interessados nos detalhes, a técnica empregada é converter a expressão regular que corresponde à palavra em um autômato finito; uma expressão regular.
Finalmente, como todos observaram, se o seu mecanismo de expressão regular oferecer suporte negativo, isso simplifica bastante a tarefa. Por exemplo, com o GNU grep:
grep -P '^((?!hede).)*$' input
Atualização: Encontrei recentemente a excelente biblioteca FormalTheory de Kendall Hopkins , escrita em PHP, que fornece uma funcionalidade semelhante ao Grail. Usando-o e um simplificador escrito por mim mesmo, eu consegui escrever um gerador on-line de expressões regulares negativas, com uma frase de entrada (apenas caracteres alfanuméricos e de espaço atualmente suportados): http://www.formauri.es/personal/ pgimeno / misc / non-match-regex /
Para hede
isso produz:
^([^h]|h(h|e(h|dh))*([^eh]|e([^dh]|d[^eh])))*(h(h|e(h|dh))*(ed?)?)?$
que é equivalente ao acima.
([^h]*(h([^e]|$)|he([^d]|$)|hed([^e]|$)))*
:? A ideia é simples. Mantenha a correspondência até ver o início da sequência indesejada e, em seguida, corresponda apenas nos casos N-1 em que a sequência está inacabada (onde N é o comprimento da sequência). Esses casos N-1 são "h seguido por não-e", "ele seguido por não-d" e "hed seguido por não-e". Se você conseguiu passar estes N-1 dos casos, você com êxito não coincidir com a corda indesejado para que você possa começar a procurar[^h]*
novamente