Como usar um regex para corresponder a um padrão que não tenha uma string específica no final

1

Como posso usar um regex "POSIX BRE" ou "POSIX ERE" para corresponder a uma string (linha ou palavra) que não tem bak no final?

Eu quero fazer um ls | egrep '<regex>' para encontrar todos os arquivos que NÃO possuem bak no final do nome do arquivo.

Por exemplo, se houver três arquivos file1 , file2_bak e bak_file3 , a regex deve corresponder apenas a file1 e bak_file3 (mas não file2_bak ).

Sei que isso pode ser feito com ls | grep -v 'bak$' , mas quero fazer isso sem usar a opção -v para grep ou egrep . Eu não quero usar -v porque esta é apenas uma questão teórica / acadêmica sobre expressões regulares POSIX.

É assim que eu combino os nomes dos arquivos que têm bak no final:

$ ls | egrep 'bak$'
file2_bak
$ 

A regex acima, bak$ , corresponde a todas as cadeias que NÃO têm bak no final. Mas como posso escrever um regex que corresponda a todas as strings que fazem não tem bak no final?

    
por sps 13.06.2015 / 05:18

4 respostas

4

Linguagens regulares (isto é, "isso pode ser combinado com um RE") são fechadas sob o complemento, então é possível, mas não é muito útil para propósitos práticos: o que você começa é a condição

last letter is k AND letter before that is a AND letter before that is b

(deixe-me escrever s[-1]=='k' and s[-2]=='a' and s[-3]=='b' de uma forma pituonal) então uma string que falha e que tem

not(s[-1]=='k' and s[-2]=='a' and s[-3]=='b')

ou seja,

not(s[-1]=='k') or not (s[-2]=='a' and s[-3]=='b'))

ou seja,

not(s[-1]=='k') or not(s[-2]=='a') or not(s[-3]=='b')

aplicando a regra de deMorgan duas vezes e, claro, esse seria o caso, em particular, se sua string tivesse tamanho 2 ou menos, então você acabaria com

grep '^$\|^.$\|^..$\|..[^k]$\|.[^a].$\|[^b]..$'

que eu considero tipificável, mas insustentável.

(Nota ao lado: em geral, você converteria sua expressão regular em um autômato finito determinístico (DFA), inverteria os estados do terminal e converteria o novo DFA novamente em uma expressão regular, que é bem definida , mas um processo tedioso e propenso a erros.)

    
por 13.06.2015 / 06:31
5

Se você usar ksh (ou bash com globalização estendida ativada, ou zsh com ksh globs ativado) você pode alcançar a função desejada usando apenas padrões de globalização de arquivos:

ls -d -- !(*bak)

Com grep , para obter uma solução simples, use a negação -v :

ls | grep -v 'bak$'
    
por 13.06.2015 / 06:11
3

Usando find :

find . -maxdepth 1 -type f ! -name "*bak"
  • . : afirma para pesquisar no diretório de trabalho atual
  • -maxdepth 1 : afirma para pesquisar apenas um nível abaixo do diretório especificado (ou seja, apenas o diretório de trabalho atual)
  • -type f : ativa para pesquisar apenas por arquivos
  • ! -name "*bak" : ativa para pesquisar apenas por nomes de arquivos que não terminem em bak

No entanto, se você quiser a saída de grep ls :

ls | grep -v 'bak$'
  • -v : imprime apenas as linhas que não correspondem à regex dada

Divisão do Regex :

  • bak : corresponde a uma bak string
  • $ : corresponde ao final da linha

O mesmo usando look-behind negativo (para PCRE s-compatible grep versions):

ls | grep -P '(?<!bak)$'
  • -P : corresponde a linhas usando PCRE s

Divisão do Regex :

  • (?<!bak) : corresponde apenas ao seguinte padrão se não for precedido por uma bak string
  • $ : corresponde ao final da linha
por 13.06.2015 / 05:25
1

Com POSIX BREs que não têm operador de alternância, você pode usar esse \{0,1\} :

LC_ALL=C grep '^\.\{0,2\}\(.*[^k]\)\{0,1\}\(.*[^a].\)\{0,1\}\(.*[^b]..\)\{0,1\}$'
    
por 14.06.2015 / 16:54