Expressão regular para corresponder a uma linha que não contém uma palavra

4294

Eu sei que é possível combinar uma palavra e, em seguida, reverter as correspondências usando outras ferramentas (por exemplo grep -v). No entanto, é possível combinar linhas que não contêm uma palavra específica, por exemplo hede, usando uma expressão regular?

Entrada:

hoho
hihi
haha
hede

Código:

grep "<Regex for 'doesn't contain hede'>" input

Saída desejada:

hoho
hihi
haha
knaser
fonte
85
Provavelmente alguns anos atrasado, mas o que há de errado com ([^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
stevendesu
323
@stevendesu: tente isso com 'uma palavra muito, muito longa' ou melhor ainda com meia frase. Divirta-se digitando. BTW, é quase ilegível. Não sei sobre o impacto no desempenho.
Peter Schuetze
13
@ PeterSchuetze: Claro que não é bonito para palavras muito longas, mas é uma solução viável e correta. Embora eu não tenha executado testes no desempenho, não imagino que seja muito lento, pois a maioria das regras anteriores é ignorada até você ver um h (ou a primeira letra da palavra, frase, etc.). E você pode gerar facilmente a cadeia de caracteres regex para cadeias longas usando concatenação iterativa. Se funciona e pode ser gerado rapidamente, a legibilidade é importante? É para isso que servem os comentários.
Stevendesu
57
@ stevendesu: estou ainda mais tarde, mas essa resposta está quase completamente errada. por um lado, exige que o assunto contenha "h", o que não deveria, pois a tarefa é "linhas de correspondência que [não] não contêm uma palavra específica". vamos supor que você pretenda tornar opcional o grupo interno e que o padrão é ancorado: ^([^h]*(h([^e]|$)|he([^d]|$)|hed([^e]|$))?)*$ isso falha quando instâncias de "hede" são precedidas por instâncias parciais de "hede", como em "hhede".
jaytea
8
Esta pergunta foi adicionada às Perguntas frequentes sobre a expressão regular de estouro de pilha , em "Regex-Fu avançado".
Alreralmind

Respostas:

5895

A noção de que o regex não suporta correspondência inversa não é totalmente verdadeira. Você pode imitar esse comportamento usando pesquisas negativas:

^((?!hede).)*$

A regex acima corresponderá a qualquer sequência ou linha sem quebra de linha, que não contenha a (sub) sequência 'hede'. Como mencionado, isso não é algo que o regex seja "bom" em (ou deveria fazer), mas ainda assim, é possível.

E se você também precisar corresponder os caracteres de quebra de linha, use o modificador DOT-ALL ( so seguinte no padrão a seguir):

/^((?!hede).)*$/s

ou use-o em linha:

/(?s)^((?!hede).)*$/

(onde o /.../ são os delimitadores de expressões regulares, ou seja, não fazem parte do padrão)

Se o modificador DOT-ALL não estiver disponível, você poderá imitar o mesmo comportamento com a classe de caracteres [\s\S]:

/^((?!hede)[\s\S])*$/

Explicação

Uma string é apenas uma lista de ncaracteres. Antes e depois de cada personagem, há uma string vazia. Portanto, uma lista de ncaracteres terá n+1cadeias vazias. Considere a string "ABhedeCD":

    ┌──┬───┬──┬───┬──┬───┬──┬───┬──┬───┬──┬───┬──┬───┬──┬───┬──┐
S = e1 A e2 B e3 h e4 e e5 d e6 e e7 C e8 D e9
    └──┴───┴──┴───┴──┴───┴──┴───┴──┴───┴──┴───┴──┴───┴──┴───┴──┘

index    0      1      2      3      4      5      6      7

Onde eestão as cordas vazias. O regex (?!hede).olha para frente para ver se não há substring "hede"a ser visto e, se esse for o caso (para que outra coisa seja vista), o .(ponto) corresponderá a qualquer caractere, exceto uma quebra de linha. Look-arounds também são chamados de asserções de largura zero porque não consomem nenhum caractere. Eles apenas afirmam / validam algo.

Portanto, no meu exemplo, toda string vazia é validada primeiro para ver se não há nenhuma "hede"adiante, antes que um caractere seja consumido pelo .(ponto). A regex (?!hede).vai fazer isso apenas uma vez, por isso é envolto em um grupo, e repetido zero ou mais vezes: ((?!hede).)*. Por fim, o início e o final da entrada são ancorados para garantir que toda a entrada seja consumida:^((?!hede).)*$

Como você pode ver, a entrada "ABhedeCD"irá falhar porque em e3, a regex (?!hede)falhar (não é "hede" lá na frente!).

Bart Kiers
fonte
26
Eu não chegaria ao ponto de dizer que isso é algo em que o regex é ruim. A conveniência desta solução é bastante óbvia e o desempenho atingido em comparação com uma pesquisa programática geralmente não é importante.
Archimaredes 03/03
29
A rigor, o loow-ahead negativo torna a expressão regular não regular.
Peter K
55
@ PeterK, claro, mas isso é SO, não MathOverflow ou CS-Stackexchange. As pessoas que fazem uma pergunta aqui geralmente procuram uma resposta prática. A maioria das bibliotecas ou ferramentas (como grepas mencionadas pelo OP) com suporte a regex têm recursos que as tornam não regulares em um sentido teórico.
Bart Kiers
19
@ Bart Kiers, sem ofensa para você responder, apenas esse abuso de terminologia me irrita um pouco. A parte realmente confusa aqui é que expressões regulares, no sentido estrito, podem fazer muito o que o OP deseja, mas a linguagem comum para escrevê-las não permite, o que leva a soluções (matematicamente feias) como soluções para o futuro. Por favor, veja esta resposta abaixo e meu comentário lá para uma maneira adequada (teoricamente alinhada) de fazê-lo. Escusado será dizer que funciona mais rápido em grandes entradas.
Peter K
17
Caso você já tenha se perguntado como fazer isso no vim:^\(\(hede\)\@!.\)*$
baldrs
739

Observe que a solução para não começa com "hede" :

^(?!hede).*$

geralmente é muito mais eficiente do que a solução para não contém "hede" :

^((?!hede).)*$

O primeiro verifica “hede” apenas na primeira posição da string de entrada, e não em todas as posições.

FireCoding
fonte
5
Obrigado, usei-o para validar que a string não contém squence de dígitos ^ ((?! \ D {5,}).) * #
Samih A
2
Olá! Não consigo compor não termina com regex "hede" . Você pode ajudar com isso?
Aleks Ya
1
@AleksYa: basta usar a versão "contains" e incluir a âncora final na string de pesquisa: altere a string para "not match" de "hede" para "hede $"
Nyerguds
2
@AleksYa: a versão não final pode ser feita usando lookbehind negativo como: (.*)(?<!hede)$ . A versão do @Nyerguds também funcionaria, mas perde completamente o objetivo de desempenho mencionado pela resposta.
thisismydesign
5
Por que tantas respostas estão dizendo ^((?!hede).)*$? Não é mais eficiente usar ^(?!.*hede).*$? Faz a mesma coisa, mas em menos etapas
JackPRead
208

Se você está usando o grep, pode usar grep -v hedepara obter todas as linhas que não contêm hede.

ETA Oh, relendo a pergunta, grep -vprovavelmente é o que você quis dizer com "opções de ferramentas".

Athena
fonte
22
Dica: para filtrar progressivamente o que você não deseja: grep -v "hede" | grep -v "hihi" | ... etc
Olivier Lalonde 5/05
51
Ou usando apenas um processogrep -v -e hede -e hihi -e ...
Olaf Dietsche
15
Ou apenas grep -v "hede\|hihi":)
Putnik
2
Se você tem muitos padrões que você deseja filtrar para fora, colocá-los em um arquivo e usogrep -vf pattern_file file
codeforester
4
Ou simplesmente, egrepou grep -Ev "hede|hihi|etc"para evitar a fuga embaraçosa.
Amit Naidu
160

Responda:

^((?!hede).)*$

Explicação:

^o início da sequência, (agrupe e capture para \ 1 (0 ou mais vezes (correspondendo à maior quantidade possível)),
(?! observe se não há,

hede sua corda,

)final da observação, .qualquer caractere, exceto \ n,
)*final de \ 1 (Observação: como você está usando um quantificador nessa captura, apenas a ÚLTIMA repetição do padrão capturado será armazenada em \ 1)
$antes de um \ n opcional, e o fim da corda

Jessica
fonte
14
impressionante que funcionou para mim no texto sublime 2 usando várias palavras ' ^((?!DSAU_PW8882WEB2|DSAU_PW8884WEB2|DSAU_PW8884WEB).)*$'
Damodar Bashyal
3
@DamodarBashyal Eu sei que estou muito atrasado aqui, mas você pode remover totalmente o segundo termo de lá e obter os mesmos resultados
forresthopkinsa
99

As respostas dadas são perfeitamente boas, apenas um ponto acadêmico:

Expressões regulares no significado de ciências da computação não são capazes de fazê-lo assim. Para eles, era algo parecido com isto:

^([^h].*$)|(h([^e].*$|$))|(he([^h].*$|$))|(heh([^e].*$|$))|(hehe.+$) 

Isso faz apenas uma correspondência COMPLETA. Fazer isso para sub-partidas seria ainda mais complicado.

Hades32
fonte
1
Importante notar que isso usa apenas expressões regulares básicas do POSIX.2 e, portanto, enquanto terse é mais portátil para quando o PCRE não está disponível.
Steve-o
5
Concordo. Muitas, senão a maioria das expressões regulares, não são linguagens regulares e não podem ser reconhecidas por um autômato finito.
ThomasMcLeod
@ThomasMcLeod, Hades32: Está dentro dos domínios de qualquer linguagem regular possível ser capaz de dizer ' não ' e ' e ' e ', bem como o' ou 'de uma expressão como' (hede|Hihi)'? (Isso talvez uma pergunta para CS.)
James Haigh
7
@JohnAllen: ME !!! ... Bem, não o regex real, mas a referência acadêmica, que também se relaciona intimamente com a complexidade computacional; Os PCREs fundamentalmente não podem garantir a mesma eficiência que as expressões regulares POSIX.
James Haigh
4
Desculpe -este resposta simplesmente não funciona, ele irá corresponder hhehe e até mesmo corresponder hehe parcialmente (segunda metade)
Falco
60

Se você deseja que o teste regex falhe apenas se a sequência inteira corresponder, o seguinte funcionará:

^(?!hede$).*

por exemplo - Se você deseja permitir todos os valores, exceto "foo" (ou seja, "foofoo", "barfoo" e "foobar" serão aprovados, mas "foo" falhará), use: ^(?!foo$).*

Obviamente, se você estiver verificando a igualdade exata , uma solução geral melhor nesse caso é verificar a igualdade das strings, ou seja,

myStr !== 'foo'

Você pode até colocar a negação fora do teste se precisar de recursos de regex (aqui, insensibilidade a maiúsculas e minúsculas):

!/^[a-f]oo$/i.test(myStr)

A solução regex na parte superior desta resposta pode ser útil, no entanto, em situações em que é necessário um teste positivo de regex (talvez por uma API).

Roy Tinker
fonte
e quanto a espaços em branco à direita? Por exemplo, se eu quiser que o teste falhe com a string " hede "?
Eagor #
@eagor a \sdirectiva corresponde a um único espaço em branco
Roy Tinker
obrigado, mas não consegui atualizar a regex para fazer isso funcionar.
Eagor # 13/17
2
@eagor:^(?!\s*hede\s*$).*
Roy Tinker
52

FWIW, como as linguagens regulares (também conhecidas como linguagens racionais) são fechadas sob complementação, sempre é possível encontrar uma expressão regular (também conhecida como expressão racional) que nega outra expressão. Mas poucas ferramentas implementam isso.

O Vcsn suporta esse operador (que indica {c}, postfix).

Você primeiro definir o tipo de suas expressões: as etiquetas são carta ( lal_char) para escolher a partir ade z, por exemplo (que define o alfabeto quando se trabalha com complementação é, naturalmente, muito importante), e o "valor" calculado para cada palavra é apenas um valor booleano : truea palavra é aceita,false , rejeitada.

Em Python:

In [5]: import vcsn
        c = vcsn.context('lal_char(a-z), b')
        c
Out[5]: {a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z}  𝔹

então você insere sua expressão:

In [6]: e = c.expression('(hede){c}'); e
Out[6]: (hede)^c

converta esta expressão em um autômato:

In [7]: a = e.automaton(); a

O autômato correspondente

finalmente, converta esse autômato em uma expressão simples.

In [8]: print(a.expression())
        \e+h(\e+e(\e+d))+([^h]+h([^e]+e([^d]+d([^e]+e[^]))))[^]*

onde +normalmente é indicado |, \edenota a palavra vazia e [^]geralmente é escrito .(qualquer caractere). Então, com um pouco de reescrita()|h(ed?)?|([^h]|h([^e]|e([^d]|d([^e]|e.)))).* .

Você pode ver este exemplo aqui e tente o Vcsn online .

akim
fonte
6
Verdadeiro, mas feio e factível apenas para pequenos conjuntos de caracteres. Você não quer fazer isso com seqüências de caracteres Unicode :-)
reinierpost
Existem mais ferramentas que permitem isso, uma das mais impressionantes sendo Ragel . Lá, seria escrito como (qualquer * - ('hehe' qualquer *)) para correspondência alinhada ao início ou (qualquer * - ('hehe' qualquer *)) para desalinhado.
Peter K
1
@reinierpost: por que é feio e qual é o problema do unicode? Não posso concordar com os dois. (Não tenho experiência com o vcsn, mas tenho com o DFA).
Peter K
3
@PedroGimeno Quando você ancorou, fez questão de colocar esse regex em parênteses primeiro? Caso contrário, as precedências entre âncoras e |não funcionarão muito bem. '^(()|h(ed?)?|([^h]|h([^e]|e([^d]|d([^e]|e.)))).*)$'.
akim
1
Acho que vale a pena observar que esse método é para combinar linhas que não são a palavra 'hede', em vez de linhas que não contêm a palavra 'hede', que é o que o OP solicitou. Veja minha resposta para o último.
Pedro Gimeno
51

