Na expressão regular, `$` nem sempre significa “fim da string”
(sethmlarson.dev)- Em Python
re,$pode corresponder não apenas ao fim da string, mas também antes da última quebra de linha no final, mesmo com o modo multilinha desativado - O fato de
^parecer significar “início da string” não quer dizer que$funcione de forma totalmente simétrica; seu significado real varia conforme a implementação da expressão regular - Os resultados de
$,\ze\Zpara"cat\n"diferem entre PHP, ECMAScript, Python, Go, Java 8, .NET 7.0 e Rust, e o\zdo Python foi adicionado recentemente no Python 3.14 - Se for aceitável permitir a quebra de linha final, então o
$em modo multilinha faz correspondência com"cat\n"em todas as plataformas da tabela, mas para corresponder apenas ao fim sem incluir a quebra de linha, a escolha da sintaxe muda - Se a quebra de linha final não deve ser correspondida, na maioria das plataformas deve-se usar
\z; já no Python anterior ao 3.14 e no ECMAScript, é preciso considerar alternativas diferentes
Onde $ faz correspondência no re do Python
- No módulo de expressões regulares
redo Python,$pode corresponder ao fim da string ou imediatamente antes da última quebra de linha no final da string, mesmo com o modo multilinha desativado cat$corresponde a"lolcat"e não corresponde a"internet cat video", então pode parecer simples, mas se houver uma quebra de linha no final, como em"cat\n", o comportamento pode ser diferente do esperado- Quando
re.MULTILINEé especificado,$corresponde ao fim da string e ao fim de cada linha, ou seja, imediatamente antes de cada quebra de linha - Mesmo no comportamento padrão,
$corresponde ao fim da string e, se houver uma quebra de linha no final, também imediatamente antes dessa quebra
Fazendo correspondência sem incluir a quebra de linha final
- Para corresponder estritamente apenas ao fim da string, usar só
$pode não ser suficiente;\ze\Zpassam a ser candidatos como âncoras de fim - Com base na documentação de expressões regulares do Python e em outra explicação sobre sintaxe de expressões regulares, o suporte e o significado de
\ze\Zvariam conforme a implementação - A diferença para
"cat\n"é a seguinte- PHP:
"cat$"corresponde com ou sem multilinha,"cat\z"não corresponde, e"cat\Z"corresponde - ECMAScript:
"cat$"em multilinha corresponde,"cat$"sem multilinha não corresponde, e\ze\Znão são suportados - Python:
"cat$"corresponde com ou sem multilinha, e"cat\z"e"cat\Z"não correspondem a"cat\n" - Go e Rust:
"cat$"em multilinha corresponde,"cat$"sem multilinha e"cat\z"não correspondem, e\Znão é suportado - Java 8 e .NET 7.0:
"cat$"corresponde com ou sem multilinha,"cat\z"não corresponde, e"cat\Z"corresponde
- PHP:
- O
\zdo Python foi adicionado recentemente no Python 3.14; em versões anteriores, ele não era suportado - Se for aceitável permitir a quebra de linha final, o
$em modo multilinha corresponde a"cat\n"de forma consistente em todas as plataformas da tabela - Se você não quiser corresponder à quebra de linha final, na maioria das plataformas deve usar
\z; no Python anterior ao 3.14, deve usar\Z; no ECMAScript, deve usar$sem multilinha - Os dados da tabela foram coletados em regex101.com e não foram testados em runtimes reais
1 comentários
Comentários do Hacker News
Há muito tempo penso em
^como “início da linha” e em$como “fim da linha”Ao lidar com expressões regulares, muitas vezes processamos texto linha por linha, então o resultado costuma ser o mesmo, mas a forma como penso nesse operador ainda está mais próxima de “linha” do que de “string”
Provavelmente isso vem muito da influência de ter conhecido regex por meio do
grep, criando o hábito de ver a entrada como linhas, não como uma stringUso expressões regulares há quase 20 anos, mas acho que nunca tinha ouvido dizer que
$é o fim da string; sempre considerei como fim da linha^de “início da string”Na prática, assim como
$é “fim da linha”,^também é “início da linha”; o início da string parece ser mais\A, e o fim da string,\Z$basicamente se comporta como uma asserção positiva de lookahead para o fim da stringEle não casa nem consome o caractere de quebra de linha
Só no modo multilinha ele casa com posições de quebra de linha, mas mesmo assim parece não consumi-las
Na prática, usando
$, não consegui criar uma regex que capturasse o último caractere de uma linha, consumisse a quebra de linha e depois capturasse o primeiro caractere da linha seguinte; o grupo de captura simplesmente termina em$grepque implantou essa percepçãoExpressões regulares POSIX e expressões regulares do Python são diferentes
Em geral, a sintaxe de regex não é universal; é preciso consultar a documentação da implementação em uso
Segundo o capítulo 9 do POSIX, expressões regulares operam sobre strings, mas alguns utilitários limitam o processamento a linhas
Além disso,
$é descrito como uma âncora fixada ao fim da string alvo da correspondência; portanto, no fim das contas, se$significa fim da string ou fim da linha depende do utilitário ou do modoFerramentas comuns como
grep,sed,awke Python operam por padrão linha por linha, então normalmente tratam isso como fim da linhaNão existe uma única sintaxe universal de expressões regulares
Sem saber qual linguagem e quais opções estão sendo usadas, não dá para ler ou escrever regex de forma confiável
https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1...
Este tema é perfeito para apresentar Robert Elder a quem ainda não o conhece
Ele produz bom conteúdo no YouTube e no blog, e na série sobre expressões regulares se aprofunda bastante nas diferenças de comportamento entre regex implementadas por várias ferramentas
O vídeo recente também é bom: https://www.youtube.com/watch?v=ys7yUyyQA-Y
Há bastante conteúdo que leitores do HN provavelmente achariam interessante, e ele também aborda temas como a realidade e as dificuldades de consultoria
https://www.youtube.com/@RobertElderSoftware
https://blog.robertelder.org/
https://blog.robertelder.org/regular-expressions/
https://www.youtube.com/watch?v=cK87ktENPrI
Quando aprendi Perl, expressões regulares foram uma das primeiras coisas que realmente internalizei, e até hoje Perl ocupa um cantinho confortável na minha mente graças ao livro “Camel”
Hoje, o conhecimento mais importante é que varia conforme a implementação, então criei o hábito de abrir a tabela de referência correspondente sempre que vou fazer algo
Por exemplo, regex do Emacs não aceita caracteres de palavra no formato
\we exige uma classe de caracteres parecida com\s_-, o que é irritante, mas acho que o Emacs é imbatível em documentação e facilidade de descobertaAlguns utilitários exigem escape em parênteses e outros não; às vezes esse comportamento é configurável, às vezes não
Depois de passar por todas as fases de confusão, irritação e negação, agora eu simplesmente aceito
O conceito é o mesmo em qualquer lugar, mas os dialetos mudam
Especialmente no shell, em vez de ficar pensando se
sed/grep/awksão GNU ou BSD, é muito mais comum eu enfiarperlno pipelinePerl parece um gato pisando no teclado
Quase consigo ouvir inúmeros gerentes de contratação ruins adicionando “como você casa o fim de uma string em uma regex?” à lista de perguntas-armadilha
É estranho deixar Perl de fora de uma lista sobre expressões regulares
Na documentação
perlre,$é descrito assim: casa com o fim da string, ou antes de uma quebra de linha no fim da string, ou, com/m, antes de qualquer quebra de linhaTalvez isso também indique o quanto Perl saiu do radar hoje em dia
Raku, o antigo Perl 6, definiu
^e$como início/fim da string e introduziu^^e$$como início/fim de linhaNão há modo multilinha, nem ele é necessário
\htambém representa espaço em branco horizontal, e\v, espaço em branco verticalPor ter sido totalmente repensado e reescrito, teve a vantagem de poder aprender com o fato de que o comportamento antigo surpreendia as pessoas
Parece uma mistura aleatória daquela sintaxe parecida com ruído de linha aprendida ao longo de décadas
Parece claro que o padrão deveria ter sido o contrário
Teria sido mais natural usar
^e$para linhas, e^^e$$para stringsPorque parece algo como
^^line1$\n^line2$\n^line3$\n$Além disso, Perl 6 não está em todo lugar, mas Perl 5 está em todo lugar
^^parece mais “com cara de início” do que^Como normalmente se processa passando uma linha para a regex, a escolha de usar
^e$simples para a string inteira preserva certa compatibilidade retroativaFico me perguntando se alguém considera que regex foi padronizada
Sempre tive que reaprender a cada vez que mudava para um novo ambiente
Deve haver mais dialetos de regex, mas não esbarro neles, e o que conheço resolve a maior parte dos casos
É parecido com dirigir um carro alugado
Ele se comporta um pouco diferente do meu carro, tem recursos ausentes e recursos extras, mas, no geral, a maioria é bem parecida
Então, pelo menos eu considero que regex foi padronizada por vários padrões oficiais publicados
https://open-std.org/jtc1/sc22/…
https://pubs.opengroup.org/onlinepubs/9699919799/…
https://262.ecma-international.org/14.0/…
Muitos sistemas, incluindo JavaScript, implementaram PCRE, porque Perl acrescentou muitas extensões úteis ao sistema POSIX
Pelo que lembro, RE2 tenta conter os problemas de desempenho e comportamentos peculiares dos sistemas existentes, e eu achava que ele tinha sido implementado inteiro em Go
Depois descobri que não sabia que RE2 tinha surgido antes de Go
Ainda assim, o significado de
$e a forma de mudar para o modo multilinha costumam ser relativamente consistentesAs pessoas estão confundindo strings e linhas
Uma string é uma sequência de caracteres, e uma linha pode ser vista de duas formas
Se a quebra de linha for vista como terminador de linha, uma linha é formada por zero ou mais caracteres que não sejam quebra de linha, seguidos por uma quebra de linha; sem quebra de linha no final, não é uma linha completa
POSIX usa essa visão
Se a quebra de linha for vista como separador de linhas, uma linha é uma sequência de zero ou mais caracteres que não sejam quebra de linha
Em qualquer uma das visões, o conteúdo da linha termina antes da quebra de linha
A semântica de
^e$é baseada em linhas, tanto no modo de linha única quanto no modo multilinhaPara semântica baseada em string — ou, ao lidar com arquivos, algo que pode ser visto como semântica do arquivo inteiro — deve-se usar
\Ae\Z, ou equivalentesAmbas as interpretações têm vantagens
Ao transmitir texto por uma conexão serial, usar a quebra de linha como terminador ajuda a saber se uma linha completa foi recebida
Em arquivos de texto, ver a quebra de linha como separador de linhas pode ser conveniente para que a última linha não fique em estado inválido, mas usar terminadores de linha permite detectar uma linha escrita de forma incompleta
Por causa disso, houve algumas falhas graves em apps baseados em Ruby
Deve-se sempre usar
\A\zhttps://homakov.blogspot.com/2012/05/saferweb-injects-in-var...
https://sakurity.com/blog/2015/02/28/openuri.html
https://sakurity.com/blog/2015/06/04/mongo_ruby_regexp.html