Aqui está uma boa explicação de por que não é fácil negar uma regex arbitrária. Porém, eu tenho que concordar com as outras respostas: se isso é outra coisa que não uma pergunta hipotética, uma regex não é a escolha certa aqui.

Josh Lee
fonte
10
Algumas ferramentas, e especificamente o mysqldumpslow, oferecem apenas essa maneira de filtrar dados, portanto, nesse caso, encontrar um regex para fazer isso é a melhor solução além de reescrever a ferramenta (vários patches para isso não foram incluídos pelo MySQL AB / Sun / Oracle.
FGM
1
Exatamente análogo à minha situação. O mecanismo de modelo de velocidade usa expressões regulares para decidir quando aplicar uma transformação (escape html) e eu quero que ele sempre funcione EXCETO em uma situação.
Henno Vermeulen 18/10/2013
1
Que alternativa existe? Nunca encontrei nada que pudesse fazer correspondência precisa de cadeias além de regex. Se o OP estiver usando uma linguagem de programação, pode haver outras ferramentas disponíveis, mas se ele não estiver escrevendo código, provavelmente não haverá outra opção.
kingfrito_5005
2
Um dos muitos cenários não hipotéticos em que uma regex é a melhor opção disponível: estou em um IDE (Android Studio) que mostra a saída de log e as únicas ferramentas de filtragem fornecidas são: strings simples e regex. Tentar fazer isso com strings simples seria uma falha completa.
precisa saber é
48

Com aparência negativa, a expressão regular pode corresponder a algo que não contém padrão específico. Isso é respondido e explicado por Bart Kiers. Ótima explicação!

No entanto, com a resposta de Bart Kiers, a parte do lookahead testará de 1 a 4 caracteres à frente, combinando com qualquer caractere único. Podemos evitar isso e deixar que a parte do lookah verifique o texto inteiro, verifique se não há 'hede' e, em seguida, a parte normal (. *) Pode comer o texto inteiro de uma só vez.

Aqui está o regex aprimorado:

/^(?!.*?hede).*$/

Observe que o quantificador lento (*?) Na parte negativa do lookahead é opcional; você pode usar (*) quantificador ganancioso, dependendo dos seus dados: se 'hede' aparecer e na metade inicial do texto, o quantificador lento seja mais rápido; caso contrário, o quantificador ganancioso será mais rápido. No entanto, se 'hede' não estiver presente, ambos serão iguais lentamente.

Aqui está o código de demonstração .

Para obter mais informações sobre lookahead, confira o ótimo artigo: Mastering Lookahead and Lookbehind .

Além disso, consulte o RegexGen.js , um gerador de expressões regulares JavaScript que ajuda a construir expressões regulares complexas. Com o RegexGen.js, você pode construir o regex de uma maneira mais legível:

var _ = regexGen;

var regex = _(
    _.startOfLine(),             
    _.anything().notContains(       // match anything that not contains:
        _.anything().lazy(), 'hede' //   zero or more chars that followed by 'hede',
                                    //   i.e., anything contains 'hede'
    ), 
    _.endOfLine()
);
amobiz
fonte
3
tão simplesmente verificar se determinada string não contém str1 e str2:^(?!.*(str1|str2)).*$
S.Serpooshan
1
Sim, ou você pode usar um quantificador lento:, ^(?!.*?(?:str1|str2)).*$dependendo dos seus dados. Adicionado o ?:desde que não precisamos capturá-lo.
amobiz
Essa é de longe a melhor resposta por um fator de 10xms. Se você adicionou o código jsfiddle e os resultados à resposta, as pessoas podem perceber. Eu me pergunto por que a versão preguiçosa é mais rápida que a versão gananciosa quando não há hede. Eles não deveriam levar a mesma quantidade de tempo?
user5389726598465
Sim, eles levam a mesma quantidade de tempo, pois ambos testam o texto inteiro.
amobiz
41

Benchmarks

Decidi avaliar algumas das opções apresentadas e comparar seu desempenho, além de usar alguns novos recursos. Benchmarking no .NET Regex Engine: http://regexhero.net/tester/

Texto de referência:

As primeiras 7 linhas não devem corresponder, pois contêm a expressão pesquisada, enquanto as 7 linhas inferiores devem corresponder!

Regex Hero is a real-time online Silverlight Regular Expression Tester.
XRegex Hero is a real-time online Silverlight Regular Expression Tester.
Regex HeroRegex HeroRegex HeroRegex HeroRegex Hero is a real-time online Silverlight Regular Expression Tester.
Regex Her Regex Her Regex Her Regex Her Regex Her Regex Her Regex Hero is a real-time online Silverlight Regular Expression Tester.
Regex Her is a real-time online Silverlight Regular Expression Tester.Regex Hero
egex Hero egex Hero egex Hero egex Hero egex Hero egex Hero Regex Hero is a real-time online Silverlight Regular Expression Tester.
RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRegex Hero is a real-time online Silverlight Regular Expression Tester.

Regex Her
egex Hero
egex Hero is a real-time online Silverlight Regular Expression Tester.
Regex Her is a real-time online Silverlight Regular Expression Tester.
Regex Her Regex Her Regex Her Regex Her Regex Her Regex Her is a real-time online Silverlight Regular Expression Tester.
Nobody is a real-time online Silverlight Regular Expression Tester.
Regex Her o egex Hero Regex  Hero Reg ex Hero is a real-time online Silverlight Regular Expression Tester.

Resultados:

Os resultados são iterações por segundo, com a mediana de 3 execuções - Maior número = melhor

01: ^((?!Regex Hero).)*$                    3.914   // Accepted Answer
02: ^(?:(?!Regex Hero).)*$                  5.034   // With Non-Capturing group
03: ^(?>[^R]+|R(?!egex Hero))*$             6.137   // Lookahead only on the right first letter
04: ^(?>(?:.*?Regex Hero)?)^.*$             7.426   // Match the word and check if you're still at linestart
05: ^(?(?=.*?Regex Hero)(?#fail)|.*)$       7.371   // Logic Branch: Find Regex Hero? match nothing, else anything

P1: ^(?(?=.*?Regex Hero)(*FAIL)|(*ACCEPT))  ?????   // Logic Branch in Perl - Quick FAIL
P2: .*?Regex Hero(*COMMIT)(*FAIL)|(*ACCEPT) ?????   // Direct COMMIT & FAIL in Perl

Como o .NET não suporta verbos de ação (* FAIL, etc.), não pude testar as soluções P1 e P2.

Resumo:

Tentei testar a maioria das soluções propostas, algumas otimizações são possíveis para certas palavras. Por exemplo, se as duas primeiras letras da sequência de pesquisa não forem as mesmas, a resposta 03 poderá ser expandida para ^(?>[^R]+|R+(?!egex Hero))*$resultar em um pequeno ganho de desempenho.

Porém, a solução mais rápida e mais legível em termos de desempenho parece ser 05 usando uma declaração condicional ou 04 com o quantificador possível. Eu acho que as soluções Perl devem ser ainda mais rápidas e fáceis de ler.

Falco
fonte
5
Você deveria tempo ^(?!.*hede)também. /// Além disso, provavelmente é melhor classificar as expressões para o corpus correspondente e o não correspondente separadamente, porque geralmente é um caso em que a maioria das linhas corresponde ou a maioria das linhas não.
precisa saber é o seguinte
32

Não é regex, mas achei lógico e útil usar greps seriais com pipe para eliminar o ruído.

por exemplo. procure um arquivo de configuração do apache sem todos os comentários-

grep -v '\#' /opt/lampp/etc/httpd.conf      # this gives all the non-comment lines

e

grep -v '\#' /opt/lampp/etc/httpd.conf |  grep -i dir

A lógica do grep serial é (não é um comentário) e (corresponde ao diretório)

kiwalk
fonte
2
Eu acho que ele está pedindo para a versão regex dogrep -v
Angel.King.47
9
Isso é perigoso. Também perde linhas comogood_stuff #comment_stuff
Xavi Montero
29

com isso, você evita testar um lookahead em cada posição:

/^(?:[^h]+|h++(?!ede))*+$/

equivalente a (para .net):

^(?>(?:[^h]+|h+(?!ede))*)$

Resposta antiga:

/^(?>[^h]+|h+(?!ede))*$/
Casimir et Hippolyte
fonte
7
Bom ponto; Estou surpreso que ninguém tenha mencionado essa abordagem antes. No entanto, esse regex específico é propenso a retroceder catastrófico quando aplicado a texto que não corresponde. Aqui está como eu faria isso:/^[^h]*(?:h+(?!ede)[^h]*)*$/
Alan Moore
... ou você pode simplesmente tornar todos os quantificadores possessivos. ;)
Alan Moore
@ Alan Moore - também estou surpreso. Vi seu comentário (e melhor regex na pilha) aqui somente depois de postar esse mesmo padrão em uma resposta abaixo.
Ridgerunner
@ridgerunner, não precisa ser o melhor. Vi benchmarks em que a resposta principal tem um desempenho melhor. (Fiquei surpreendido sobre que tho.)
Qtax
23

O mencionado acima (?:(?!hede).)*é ótimo porque pode ser ancorado.

^(?:(?!hede).)*$               # A line without hede

foo(?:(?!hede).)*bar           # foo followed by bar, without hede between them

Mas o seguinte seria suficiente neste caso:

^(?!.*hede)                    # A line without hede

Essa simplificação está pronta para a adição de cláusulas "AND":

^(?!.*hede)(?=.*foo)(?=.*bar)   # A line with foo and bar, but without hede
^(?!.*hede)(?=.*foo).*bar       # Same
ikegami
fonte
20

Aqui está como eu faria isso:

^[^h]*(h(?!ede)[^h]*)*$

Preciso e mais eficiente que as outras respostas. Ele implementa a técnica de eficiência "desenrolar o ciclo" de Friedl e requer muito menos retorno.

ridgerunner
fonte
17

Se você deseja combinar um caractere para negar uma palavra semelhante à classe de caracteres negados:

Por exemplo, uma sequência:

<?
$str="aaa        bbb4      aaa     bbb7";
?>

Não use:

<?
preg_match('/aaa[^bbb]+?bbb7/s', $str, $matches);
?>

Usar:

<?
preg_match('/aaa(?:(?!bbb).)+?bbb7/s', $str, $matches);
?>

Aviso "(?!bbb)."não é lookbehind nem lookahead, é lookcurrent, por exemplo:

"(?=abc)abcde", "(?!abc)abcde"
diyismo
fonte
3
Não há "lookcurrent" no perl regexp. Este é realmente um lookahead negativo (prefixo (?!). O prefixo de lookahead positivo seria (?=enquanto os prefixos lookbehind correspondentes seriam (?<!e (?<=respectivamente. Um lookahead significa que você lê os próximos caracteres (daí “adiante”) sem consumi-los. Um lookbehind significa que você verifica os caracteres que já foram consumidos.
Didier L
14

Na minha opinião, uma variante mais legível da resposta principal:

^(?!.*hede)

Basicamente, "corresponda no início da linha se, e somente se, não tiver 'hede' nela" - portanto, o requisito foi traduzido quase diretamente em regex.

Obviamente, é possível ter vários requisitos de falha:

^(?!.*(hede|hodo|hada))

Detalhes: a âncora ^ garante que o mecanismo regex não tente novamente a correspondência em todos os locais da cadeia, o que corresponderia a todas as cadeias.

A âncora ^ no começo deve representar o início da linha. A ferramenta grep corresponde a cada linha, uma de cada vez, em contextos em que você está trabalhando com uma sequência de múltiplas linhas, você pode usar o sinalizador "m":

/^(?!.*hede)/m # JavaScript syntax

ou

(?m)^(?!.*hede) # Inline flag
Dannie P
fonte
Excelente exemplo com negação múltipla.
Peter Parada
Uma diferença de resposta superior é que isso não corresponde a nada, e que coincide com toda a linha se não houver "hede"
Z. Khullah
13

O OP não especificou ou Tag a postagem para indicar o contexto (linguagem de programação, editor, ferramenta) no qual o Regex será usado.

Para mim, às vezes eu preciso fazer isso enquanto edito um arquivo usando Textpad.

Textpad suporta alguns Regex, mas não suporta lookahead ou lookbehind, portanto, são necessárias algumas etapas.

Se eu estou procurando reter todas as linhas que NÃO contêm a string hede, eu faria assim:

1. Pesquise / substitua o arquivo inteiro para adicionar uma "Tag" exclusiva ao início de cada linha que contenha qualquer texto.

    Search string:^(.)  
    Replace string:<@#-unique-#@>\1  
    Replace-all  

2. Exclua todas as linhas que contêm a string hede(a string de substituição está vazia):

    Search string:<@#-unique-#@>.*hede.*\n  
    Replace string:<nothing>  
    Replace-all  

3. Nesse ponto, todas as linhas restantes NÃO contêm a sequência hede. Remova a "Tag" exclusiva de todas as linhas (a sequência de substituição está vazia):

    Search string:<@#-unique-#@>
    Replace string:<nothing>  
    Replace-all  

Agora você tem o texto original com todas as linhas que contêm a string hederemovida.


Se eu estiver olhando para fazer alguma outra coisa, apenas para as linhas que NÃO contêm a sequência hede, eu faria assim:

1. Pesquise / substitua o arquivo inteiro para adicionar uma "Tag" exclusiva ao início de cada linha que contenha qualquer texto.

    Search string:^(.)  
    Replace string:<@#-unique-#@>\1  
    Replace-all  

2. Para todas as linhas que contêm a string hede, remova a "Tag" exclusiva:

    Search string:<@#-unique-#@>(.*hede)
    Replace string:\1  
    Replace-all  

3. Neste ponto, todas as linhas que começam com o "Tag" exclusivo, NÃO contêm a string hede. Agora posso fazer o meu Something Else apenas para essas linhas.

4. Quando termino, removo o "Tag" exclusivo de todas as linhas (a seqüência de substituição está vazia):

    Search string:<@#-unique-#@>
    Replace string:<nothing>  
    Replace-all  
Kevin Fegan
fonte
12

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 grepimplementa 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.txtno 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 hedeisso produz:

^([^h]|h(h|e(h|dh))*([^eh]|e([^dh]|d[^eh])))*(h(h|e(h|dh))*(ed?)?)?$

que é equivalente ao acima.

Pedro Gimeno
fonte
11

Desde a introdução do ruby-2.4.1, podemos usar o novo Operador ausente nas expressões regulares do Ruby

do documento oficial

(?~abc) matches: "", "ab", "aab", "cccc", etc.
It doesn't match: "abc", "aabc", "ccccabc", etc.

Assim, no seu caso, ^(?~hede)$faz o trabalho para você

2.4.1 :016 > ["hoho", "hihi", "haha", "hede"].select{|s| /^(?~hede)$/.match(s)}
 => ["hoho", "hihi", "haha"]
aelor
fonte
9

Através do verbo PCRE (*SKIP)(*F)

^hede$(*SKIP)(*F)|^.*$

Isso pula completamente a linha que contém a string exata hedee corresponde a todas as linhas restantes.

DEMO

Execução das peças:

Vamos considerar o regex acima, dividindo-o em duas partes.

  1. Parte antes do |símbolo. Parte não deve ser correspondida .

    ^hede$(*SKIP)(*F)
  2. Parte após o |símbolo. Peça deve ser combinada .

    ^.*$

PARTE 1

O mecanismo Regex iniciará sua execução a partir da primeira parte.

^hede$(*SKIP)(*F)

Explicação:

  • ^ Afirma que estamos no início.
  • hede Corresponde à string hede
  • $ Afirma que estamos no final da linha.

Portanto, a linha que contém a string hedeseria correspondida. Depois que o mecanismo regex vê o seguinte verbo (*SKIP)(*F)( Nota: você pode escrever (*F)como(*FAIL) ), ele ignora e faz com que a correspondência falhe. |chamado alteração ou operador OR lógico adicionado ao lado do verbo PCRE, que corresponde a todos os limites existentes entre cada caractere em todas as linhas, exceto a linha que contém a sequência exata hede. Veja a demonstração aqui . Ou seja, ele tenta corresponder os caracteres da sequência restante. Agora o regex na segunda parte seria executado.

PARTE 2

^.*$

Explicação:

  • ^ Afirma que estamos no início. isto é, corresponde a todas as linhas iniciadas, exceto a da hedelinha. Veja a demonstração aqui .
  • .*No modo Multilinha, .corresponderia a qualquer caractere, exceto caracteres de nova linha ou retorno de carro. E *repetiria o caractere anterior zero ou mais vezes. Então, .*seria igual a toda a linha. Veja a demonstração aqui .

    Ei, por que você adicionou. * Em vez de. +?

    Porque .*corresponderia a uma linha em branco, mas .+não corresponderá a um espaço em branco. Queremos combinar todas as linhas hede, exceto , pode haver uma possibilidade de linhas em branco também na entrada. então você deve usar em .*vez de .+. .+repetiria o caractere anterior uma ou mais vezes. Veja .*corresponde a uma linha em branco aqui .

  • $ O final da âncora da linha não é necessário aqui.

Avinash Raj
fonte
7

Pode ser mais sustentável para duas regexes no seu código, uma para executar a primeira correspondência e, se corresponder, execute a segunda regex para verificar casos extremos que você deseja bloquear, por exemplo ^.*(hede).*, para ter uma lógica apropriada no código.

OK, admito que essa não é realmente uma resposta para a pergunta postada e também pode usar um pouco mais de processamento do que uma única regex. Mas para os desenvolvedores que vieram aqui procurando uma solução rápida de emergência para um caso externo, essa solução não deve ser negligenciada.

andrew pate
fonte
6

Outra opção é que, para adicionar uma visão positiva e verificar se hehehá algum lugar na linha de entrada, negaríamos isso, com uma expressão semelhante a:

^(?!(?=.*\bhede\b)).*$

com limites de palavras.


A expressão é explicada no painel superior direito de regex101.com , se você deseja explorar / simplificar / modificá-la e, neste link , é possível ver como ela corresponderia a algumas entradas de amostra, se desejar.


Circuito RegEx

O jex.im visualiza expressões regulares:

insira a descrição da imagem aqui

Emma
fonte
5

A linguagem TXR suporta negação de regex.

$ txr -c '@(repeat)
@{nothede /~hede/}
@(do (put-line nothede))
@(end)'  Input

Um exemplo mais complicado: combine todas as linhas que começam ae terminam com z, mas não contêm a substring hede:

$ txr -c '@(repeat)
@{nothede /a.*z&~.*hede.*/}
@(do (put-line nothede))
@(end)' -
az         <- echoed
az
abcz       <- echoed
abcz
abhederz   <- not echoed; contains hede
ahedez     <- not echoed; contains hede
ace        <- not echoed; does not end in z
ahedz      <- echoed
ahedz

A negação de Regex não é particularmente útil por si só, mas quando você também tem interseção, as coisas ficam interessantes, pois você tem um conjunto completo de operações de conjuntos booleanos: você pode expressar "o conjunto que corresponde a isso, exceto as que correspondem a esse".

Kaz
fonte
Observe que também é a solução para o regex baseado no ElasticSearch Lucene.
Wiktor Stribiżew
4

A função abaixo ajudará você a obter a saída desejada

<?PHP
      function removePrepositions($text){

            $propositions=array('/\bfor\b/i','/\bthe\b/i'); 

            if( count($propositions) > 0 ) {
                foreach($propositions as $exceptionPhrase) {
                    $text = preg_replace($exceptionPhrase, '', trim($text));

                }
            $retval = trim($text);

            }
        return $retval;
    }


?>
Daniel Nyamasyo
fonte
2

^ ((?! hede).) * $ é uma solução elegante, exceto porque consome caracteres, você não poderá combiná-lo com outros critérios. Por exemplo, digamos que você queira verificar a não presença de "hede" e a presença de "haha". Esta solução funcionaria porque não consumirá caracteres:

^ (?!. \ bhede \ b) (? =. \ bhaha \ b)

cloudhopperpilot
fonte
1

Como usar os verbos de controle de retrocesso do PCRE para corresponder a uma linha que não contém uma palavra

Aqui está um método que eu nunca vi usado antes:

/.*hede(*COMMIT)^|/

Como funciona

Primeiro, ele tenta encontrar "hede" em algum lugar da linha. Se for bem-sucedido, nesse ponto, (*COMMIT)instrui o mecanismo a, não apenas voltar atrás em caso de falha, mas também não tentar nenhuma correspondência adicional nesse caso. Em seguida, tentamos corresponder a algo que não pode ser correspondido (neste caso ^).

Se uma linha não contiver "hede", a segunda alternativa, um subpadrão vazio, corresponderá com êxito à string do assunto.

Esse método não é mais eficiente do que um visual negativo, mas eu pensei em usá-lo aqui para o caso de alguém o achar bacana e utilizá-lo para outras aplicações mais interessantes.

Jaytea
fonte
0

Uma solução mais simples é usar o operador not !

Sua instrução if precisará corresponder a "contém" e não a "exclui".

var contains = /abc/;
var excludes =/hede/;

if(string.match(contains) && !(string.match(excludes))){  //proceed...

Acredito que os designers da RegEx anteciparam o uso de não operadores.

user1691651-John
fonte
0

Talvez você encontre isso no Google enquanto tenta escrever uma regex capaz de corresponder aos segmentos de uma linha (em oposição a linhas inteiras) que não contêm substring. Demorei um pouco para descobrir, então vou compartilhar:

Dada uma sequência: <span class="good">bar</span><span class="bad">foo</span><span class="ugly">baz</span>

Quero corresponder <span>tags que não contêm a substring "ruim".

/<span(?:(?!bad).)*?>irá combinar <span class=\"good\">e <span class=\"ugly\">.

Observe que existem dois conjuntos (camadas) de parênteses:

  • O mais interno é para o lookahead negativo (não é um grupo de captura)
  • O mais externo foi interpretado por Ruby como grupo de captura, mas não queremos que seja um grupo de captura, então eu adicionei?: No início, e não é mais interpretado como um grupo de captura.

Demonstração em Ruby:

s = '<span class="good">bar</span><span class="bad">foo</span><span class="ugly">baz</span>'
s.scan(/<span(?:(?!bad).)*?>/)
# => ["<span class=\"good\">", "<span class=\"ugly\">"]
BrunoFacca
fonte
0

Com o ConyEdit , você pode usar a linha de comando cc.gl !/hede/para obter linhas que não contêm a correspondência de regex ou usar a linha de comando cc.dl /hede/para excluir linhas que contêm a correspondência de regex. Eles têm o mesmo resultado.

Donald
fonte
0

Eu queria adicionar outro exemplo, se você estiver tentando corresponder uma linha inteira que contenha a cadeia X , mas também não contenha a cadeia Y .

Por exemplo, digamos que queremos verificar se nosso URL / string contém " guloseimas saborosas ", desde que também não contenha " chocolate " em nenhum lugar.

Esse padrão de regex funcionaria (também funciona em JavaScript)

^(?=.*?tasty-treats)((?!chocolate).)*$

(sinalizadores globais de várias linhas no exemplo)

Exemplo interativo: https://regexr.com/53gv4

Fósforos

(Esses URLs contêm "guloseimas saborosas" e também não contêm "chocolate")

  • example.com/tasty-treats/strawberry-ice-cream
  • example.com/desserts/tasty-treats/banana-pudding
  • example.com/tasty-treats-overview

Não corresponde

(Esses URLs contêm "chocolate" em algum lugar - para que não correspondam, mesmo que contenham "guloseimas saborosas")

  • example.com/tasty-treats/chocolate-cake
  • example.com/home-cooking/oven-roasted-chicken
  • example.com/tasty-treats/banana-chocolate-fudge
  • example.com/desserts/chocolate/tasty-treats
  • example.com/chocolate/tasty-treats/desserts
Matthew Rideout
fonte