1 Introdução a Expressões Regulares Eis o cenário: você recebe o trabalho de verificar palavras duplicadas (tais como “isto isto”) em páginas de um servidor web, um problema comum em documentos sujeitos à muita edição. A sua tarefa é criar uma solução que:
• Aceite qualquer quantidade de arquivos para verificar, informe cada linha de cada arquivo que tenha palavras duplicadas, destaque cada palavra repetida (usando as strings de caracteres de escape ANSI [American National Standard Institute – órgão americano para estabelecimento de padrões e normas técnicas]) e garanta que o nome de arquivo fonte apareça com cada linha no relatório.
• Trabalhe através de linhas, mesmo encontrando situações onde uma palavra ao final de uma linha é repetida no início da seguinte.
• Encontre palavras repetidas, apesar das diferenças de conversão para maiúscula, tais como com ‘The the...’, assim como que permita quantidades diversas de espaço em branco (espaços, tabulações, novas linhas e similares) ficar entre as palavras.
• Encontre palavras duplicadas mesmo quando separadas por tags (tags, rótulos) HTML [Hypertext Markup Language – Linguagem de Marcação de Hipertexto]. As tags HTML servem para marcar texto em páginas da World Wide Web, para transformar, por exemplo, uma palavra em negrito: ‘... it is very very important...’ (é muito importante). Certamente, esta é uma ordem comprida! Mas, é um problema real que precisa ser resolvido. Em determinado ponto, ao trabalhar no manuscrito deste livro, executei tal ferramenta sobre o que eu tinha escrito até então e fiquei surpreso com a quantidade de palavras duplicadas que tinha aparecido. Há muitas linguagens de programação que se poderia usar para solucionar o problema, porém, uma que tenha e à expressão regular pode tornar o trabalho substancialmente mais fácil. Expressões regulares são primordiais para processamento de texto poderoso, flexível e eficiente. As próprias expressões regulares, com uma notação de padrão geral quase como uma mini linguagem de programação, permitem que você descreva e analise texto. 1
2
Capítulo 1: Introdução a Expressões Regulares
Com o e adicional fornecido com o uso de uma ferramenta especial, as expressões regulares podem acrescentar, remover, isolar e, de uma maneira geral, dobrar, alongar e mutilar todos os tipos de texto e de dados. Poderia ser tão simples quanto um comando de busca de um editor de texto, ou tão poderosa quanto uma linguagem completa de processamento de texto. Este livro mostra as várias maneiras pelas quais as expressões regulares podem aumentar a sua produtividade. Ele o ensina como pensar em expressões regulares, para que você possa dominá-las, usufruindo a vantagem de toda a magnitude da força delas. Um programa completo que solucione o problema de palavra repetida pode ser implantado em apenas algumas linhas de muitas das linguagens de programação populares de hoje em dia. Com um único comando localizar-e-substituir de expressão regular, você pode encontrar e destacar no documento as palavras duplicadas. Com um outro, é possível remover todas as linhas sem palavras repetidas (deixando apenas as linhas de interesse para relatar). Por fim, com um terceiro, você pode garantir que cada linha a ser exibida inicie com o nome do arquivo de onde a linha veio. No próximo capítulo, veremos exemplos em Perl e Java. A linguagem hospedeira (Perl, Java, VB.NET ou qualquer que seja), oferece e periférico de processamento, mas, a verdadeira força vem das expressões regulares. Aproveitando essa força para as suas próprias necessidades, você pode aprender como escrever expressões regulares para identificar o texto desejado, enquanto ignora o texto que não deseja. Depois, você pode combinar as suas expressões com a construção de e da linguagem para, na verdade, fazer algo com o texto (acrescentar códigos adequados destacados, remover o texto, alterar o texto e assim por diante).
Solucionando Problemas Reais Saber como manobrar expressões regulares desencadeia forças de processamento que você poderia nem mesmo saber que estavam disponíveis. Várias vezes em determinado dia, as expressões regulares me ajudam a solucionar problemas, tanto os grandes quanto os pequenos (e com bastante frequência, alguns que são pequenos e que ficariam grandes se não fosse pelas expressões regulares). Mostrar um exemplo que oferece a chave para solucionar um problema grande e importante ilustra claramente o benefício das expressões regulares, mas talvez não seja tão óbvio como as expressões regulares podem ser usadas no decorrer do dia para solucionar problemas bem “pouco interessantes”. Eu uso “pouco interessante” no sentido de que tais problemas, frequentemente, não estão sujeitos às estórias sobre guerra de um botequim, mas que são bem interessantes devido ao fato de que até serem solucionados, não é possível prosseguir em seu trabalho real. Como um simples exemplo, eu precisei verificar uma série de arquivos (na verdade, os 70 arquivos ou mais que compreendem a fonte para este livro) para confirmar se cada arquivo continha exatamente ‘SetSize’ [ajustar tamanho] com tanta frequência (ou tão raramente) quanto continha ‘ResetSize’ [reajustar tamanho]. Para complicar as coisas, eu precisava desconsiderar as letras maiúsculas (de modo que, por exemplo, ‘setSize’ seria contado exatamente como ‘SetSize’). Certamente que inspecionar as 32.000 linhas de texto à mão não era prático. Mesmo usando a busca normal “encontrar essa palavra” em um editor seria árduo, especialmente com todos os arquivos e todas as possíveis diferenças de maiúsculas. Expressões regulares para o resgate! Digitando apenas um único comando curto, eu fui
Expressões Regulares como uma Linguagem
3
capaz de verificar todos os arquivos e confirmar o que precisava saber. Tempo total perdido: talvez 15 segundos para digitar o comando e outros 2 segundos para a verificação de todos os dados. Uau! (Se você estiver interessado em ver o que realmente usei, dê uma olhada adiante, na página 32). Como um outro exemplo, certa vez eu estava ajudando um amigo com alguns problemas de e-mail em uma máquina remota e ele queria me enviar uma listagem das mensagens de seu arquivo da caixa de correio. Eu poderia ter carregado uma cópia de todo o arquivo em um editor de texto e, manualmente, removido tudo, exceto as linhas de cabeçalho de cada mensagem, deixando uma espécie de índice. Mesmo se o arquivo não fosse tão grande quanto era e, mesmo se eu não estivesse conectado a uma lenta linhas discadas, a tarefa teria sido lenta e monótona. Da mesma forma, eu teria sido colocado na inconfortável posição de, realmente, ver o texto da correspondência pessoal dele. De novo, expressões regulares para o resgate! Eu dei um simples comando (usando a ferramenta comum de busca egrep, descrita mais adiante neste capítulo) para exibir a linha From:[de] e Subject: [assunto] de cada mensagem. Para informar ao egrep quais tipos de linha eu queria ver, usei a expressão regular ^(From|Subject):. Quando ele obteve a sua lista, pediu-me para enviar uma mensagem em especial (5.000 linhas!). Novamente, usar um editor de texto ou o próprio sistema de e-mail para extrair apenas uma mensagem, demoraria muito tempo. Em vez disso, eu usei uma outra ferramenta (uma chamada sed) e, de novo, usei expressões regulares para descrever exatamente o texto no arquivo que queria. Assim, pude extrair e enviar a mensagem desejada, rapidamente e com facilidade. Poupar a ambos muito tempo e irritação usando a expressão regular não foi “excitante”, mas com certeza, muito mais excitante do que perder uma hora no editor de texto. Se eu não conhecesse expressões regulares, nunca teria pensado que havia uma alternativa. Assim, com justiça, esta estória demonstra como expressões regulares e ferramentas associadas podem capacitá-lo a fazer coisas que você poderia pensar nunca ser capaz de fazer. Quando aprender as expressões regulares, você perceberá que elas são parte valiosa de seu conjunto de ferramentas e se verá imaginando que não poderia viver sem elas.† Um comando total sobre as expressões regulares é uma habilidade valiosa. Este livro oferece as informações necessárias para conseguir tal habilidade e é a minha esperança que ele forneça também a motivação para fazê-lo.
Expressões Regulares como uma Linguagem A menos que tenha alguma experiência com expressões regulares, você não entende a expressão regular ^(From|Subject): do último exemplo, porém, não há nenhuma mágica nela. De fato, não há nada mágico em mágica. O mágico simplesmente entende algo simples que não parece ser simples ou natural à plateia inexperiente. Quando você tiver aprendido como segurar uma carta enquanto faz a sua mão parecer vazia, só será necessário praticar e você também poderá “fazer mágica”. Como um idioma estrangeiro – uma vez aprendido, ele deixa de ser esquisito.
† Se você tem uma TiVo [Television Input Video Output – Entrada de Televisão Saída de Vídeo] conhece o sentimento!
4
Capítulo 1: Introdução a Expressões Regulares
A Analogia de Nome de Arquivo Uma vez que decidiu usar este livro, provavelmente você tem ao menos alguma ideia do que é uma “expressão regular”. Ainda que não tenha, quase com certeza já está familiarizado com o conceito básico. Você sabe que report.txt é um nome de arquivo específico, mas, se teve qualquer experiência com Unix ou DOS/Windows, também sabe que o padrão “*.txt” pode ser usado para selecionar múltiplos arquivos. Com padrões de nome de arquivo como esse (chamado arquivos globais ou curingas), alguns caracteres têm significado especial. O asterisco significa “combinar qualquer coisa” e um ponto de interrogação significa “combinar qualquer caractere”. Assim, com o arquivo global “*.txt”, começamos com uma combinação de qualquer coisa * e terminamos com a literal .txt, de modo que terminamos com um padrão que significa “selecione os arquivos cujos nomes começam com qualquer coisa e terminam com .txt”. A maioria dos sistemas oferece alguns caracteres especiais adicionais, porém, geralmente, esses padrões de nome de arquivo são limitados em força expressiva. Isso não implica em muitos problemas, pois o escopo do problema (oferecer maneiras convenientes de especificar grupos de arquivos) é limitado, quer dizer, apenas a nomes de arquivos. Por outro lado, lidar com texto em geral é um problema bem maior. Prosa e poesia, programa de listagens, relatórios, HTML, tabelas de código, listas de palavras ... você decide, se uma necessidade particular é específica o bastante, tal como “selecionar arquivos”, você pode desenvolver um tipo de esquema especializado ou de ferramenta para ajudá-lo na tarefa. No entanto, com o ar dos anos, foi desenvolvida uma linguagem de padrão generalizado, que é poderosa e expressiva para uma ampla variedade de usos. Cada programa a implementa e a usa de maneira diferente, porém, de maneira geral, essa poderosa linguagem padrão, e os próprios padrões, são chamados de expressões regulares.
A Analogia de Linguagem Expressões regulares completas são compostas por dois tipos de caracteres. Os caracteres especiais (como o * da analogia de nome de arquivo) são chamados de metacaracteres, enquanto que os demais são chamados de literais, ou caracteres de texto normal. Além dos padrões de nome de arquivo, o que define as expressões regulares são os expressivos poderes avançados que seus metacaracteres oferecem. Os padrões de nome de arquivo oferecem limitados metacaracteres às necessidades limitadas, porém, uma “linguagem” de expressão regular oferece metacaracteres ricos e expressivos para usos avançados. Pode ser útil pensar em expressões regulares como a linguagem delas mesmo, com texto literal agindo como as palavras e os metacaracteres como a gramática. As palavras são combinadas com a gramática, de acordo com um conjunto de regras, para criar uma expressão que comunica uma ideia. No exemplo do e-mail, a expressão que usei para encontrar linhas começando com “From:” ou “Subject:” foi [^(From|Subject)]:. Os metacaracteres estão sublinhados; em breve chegaremos à interpretação deles. Como com o aprendizado de qualquer outra linguagem, inicialmente, as expressões regulares podem parecer intimidantes. Por isso elas se parecem com mágica àqueles com ape-
beginning with ‘From:’ or ‘Subject:’ was ! ˆ( From;Subject ):". The metacharacters are underlined; we’ll get to their interpretation soon. O Estado de Espírito da Expressão Regular
5
As with learning any other language, regular expressions might seem intimidating at first. This is why it seems like magic to those with only a superficial understandnas superficial, e talvez inível para aqueles que nunca viram. ing,um andconhecimento perhaps completely unapproachable to those who have never seen as it at Regular Expressions a Language claro a um aluno Mas, assimas como all. But, just as abcdefghi!† ficaria wouldlogo soon become clear de to Japonês, a 5studenta of Japanese, regular the regular expressão em expression in
The Language Analogy s!<ênfase>([0-9]+(\.[0-9]+) {3})!
$1
! s!<emphasis>([0-9]+(\.[0-9]+){3})!
$1
!
Full regular arecrystal composed twotambém. types of characters. The special logo ficarábecome clara como água para willexpressions soon clear toofvocê you, too. characters (like the + from the filename analogy) are called metacharacters, while Este exemplo é de um script de linguagem Perl que that o meu editor usouused para to modificar This example a Perl editor modifyum a mathe rest are called literal,isorfrom normal text language characters.script What setsmy regular expressions <ênfase> para marcar endereços nuscrito. O autor tinha usado por engano a digitação de tag manuscript. The author hadadvanced mistakenly used thepowers typesetting tag <emphasis> to apart from filename patterns the expressive that their metaIP da Internet (que sãoare conjuntos de pontos e números que se parecem com 209.204.146.22). A IP addr esses (which are sets of periods and numbers that look like mark Internet charactersmágica provide. provide for limited usa oFilename comandopatterns de substituição de limited texto demetacharacters Perl com a expressão regular ). The“language” incantationprovides uses Perl’s command with the needs, but209.204.146.22 a regular expression rich text-substitution and expressive metachar expression <ênfase>([0-9]+(\.{[0-9]+) {3}) regular acters for advanced uses. para substituir tais tags pela tag apropriada
, enquanto deixa de fora os outros usos ! <emphasis>([0-9]+(\.[0-9]+){3})" It might de help to consider regular expressions their own language, withdeliteral <ênfase>. Em capítulos posteriores, as você aprenderá os detalhes como, exatamente, esse tipo desuch mágica montado, portanto, serátag, capaz de leaving aplicar as técnicas text acting the words and as the você grammar. Thewhile words are comtoasreplace tagsémetacharacters with the appropriate
other uses às of suas próprias necessidades, com o seu próprio aplicativo ou linguagem de programação. bined with grammar according to a set of rules to create an expression that com<emphasis> alone. In later chapters, you’ll learn all the details of exactly how this municatesO an idea. In thelivro email example, the expression I used to find type of incantation is constructed, so you’ll be able to apply the lines techniques to objetivo deste beginningyour withown ‘From: ’ orwith ‘Subject: ’ was ! ˆ( From;Subject ):". Thelanguage. metacharneeds, your own application or programming chance de we’ll que você substituir tags <ênfase> por tags
é pequena, acters areAunderlined; get toprecisará their interpretation soon. mas, é bem provável que você incorrerá em problemas semelhantes a “substituir isto por † “Regular expressions are easy!” A somewhat humorous comment about this: as Chapter 3 explains, As with learning any other language, expressions might seem intimidating aquilo”. O objetivo deste livroregular não é ensinar soluções para problemas específicos, mas ao the term regular expression originally comes from formal algebra. When people ask me what my contrário, éit ensiná-lo pensar sobre expressões regulares, de modo quefamiliar você seja at first. This is why seems likecomo magic toexpressions” those with onlya ablank superficial understandbook is about, the answer “regular draws face if they are not already with de the completely concept. The Japanese word for regular expression, abcd , meansseen as little superar qualquer problema que possa surgir. ing, and capaz perhaps unapproachable to those who have never it to at the average Japanese as its English counterpart, but my reply in Japanese usually draws a bit more than a blank † all. But, just asYou abcdefghi! soon become clear to a tostudent of common stare. see, the “regular” partwould is unfortunately pronounced identically a much more word, a medical term for “repr oductive organs.” You can only imagine what flashes through their Japanese, the regular expression in
Ominds Estado de Espírito da Expressão Regular until I explain!
Conforme veremos em breve, as expressões regulares completas são montadas a partir s!<emphasis>([0-9]+(\.[0-9]+){3})!
$1
!
de pequenas unidades de bloco de montagem. Cada bloco individual é bastante simples, will soonmas, become clear to you, vistocrystal que eles podem sertoo. combinados de uma infinidade de maneiras, saber como combiná-los para atingir determinado objetivo alguma This example is from a Perl language script that my requer editor used to experiência. modify a Assim, este capítulo oferece uma rápida visão geral de alguns conceitos de expressão manuscript. The author had mistakenly used the typesetting tag <emphasis> toregular. Apesar de ele não se aprofundar muito, oferece uma base para o restante deste livro se construir mark Internet IP addresses (which are sets of periods and numbers that look like e ajusta o cenário para importantes aspectos laterais que são mais detalhados antes de 209.204.146.22). The incantation uses Perl’s text-substitution command with the nos aprofundarmos nas verdadeiras expressões regulares. ly 2006 02:01 regular expression Enquanto que alguns exemplos podem parecer tolos (porque alguns são tolos), eles ! <emphasis>([0-9]+(\.[0-9]+){3})" representam os tipos de tarefas que você desejará fazer – ainda que por enquanto não Se with cada the ponto não parecer fazer tag, sentido, não se preocupe muito. to replaceperceba. such tags appropriate
while leaving other uses of Apenas deixe a essência da lição entrar. Esse é o objetivo deste capítulo. <emphasis> alone. In later chapters, you’ll learn all the details of exactly how this type of incantation is constructed, so you’ll be able to apply the techniques to your own needs, with your own application or programming language. Um comentário, certa forma humorístico, sobre isso: conforme explica o Capítulo † “Regular †“Expressões expressions regulares are easy!”são A fáceis!” somewhat humorous de comment about this: as Chapter 3 explains, originalmente o termo expressão regular é oriundo álgebra formal. me perguntam de que trata the term 3,regular expression originally comes from formaldaalgebra. When Quando people as askpessoas me what my o meu the livro,answer a resposta “expressões regulares” as deixa com um se already já não estiverem book is about, “regular expressions” draws a blank facearifinexpressivo they are not familiar familiarizadas com with the o concept. Japanese word para for regular expression, means as to para the average conceito.The A palavra Japonesa a expressão regular, abcd,significa tãolittle pouco o Japonês mediano quanto Japanese para as itso English counterpart, but my in Japanese usually draws a bit morecausa than mais a blank correspondente em inglês, masreply a minha resposta em Japonês, normalmente, do que um rosto inexstare. Youpressivo. see, the “regular” part “regular”, is unfortunately pronounced identically to a forma muchque more Veja isto, a parte infelizmente é pronunciada da mesma umacommon palavra muito comum, um word, a termo medical term para for “repr oductive organs.”Você Youpode can imaginar only imagine whatà flashes through médico “órgãos reprodutores”. o que vem mente das pessoastheir até que eu explique! minds until I explain!
6
Capítulo 1: Introdução a Expressões Regulares
Se Você Tiver Alguma Experiência com Expressão Regular Se você já estiver familiarizado com as expressões regulares, muito desta visão geral não será nova, mas de qualquer forma, por favor, assegure-se de dar ao menos uma olhada nela. Mesmo que você esteja ciente do significado básico de determinados metacaracteres, talvez algumas formas de pensar sobre e olhar para as expressões regulares sejam novas. Assim como há uma diferença entre tocar uma peça musical bem e fazer música, há uma diferença entre saber sobre expressões regulares e entendê-las, realmente. Algumas das lições apresentam as mesmas informações com as quais você já está familiarizado, mas em maneiras que podem ser novas e que são os primeiros os para entender realmente.
Buscando Arquivos de Texto: Egrep Encontrar texto é um dos usos mais simples de expressões regulares – muitos editores de texto e processadores de palavras permitem que você busque um documento, usando um padrão de expressão regular. Mais simples ainda é o utilitário egrep. Dê à egrep uma expressão regular e alguns arquivos para buscar e ele tentará combinar a expressão regular a cada linha de cada arquivo, exibindo apenas aquelas linhas nas quais for encontrada uma combinação. O utilitário egrep está disponível gratuitamente para muitos sistemas, incluindo DOS, MacOS, Windows, Unix e assim por diante. Veja no web site deste livro, http://regex.info, links sobre como conseguir uma cópia de egrep para o seu sistema. Voltando ao exemplo de e-mail da página 3, o comando que usei para gerar um sumário improvisado do arquivo de e-mail é mostrado na Figura 1-1. O egrep interpreta o primeiro agumento da linha de comando como uma expressão regular e, quaisquer argumentos restantes como arquivo(s) a buscar. No entanto, observe que as aspas simples mostradas na Figura 1-1 não fazem parte da expressão regular, mas são solicitadas pelo meu shell de comando.† Ao usar egrep, normalmente eu envolvo a expressão regular com aspas simples. Exatamente quais caracteres são especiais, em quais contextos, para qual (à expressão regular ou à ferramenta) e em que ordem eles são interpretados, tudo é uma questão que cresce em importância quando você vai para o uso de expressão regular em linguagens de programação totalmente desenvolvidas – algo que veremos a partir do próximo capítulo.
prompt [indicador] de envoltório de comando
aspas para o envoltório expressão regular ada para egrep
% egrep ’^ (De|Assunto):
’ caixa de correspondência – arquivo
argumento de primeira linha de comando
Figura 1-1: Chamando egrep a partir da linha de comando. † O shell de comando é a parte do sistema que aceita os seus comandos digitados e, de fato, executa o programa solicitado. Com o shell que eu uso, as aspas simples servem para agrupar o argumento do comando, dizendo ao shell para não prestar muita atenção ao que está dentro. Se eu não usá-las, o pode pensar, por exemplo, de um ‘*’, que eu pretendia que fizesse parte da expressão regular, como se fosse realmente parte de um padrão de nome de arquivo que ele devia interpretar. Não quero que isso aconteça, portanto, uso as aspas para “ocultar” do shell os metacaracteres. Usuários de Windows de COMMAND COM ou CMD.EXE provavelmente devem usar aspas duplas.
Metacaracteres Egrep
7
Em instantes, começaremos a analisar apenas o que significam as diversas partes da regex, mas provavelmente, você já pode imaginar apenas vendo que alguns dos caracteres têm significados especiais. Neste caso, os parênteses, os caracteres ^ e | são metacaracteres de expressão regular e combinam com os outros caracteres para gerar o resultado que desejo. Por outro lado, se a sua expressão regular não usa qualquer um da dúzia ou algo assim dos metacaracteres que egrep entende, efetivamente ela se torna uma busca de “texto simples”. Por exemplo, buscar por cat em um arquivo, encontra e exibe todas as linhas com as três letras, c ∙ a ∙ t em uma linha. Por exemplo, isso inclui qualquer linha contendo vacation [férias]. Ainda que a linha possa não ter a palavra cat, a string c-a-t em vacation ainda pode ser combinada. Como ela está lá, egrep prossegue e exibe toda a linha. O ponto chave é que a expressão regular de busca não é feita em uma base de “palavra” – egrep pode entender o conceito de bytes e linhas em um arquivo, mas normalmente, ele não tem ideia de palavras do Inglês (ou de qualquer outro idioma), sentenças, parágrafos ou outros conceitos de alto nível.
Metacaracteres Egrep Vamos começar a explorar alguns dos metacaracteres egrep que fornecem sua força de expressão regular. arei rapidamente por eles com alguns exemplos, deixando os exemplos detalhados e as descrições para capítulos posteriores. Convenções Tipográficas. Antes de começar, por favor, reveja as convenções tipográficas explicadas no prefácio, na página xix. Este livro cria certas novidades na área de digitação, portanto, algumas das minhas notações podem, em princípio, não ser familiares.
Início e Fim da Linha Provavelmente, os metacaracteres mais fáceis de entender são ^ (circunflexo) e $ (cifrão), que representam, respectivamente, o início e o fim da linha de texto, como ela está sendo verificada. Como vimos, a expressão regular cat encontra c-a-t em qualquer lugar na linha, mas ^cat só combina se o c-a-t estiver no início da linha – efetivamente, o ^ só é usado para ancorar a combinação (do resto da expressão regular) para o início da linha. Da mesma forma, cat$ só encontra c-a-t ao final da linha, tal como uma linha terminando com scat. É melhor se acostumar a interpretar expressões regulares de uma maneira mais literal. Por exemplo, não pense que
^cat combina uma linha com cat no início
e sim que:
^cat combina se você tiver o início de uma linha, imediatamente seguido por c, logo seguido por a e logo depois seguido por t.
Ambos os finais significam a mesma coisa, porém, ler da maneira mais literal permite que você entenda intrinsecamente uma nova expressão quando a vir. Como egrep interpreta ^cat$, ^$ ou mesmo só ^ ? Vire a página para verificar as suas interpretações. O circunflexo e o cifrão são especiais, uma vez que eles combinam uma posição na linha em vez de qualquer dos caracteres do próprio texto. É claro que há diversas maneiras de
8
Capítulo 1: Introdução a Expressões Regulares
combinar texto real. Além de oferecer caracteres literais, como cat em sua expressão regular, você também pode usar alguns dos itens discutidos nas próximas poucas seções.
Classes de Caracteres Combinando qualquer um de vários caracteres Digamos que você queira buscar por “grey” [cinza], mas também encontrar se tiver sido soletrado como “gray” [cinza]. A expressão regular que montei [...], geralmente chamada de uma classe de caracteres, permite que você relacione os caracteres que deseja permitir àquele ponto na combinação. Enquanto que e combina apenas um e e a combina apenas um a, a expressão regular ea combina ambos. Portanto, veja gr[ea]y: isso quer dizer para encontrar “g, seguido de r, seguido por um e ou um a, tudo seguido por y.” Pelo fato de eu ser péssimo em soletrar, estou sempre usando expressões regulares como esta na imensa lista de palavras em Inglês, para descobrir a soletração certa. Uma que uso com frequência é sep[ea]r[ea]te, porque nunca consigo me lembrar se a palavra é soletrada “seperate”, “separate” ou “separete” [separado]. A que aparece na lista é a de grafia certa; expressões regulares para o resgate. Observe como fora de uma classe, caracteres literais (como o g e o r de gr[ea]y) têm um implícito “e depois” entre eles – “combinar g e depois r ...” Isso é totalmente o oposto dentro de uma classe de caracteres. O conteúdo de uma classe é uma lista de caracteres que pode combinar àquela altura, daí a implicação ser “or” [ou]. Como um outro exemplo, talvez você queira permitir letra maiúscula na primeira letra de uma palavra, tal como com [Ss]mith. Lembre-se de que isso ainda combinará linhas que contenham smith (ou Smith) embutido em uma outra palavra, tal como com blacksmith [ferreiro]. Eu não quero bater sempre na mesma tecla através dessa visão geral, mas essa questão parece ser uma fonte de problemas dentre alguns novos usuários. Abordarei algumas formas de lidar com esse problema de palavra embutida depois que examinarmos mais alguns metacaracteres. Na classe, é possível relacionar tantos caracteres quantos você quiser. Por exemplo, [123456] combina qualquer dos dígitos relacionados. Essa classe em especial, pode ser útil como parte de
, que combina
, 6882k
, 6882k
, etc. Isso pode ajudar ao buscar cabeçalhos HTML.
Dentro de uma classe de caracteres, o metacaractere de classe de caracteres ‘-’ (traço, travessão), indica uma faixa de caracteres:
, idêntica ao exemplo anterior. [0-9] e [a-z] são abreviações comuns de classes para combinar dígitos e letras minúsculas em inglês, respectivamente. Faixas múltiplas são boas, assim, [0123456789abcdefABCDEF] pode ser escrita como [0-9a-fA-F], (ou talvez [A-Fa-f0-9], visto que a ordem na qual são dadas as faixas não importa). Estes três últimos exemplos podem ser úteis ao processar números hexadecimais. Você pode combinar livremente faixas com caracteres literais: [0-9A-Z_!.?], combinar um dígito, letra maiúscula, sublinhado, ponto de exclamação, ponto ou ponto de interrogação. Observe que um traço só é um metacaractere dentro de uma classe de caracteres – caso contrário, ele combina com o caractere normal de traço. Na verdade, ele nem sempre é um metacaractere dentro de uma classe de caracteres. Se ele for o primeiro caractere relacionado na classe, provavelmente, ele pode não indicar uma faixa, assim, não é considerado um metacaractere. Da mesma forma, o ponto de interrogação e o ponto ao final da classe, normalmente são metacaracteres de expressão regular, mas só quando não dentro de uma classe (portanto, para ficar claro, os únicos metacaracteres especiais dentro da classe em [0-9A-Z_!.?] são os dois traços / travessões).
9
Metacaracteres Egrep
Lendo ^cat$, ^$ e ^ Respostas às perguntas da página 7.
^cat$
Literalmente, significa: combina se a linha tiver um início de linha (que, é claro, todas as linhas têm), imediatamente seguido por c-a-t, e depois, imediatamente seguido pelo final da linha. Efetivamente, significa: uma linha que consiste apenas de cat – sem palavras extras, espaços, pontuação, apenas ‘cat’.
^$
Literalmente, significa: combina se a linha tiver um início de linha, imediatamente seguido pelo final da linha. Efetivamente, significa: uma linha vazia (sem qualquer conteúdo, nem ao menos espaços).
^
Literalmente, significa: combina se a linha tiver um início de linha. Efetivamente, sem sentido! Visto que cada linha tem um início, cada linha combinará – até mesmo as linhas que estão vazias!
Considere as classes de caracteres como a mini linguagem delas. As regras relativas adas pelos metacaracteres (e o que eles fazem) são completamente diferentes dentro e fora de classes de caracteres. Veremos mais exemplos disso em breve.
Classes de Caracteres Negadas Se você usar [^...] em vez de [...], a classe combina qualquer caractere que não esteja relacionado. Por exemplo, [^1-6] combina um caractere que não é de 1 a 6. O ^ precedente na classe, “nega” a lista, portanto, em vez de relacionar os caracteres que deseja incluir na classe, você relaciona os caracteres que não deseja que sejam incluídos. Você pode ter notado que o ^ usado aqui é igual ao circunflexo de início de linha apresentado na página 7. O caractere é o mesmo, mas o significado é completamente diferente. Exatamente como a palavra em inglês “wind” pode significar coisas diferentes no contexto (às vezes uma brisa forte, às vezes o que você faz com um relógio), o mesmo pode acontecer com um metacaractere. Já vimos um exemplo, o intervalo de construção do traço / travessão. Ele só é válido dentro de uma classe de caracteres (e lá, apenas quando não inicialmente dentro da classe). ^ é uma âncora de linha fora de uma classe, porém, um metacaractere de classe dentro de uma classe (exceto apenas quando está imediatamente após a chave de abertura da classe; caso contrário, ele não é especial dentro de uma classe). Não tenha medo – há casos especiais mais complexos, outros, que veremos mais adiante, não são tão ruins. Como um outro exemplo, vamos buscar aquela lista de palavras em inglês que têm q seguido por algo diferente de u. Traduzir isso em uma expressão regular, se torna q[^u]. Experimentei isso na lista que tenho e, com certeza, não havia muitos. Encontrei alguns, inclusive uma série de palavras que eu nem sabia que existiam em inglês. Eis o que aconteceu. (O que eu digitei está em negrito.)
10
Capítulo 1: Introdução a Expressões Regulares
% egrep ’q[^u]’ word.list Iraqi Iraqian migra qasida qintar goph zaqqum%
Duas palavras notáveis não relacionadas são “Qantas”, a empresa aérea Australiana, e “Iraq” [Iraque]. Embora ambas as palavras estejam no arquivo word.list, nenhuma delas foi exibida pelo meu comando egrep. Por que? Pense um pouco nisso e depois vire a página para conferir o seu raciocínio. Lembre-se que um caractere de classe de negação significa “combinar com um caractere que não está relacionado” e não “não combinar com o que está relacionado”. Esses podem parecer iguais, mas o exemplo Iraq mostra a diferença sutil. Uma maneira conveniente de ver uma classe negada é que ela é, apenas, uma abreviação de uma classe normal que inclui todos os possíveis caracteres, exceto aqueles que estão relacionados.
Combinando Qualquer Caractere com Dot [Ponto] O metacaractere . (normalmente chamado de dot ou ponto) é uma abreviação de uma classe de caracteres que combina com qualquer caractere. Ele pode ser conveniente quando você quer ter um detentor de lugar do tipo “qualquer caractere aqui” em sua expressão. Por exemplo, se quiser buscar por uma data, tal como 19/03/76, 19-03-76 ou mesmo 19.03.76, você teria que ter o trabalho de montar uma expressão regular que usasse classes de caracteres para permitir, explicitamente ‘/’, ‘-‘ ou ‘.’ entre cada número, tal como 19[-./]03[-./]76. No entanto, também seria possível experimentar apenas 19.03.76. Algumas coisas estão acontecendo com este exemplo que, inicialmente, poderiam não ficar claras. Em 19[-./]03[-./]76 não há metacaracteres, pois eles estão dentro de uma classe de caracteres. (Lembre-se, a lista de metacaracteres e seus significados são diferentes dentro e fora das classes de caracteres.) Os traços (ou travessões), neste caso, também não são metacaracteres de classe, pois cada um é a primeira coisa depois de [ ou [^. Se eles não estivessem na frente, como com [.-/], eles seriam a classe de faixa de metacaracteres, o que nesta situação, seria um erro.
Resposta ao Questionário Resposta à pergunta da página 10. Por que q[^u] não combina com “Qantas” ou “Iraq”? Qantas não combinou porque a expressão regular pedia um q em letra maiúscula, visto que o Q em Qantas é maiúsculo. Se tivéssemos usado Q[^u], o teríamos encontrado, mas não os outros, pois eles não têm um Q em maiúscula. A expressão [Qq] [^u] teria encontrado todos eles. De certa forma, o exemplo de Iraq faz uma pergunta ardilosa. A expressão regular pede um q seguido por um caractere que não é u, o que impede combinar q ao final da linha. Normalmente, as linhas têm caracteres de nova linha em cada extremidade
Metacaracteres Egrep
11
final, mas, um pequeno fato que me esqueci de mencionar (me desculpe!) foi que egrep as retira antes de verificar com a expressão regular, assim, após um q terminando a linha, não há um u com o qual ser combinado. Não se sinta mal por causa da pergunta ardilosa.† Deixe-me garantir que se egrep não tivesse retirado automaticamente as novas linhas (muitas outras ferramentas não as retira), ou se Iraq tivesse sido seguido por espaços, outras palavras ou não, a linha teria sido combinada. É importante para entender, eventualmente, os pequenos detalhes de cada ferramenta, porém, a esta altura, o que eu gostaria que você entendesse a partir deste exercício é que uma classe de caracteres, mesmo negada, ainda exige um caractere com o qual combinar. Com 19.03.76 , os pontos são metacaracteres – aqueles que combinam com qualquer caractere (inclusive o traço, o ponto e a barra oblíqua que estamos esperando). No entanto, é importante saber que cada ponto pode combinar com qualquer caractere, portanto, ele pode combinar, digamos, com ‘números de loteria: 19 203319 7639’. Assim, 19[-./]03[-./]76 é mais exato, porém, é mais difícil de ler e escrever. 19.03.76 é fácil de entender, mas vago. O que deveríamos usar? Tudo depende do que você sabe sobre os dados sendo buscados e o quão específico você sente que precisa ser. Uma questão importante repetitiva está relacionada com equilibrar o seu conhecimento de texto sendo procurado com a necessidade de ser sempre preciso ao escrever uma expressão. Por exemplo, se você soubesse que os seus dados seriam, pouco provavelmente, para 19.03.76 combinar em um lugar indesejado, com certeza seria razoável usá-los. Conhecer bem o texto alvo é uma parte importante em moldar com eficiência as expressões regulares.
Alternância Combinando qualquer de várias subexpressões Um metacaractere muito conveniente é |, o qual significa “ou”. Ele permite que você combine múltiplas expressões em uma única expressão que combina com qualquer das individuais. Digamos, por exemplo, que Bob e Robert sejam expressões separadas, mas Bob|Robert seja uma expressão que combina com qualquer uma delas. Quando combinadas assim, as subexpressões são chamadas de alternativas. Voltando ao nosso exemplo gr[ea]y,é interessante notar que ele pode ser escrito como grey|gray e até como gr(a|e)y. O último caso usa parênteses para restringir a alternância. (A título de registro, os parênteses também são metacaracteres.) Observe que algo como gr[a|e]y não é o que queremos – dentro de uma classe, o caractere ‘|’ é apenas um caractere normal, como a e e.
Com gr(a|e)y, são necessários os parênteses, pois, sem eles, gra|ey significa “gra ou ey”, que não desejamos aqui. A alternância vai longe, mas não além dos parênteses. Um outro exemplo é (First|1st)•[Ss]treet [Rua Um].† De fato, visto que ambos First e 1st ter
† Uma vez, na quarta série, eu estava liderando a competição de soletração quando fui solicitado a soletrar “miss”. A minha resposta foi “m-i-s-s”[falha, engano]. A Srta. Smith teve o prazer de me dizer que não, que era “M-i-s-s” [Senhorita, mulher jovem, solteira], com M maiúsculo, que eu deveria ter pedido uma sentença de exemplo, e que eu estava fora. Foi um momento traumático na vida de um menino. Depois daquilo, nunca mais gostei da Srta. Smith e, desde então, tenho sido muito ruim em soletração.
12
Capítulo 1: Introdução a Expressões Regulares
minam com st, a combinação pode ser abreviada para (Fir|1)st•[Ss]treet. Necessariamente, isso não é muito fácil de ler, mas, assegure-se de entender que, efetivamente, (first|1st) e (fir|1)st significam a mesma coisa. Eis um exemplo envolvendo uma soletração alternativa do meu nome. Compare e contraste as seguintes três expressões, as quais são, efetivamente, iguais: Jeffrey|Jeffery
Jeff(rey|ery) Jeff(re|er)y
Para combiná-las com a ortografia Britânica também, elas seriam: (Geoff|Jeff)(rey|ery)
(Geo|Je)ff(rey|ery)
(Geo|Je)ff(re|er)y
Por fim, observe que essas três combinações são iguais a mais longa (porém mais simples) Jeffrey|Geoffery|Jeffery|Geoffrey. Todas elas são maneiras diferentes de especificar as mesmas combinações desejadas. Ainda que os exemplos gr[ea]y comparado com gr(a|e)y possam embaçar a distinção, tenha cuidado para não confundir o conceito de alternância com aquele de uma classe de caracteres. Uma classe de caracteres pode combinar com apenas um único caractere no texto alvo. Com a alternância, visto que cada alternativa pode ser uma expressão regular totalmente desenvolvida, dentro e fora de si mesma, cada alternativa pode combinar com uma quantidade arbitrária de texto. As classes de caracteres são quase como a sua própria mini linguagem especial (por exemplo, com as suas próprias ideias sobre metacaracteres), enquanto que a alternância é parte da linguagem “principal” de expressão regular. Você descobrirá que ambas são extremamente úteis. Também é preciso tomar cuidado ao usar o circunflexo ou o cifrão em uma expressão que tenha alternância. Compare ^From|Subject|Date:• com ^(From|Subject|Date):•. Ambas parecem semelhantes ao nosso exemplo anterior de e-mail, mas o que cada uma combina (e, portanto, o quão útil é), difere muito. A primeira é composta de três alternativas, assim, ela combina com “From, Subject ou Date:•”, o que não é especialmente útil. Queremos o circunflexo inicial e o :• final para aplicar a cada alternativa. Nós podemos conseguir isso, usando parênteses para “restringir” a alternância: ^(From|Subject|Date):•
A alternância é restrita pelos parênteses, assim, literalmente, essa regex significa “combinar com o início da linha, depois com um de From, Subject ou Date e depois, combinar com :•. Efetivamente, ela combina: 1) início de linha, seguido por F-r-o-m, seguido por ‘:•’ ou 2) início de linha, seguido por S-u-b-j-e-c-t, seguido por ‘:•’ ou 3) início de linha, seguido por D-a-t-e, seguido por ‘:•’ Colocando menos literalmente, ela combina linhas começando com ‘From:•’, ‘Subject:•’ ou ‘Date:•’, que é bastante útil para relacionar as mensagens em um arquivo de e-mail. † Recordação das convenções tipográficas da página xix, em que “•” é como às vezes eu mostro um caractere de espaço, de modo que ele possa ser visto com facilidade.
Metacaracteres Egrep
13
Eis um exemplo: % egrep ’^(From|Subject|Date): ’ mailbox From:
[email protected] (The King) Subject: be seein’ ya around Date: Mon, 23 Oct 2006 11:04:13 From: The Prez
[email protected] Date: Wed, 23 Oct 2006 8:36:24 Subject: now, about your vote . . .
Ignorando as Diferenças em Letras Maiúsculas Este exemplo de cabeçalho de e-mail oferece uma boa oportunidade para apresentar o conceito de uma combinação case-insensitive [ignorando maiúsculas e minúsculas]. Normalmente, o campo de fontes em um cabeçalho de e-mail aparece com maiúscula à frente, tal como “Assunto” e “De”, porém, o e-mail padrão permite a mistura de letras maiúsculas e minúsculas, de modo que coisas como “DATA” e “de” também são permitidas. Infelizmente, a expressão regular na seção anterior não as combina. Uma abordagem é substituir From por [Ff][Rr][Oo][Mm] para combinar qualquer forma de “from”, porém, isso é no mínimo de manejo bem difícil. Por sorte, há uma maneira de dizer ao egrep para ignorar o estilo de fonte ao fazer comparações, ou seja, executar a combinação de uma maneira sensível à case-insensitive, onde as diferenças de maiúsculas ou minúsculas são simplesmente ignoradas. Isso não faz parte da linguagem de expressão regular, mas é um recurso útil associado que muitas ferramentas fornecem. A opção de linha de comando de egrep “-i” o informa para fazer uma combinação sensível à tipográfica. Coloque –i na linha de comando antes da expressão regular: %
egrep –i ’^(From)Subject|Date): ’ mailbox
Isso apresenta todas as linhas que combinamos anteriormente, mas também inclui linhas como: SUBJECT: MAKE MONEY FAST
Com bastante frequência, eu me vejo usando a opção –i (talvez devido à relação com a nota de rodapé da página 11!), portanto, recomendo ter isso em mente. Em capítulos posteriores, veremos outros recursos de e convenientes.
Limites de Palavra Um problema comum é que uma expressão regular que combina a palavra desejada, com frequência também pode combinar onde a “palavra” está embutida em uma palavra maior. Mencionei isso rapidamente nos exemplos cat, gray e Smith. Mas, acontece que algumas versões de egrep oferecem e limitado ao reconhecimento de palavra: nomeadamente, a habilidade de combinar o limite de uma palavra (onde uma palavra se inicia e termina). Você pode usar as meta strings \< e \> (talvez de aparência estranha), se por acaso a sua versão á-las (nem todas as versões de egrep o fazem). É possível pensar nelas como versões de ^ e $ baseadas em palavras, que combinam a posição no início e no fim de uma palavra, respectivamente. Como as âncoras de linha, circunflexo e cifrão, elas ancoram outras partes da expressão regular, mas, na verdade não usam quaisquer caracteres durante uma combinação. A expressão <\cat\> significa, literalmente, “combinar, se pudermos encontrar uma posição de início de palavra, imediatamente seguida por c-a-t, logo depois seguida por uma posição de
14
Capítulo 1: Introdução a Expressões Regulares
fim de palavra”. Com mais naturalidade, ela significa “encontrar a palavra cat”. Se você quisesse, poderia usar / < cat ou cat\> para encontrar palavras começando e terminando com cat. Observe que sozinhos, < e > não são metacaracteres – quando combinadas com uma barra invertida, as strings tornam-se especiais. Por isso é que as chamei de “meta strings”. A interpretação especial delas é importante, não a quantidade de caracteres, portanto, na maioria das vezes eu uso essas duas meta palavras alternadamente. Lembre-se que nem todas as versões de egrep am esses metacaracteres de limite de palavra, e aquelas que não entendem, magicamente, o idioma inglês. O “início de uma palavra” é simplesmente a posição onde começa uma string de caracteres alfanuméricos; “fim de palavra” é onde tal string termina. A Figura 1-2, mostra uma linha de exemplo com essas posições marcadas. Os inícios de palavras (conforme egrep os reconhece) estão marcados com setas para cima, os finais de palavras com setas para baixo. Como é possível ver, “início e fim de palavra” é dito mais claramente como “início e fim de uma string alfanumérica”, mas talvez, isso seja muita conversa.
That dang - tootin’ #@!%*
- posições onde \< é verdadeira
varmint’s cost me $199.95 !
- posições onde \> é verdadeira That
– “palavras”
dang - tootin’
#@!%*
- posições onde \< é verdadeira
Figura 1-2: Posições de início e fim de “palavra”
- posições onde \> é ve
Resumidamente A Tabela 1-1 resume os metacaracteres que vimos até agora. Tabela 1-1: Resumo de Metacaracteres Vistos Até Agora Metacaractere Nome
Combina
. [ ] [^ ]
ponto qualquer caractere qualquer caractere relacionado classe de caractere classe de caractere de nega- qualquer caractere não relacionado ção
^ $ \< \>
circunflexo cifrão barra invertida, menor que barra invertida, maior que
a posição no início da linha a posição ao final da linha † a posição no início de uma palavra † a posição ao final de uma palavra † não ado por todas as versões de egrep
| (
)
ou, barra parênteses
varmint’
combina com qualquer expressão que ela separa usado para limitar o escopo de |, além de usos adicionais que ainda serão abordados
Além da tabela, pontos importantes a serem lembrados incluem:
• As regras sobre quais caracteres são metacaracteres e quais não são (e exatamente o que eles significam) diferem dentro de uma classe de caracteres. Por exemplo, ponto é um metaca-
Metacaracteres Egrep
15
ractere fora de uma classe, mas não dentro de uma. Ao contrário, um traço (normalmente) é um metacaractere dentro de uma classe, mas não fora. Além do mais, um circunflexo tem um significado fora e um outro especificado dentro de uma classe imediatamente após a abertura [, e um terceiro significado se fornecido em qualquer outro lugar na classe.
• Não confunda alternância com uma classe de caracteres. A classe [abc] e a alternância
(a|b|c) significam, efetivamente, a mesma coisa, mas a semelhança neste exemplo não se estende ao caso em geral. Uma classe de caractere pode combinar exatamente um caractere, e isso é verdade, independente de quão longa ou curta possa ser a lista especificada de caracteres aceitáveis.
Por outro lado, a alternância pode ter longas alternativas arbitrárias, cada qual textualmente não relacionada com a outra: \<(1,000,000|million|thousand•thou)\>. Entretanto, a alternância não pode ser invalidada como uma classe de caractere.
• Um caractere de negação é simplesmente uma conveniência de notação para uma classe de caractere normal que combina com qualquer coisa que não esteja relacionada. Assim, [^x] não significa “combinar a menos que haja um x”, e sim “combinar se houver algo que não seja x”. Apesar de sutil, a diferença é importante. Por exemplo, o primeiro conceito combina uma linha em branco, enquanto que [^x] não combina.
• A útil opção –i ignora letras maiúsculas e minúsculas durante uma combinação (☞13).† O que vimos até agora pode ser bastante útil, mas a verdadeira força vem dos elementos opcionais e de contagem, que veremos a seguir.
Itens Opcionais Vejamos a combinação de color ou colour. Visto que ambas são iguais, exceto que uma tem um u e a outra não, podemos usar colou?r para combinar com qualquer uma. O metacaractere ? (ponto de interrogação) significa opcional. Ele é colocado depois de um caractere que tem permissão de aparecer naquele ponto da expressão, porém, cuja existência não é exigida, de fato, para ser considerada uma combinação bem sucedida. Diferente de outros metacaracteres que vimos até agora, o ponto de interrogação só combina com o item imediatamente precedente. Assim, colou?r é interpretado como “c, depois o, depois l, depois o, depois u? e depois r”. A parte u? é sempre bem sucedida: às vezes, ela combina com um u no texto, enquanto que em outras vezes não combina. A questão toda é que a parte opcional-? é considerada bem sucedida de qualquer das maneiras. Isso não significa que qualquer expressão regular que contém ? é sempre bem sucedida. Por exemplo, com 'semicolor', ambos, colo e u?, são bem sucedidos (combinando colo e nada [vazio], respectivamente). Porém, o final r falha e é isso que não permite o semicolon no final, de ser combinado por colou?r. Como um outro exemplo, imagine combinar uma data que represente quatro de Julho, com a parte “July sendo July ou Jul e a parte “fourth” sendo fourth, 4th [4º] ou apenas 4.
†Recordando as convenções tipográficas (página xix), que às vezes se parece como “☞13”, trata-se de uma abreviação para uma referência à outra página neste livro.
16
Capítulo 1: Introdução a Expressões Regulares
É claro que poderíamos simplesmente usar ( July|Jul)•(fourth|4th|4), mas, vamos explorar outras formas de expressar a mesma coisa. Primeiro, podemos encurtar o ( July|Jul) para ( July?). Você vê como eles são, efetivamente, iguais? A remoção do | significa que os parênteses não são mesmo mais necessários. Afastar os parênteses não incomoda, mas com eles removidos, July? é um pouco menos amontoado. Isso nos deixa com July?•(fourth|4th|4). Indo para a segunda metade, podemos simplificar o 4th|4 para 4(th)?. Como é possível ver, ? pode anexar a uma expressão com parênteses. Dentro dos parênteses pode haver uma subexpressão tão complexa quanto você quiser, mas “de fora” ela é considerada uma unidade individual. O agrupamento para ? (e outros metacaracteres similares que apresentaremos em breve) é um dos principais usos de parênteses. Agora, a nossa expressão se parece com July?•(fourth|4(th)?). Ainda que haja uma boa quantidade de metacaracteres e até mesmo parênteses aninhados, não é tão difícil decifrar e entender. Essa abordagem de dois exemplos essencialmente simples foi bastante longa, mas ao mesmo tempo, cobrimos tópicos importantes, que acrescentam muito, talvez apenas no subconsciente, ao nosso entendimento de expressões regulares. Da mesma forma, obtemos alguma experiência em tomar diferentes caminhos na direção do mesmo objetivo. À medida em que avançamos por este livro (e através de um entendimento melhor), você descobrirá muitas oportunidades para resultados criativos para prosseguir enquanto estiver tentando encontrar a maneira ótima de solucionar um problema complexo. Longe de ser uma ciência retrograda, escrever expressões regulares está mais perto de uma arte.
Outros Quantificadores: Repetição Semelhantes à interrogação, são + (adição) e * (um asterisco, mas como um metacaractere de expressão regular, eu prefiro o termo estrela). O metacaractere + significa “um ou mais do item imediatamente precedente”, e * significa “qualquer número do item, inclusive nenhum”. Escrito diferentemente, ...* significa “tentar combiná-lo tantas vezes quantas possível, mas está certo ajustar para nenhuma, se for preciso”. A construção com o sinal de adição, ...+ é semelhante no que se refere a que ele também tenta combinar tantas vezes quanto possível, porém é diferente, pois falha se não puder combinar pelo menos uma vez. Esses três metacaracteres, ponto de interrogação, adição e estrela são chamados de quantificadores, porque eles influenciam a quantidade do que regem. Tal como ...?, a parte ...* de uma expressão é sempre bem sucedida, sendo que a única questão é qual texto (se houver) é combinado. Compare isso com ...+, que falha, a menos que o item combine pelo menos uma vez. Por exemplo, •? permite um único espaço opcional, mas •* permite qualquer quantidade de espaços opcionais. Podemos usar isto para tornar mais flexível o exemplo da página 8
. A especificação HTML† diz que são permitidos espaços imediatamente antes do fechamento >, tal como com
e
. Inserindo •* em nossa expressão regular onde queremos permitir (mas não exigir) espaços, obtemos
. Isso ainda combinará
, pois não são exigidos quaisquer espaços, porém, ainda é flexível para se relacionar com as outras versões. Aprofundando-nos mais, busquemos uma tag HTML, tal como
, a qual indica que uma linha (uma Horizontal Rule [Linha Horizontal]) com 14 pixels de espessura deve
Metacaracteres Egrep
17
ser desenhada pela tela. Como no exemplo de
, os espaços opcionais são permitidos antes da chave angular de fechamento. Além disso, eles são permitidos em ambos os lados do sinal de igualdade. Finalmente, é exigido um espaço entre o HR e o SIZE, embora sejam permitidos mais. Para permitir mais, poderíamos simplesmente acrescentar •* ao * já existente, mas, em vez disso, vamos mudá-lo para •+. O sinal de adição permite espaços extras enquanto ainda exige pelo menos um, portanto, na realidade, para ser conciso ele é igual a ••*. Todas essas alterações nos deixam com
. Embora flexível com relação a espaços, a nossa expressão ainda é inflexível com relação ao tamanho dado na tag. Em vez de encontrar tags apenas com um tamanho em especial, tal como 14, nós queremos encontrá-las todas. Para conseguir isso, substituímos o 14 por uma expressão para encontrar um número geral. Bem, neste caso, um “número” consiste de um ou mais dígitos. Um dígito é [0-9] e adiciona “um ou mais”, portanto, acabamos substituindo 14 por [0-9]+. (Um caractere de classe é uma “unidade”, assim, pode estar diretamente sujeito a um sinal de adição, ponto de interrogação e assim por diante, sem a necessidade de parênteses.) Isto nos deixa com
, o que por certo é muita coisa, ainda que tenha sido mostrado com os metacaracteres em negrito, com o acréscimo de um pouco de espaçamento para tornar o agrupamento mais claro e eu esteja usando o símbolo “visível de espaço”, ‘•’, para clareza. (Felizmente, egrep tem a opção –i case-sensitive, ☞ 13, o que significa que não preciso usar [Hh] [Rr] em vez de HR.) A expressão regular sem enfeites
, provavelmente parece até mais complicada. Este exemplo parece bem estranho, porque os assuntos da maioria das estrelas e sinais de adição são caracteres de espaço e o nosso olhar sempre foi treinado para tratar os espaços como especiais. Esse é um costume do qual você terá que se afastar ao ler expressões regulares, pois o caractere de espaço é um caractere normal, sem diferença de digamos, j ou 4. (Em capítulos posteriores, veremos que algumas outras ferramentas am um modo especial, no qual espaço em branco é ignorado, mas, egrep não possui tal modo.) Prosseguindo na exploração de um bom exemplo, vamos imaginar que o atributo de tamanho seja opcional, portanto, você pode simplesmente usar
se for desejado o tamanho padrão. (Como sempre, espaços extras são permitidos antes de >.) Como podemos modificar a nossa expressão regular para que ela combine com qualquer tipo? A chave está em perceber qual parte do tamanho é opcional (esta é uma dica). Vire a página para conferir a sua resposta. Dê uma boa olhada em nossa última expressão (na caixa de resposta), para verificar as diferenças entre o ponto de interrogação, a estrela e o sinal de adição, e o que eles realmente significam na prática. A Tabela 1-2, resume o significado deles. Observe que cada quantificador tem algum número mínimo de combinações exigido para ser bem sucedida, e uma quantidade máxima de combinações que sempre tentará fazer. Com algumas, o número mínimo é zero; com outras, o número máximo é ilimitado.
† Se você não estiver familiarizado com HTML, não tema. Eu uso esses exemplos de mundo real, mas forneço todos os detalhes necessários para entender as ações tomadas. Aqueles que estão familiarizados com a análise de tags [tags] HTML, provavelmente reconhecerão informações importantes que não abordo a esta altura do livro.
18
Capítulo 1: Introdução a Expressões Regulares
Formando uma Subexpressão Ótima Resposta à pergunta da página 17. Neste caso, “opcional” significa que ele é permitido uma vez, porém, não exigido. Isso significa usar ?. Visto que o item opcional é maior que um caractere, precisamos usar parênteses: (...) ?. Inserindo em nossa expressão, obtemos: !
"
Observe que o final •* é mantido fora do (...)?. Isso ainda permitirá algo como
. Se tivéssemos incluído entre parênteses, os espaços finais só teriam sido permitidos quando o size do componente estivesse presente. Da mesma forma, note que o •+ antes de SIZE é incluído entre parênteses. Se ele fosse deixado fora deles, seria exigido um espaço depois do HR, mesmo se a porção SIZE não estivesse presente. Isso levaria ‘
’ a não combinar. Tabela 1-2: Resumo de Quantificador “Repetição de Metacaracteres”
? * +
Mínimo Exigido
Máximo a Tentar
Significado
nenhum nenhum 1
1 sem limite sem limite
um permitido; nenhum exigido (“um opcional”) permissão ilimitada; nenhum exigido (“qualquer quantia OK”) permissão ilimitada; um exigido (“pelo menos um”)
Faixa definida de combinações: intervalos Algumas versões de egrep am uma meta string para oferecer o seu próprio mínimo e máximo: ...{min, max}. Isso é chamado de quantificador de intervalo. Por exemplo, ...{3, 12} combina até 12 vezes, se possível, mas, para em três. Seria possível usar [a-zA-Z] {1. 5} para combinar um registrador automático de cotação de títulos dos EUA (à uma de cinco letras). Usar essa notação, {0,1}, é o mesmo que usar um ponto de interrogação. Não são muitas as versões de egrep que já am essa notação, porém, muitas ferramentas o fazem, assim, ela é discutida no Capítulo 3, quando virmos em detalhes o amplo espectro de metacaracteres comumente utilizados na atualidade.
Parênteses e Referências Anteriores Até agora, vimos duas utilizações para os parênteses: limitar o escopo de alternância, | e agrupar múltiplos caracteres em unidades maiores às quais é possível aplicar quantificadores, como pontos de interrogação e estrela. Eu gostaria de discutir sobre um outro uso especializado que não é comum em egrep (embora a versão popular do GNU e), mas que é normalmente encontrado em muitas outras ferramentas. Em muitos sabores de expressões regulares, os parênteses podem se “lembrar” de texto combinado pela subexpressão que eles envolvem. Usaremos isso em uma solução parcial para o problema de palavras duplicadas do início deste capítulo. Se você conhecesse a a palavra (the word do livro original para explicar a próxima sentença) duplicada específica a encontrar (tal como o “the” nesta sentença – conseguiria pegá-la?), poderia buscá-la explicitamente, tal como com the•the. Nesse caso, também poderia encontrar itens tais como the•theory, embora pudesse contornar o problema facilmente se o seu egrep asse as
Metacaracteres Egrep
19
meta strings de limite de palavra \<...\> mencionadas na página 14: \
. Poderíamos usar •+ para o espaço, para maior flexibilidade. No entanto, ter que verificar cada possível par de palavras seria uma tarefa impossível. Não seria bom se pudéssemos combinar uma palavra genérica e depois dizer “agora, combina novamente a mesma coisa?” Se o seu egrep ar referência anterior, é possível. A referência anterior é um recurso de expressão regular que lhe permite combinar novo texto que é igual a algum texto combinado anteriormente na expressão. Começamos com \
e substituímos o the inicial por uma expressão regular, para combinar uma palavra geral, como [A-Za-z]+. Depois, por motivos que ficarão claros no próximo parágrafo, colocamos parênteses em volta dela. Por fim, substituímos o segundo ‘the’ pela meta string especial \1. Isso produz \<([A-Za-z]+)•+\1\>. Com ferramentas que am referências anteriores, os parênteses “lembram” o texto com o qual a subexpressão dentro deles combina, e a meta string especial \1 representa aquele texto posteriormente na expressão regular, sempre que for a hora. Logicamente é possível ter mais de um conjunto de parênteses em uma expressão regular. Use \1, \2, \3, etc, para referenciar o primeiro, segundo, terceiro, etc. conjuntos. Os pares de parênteses são numerados contando os parênteses de abertura a partir da esquerda, portanto, com ([a-z])([0-9])\1\2, o •\1 refere-se ao texto combinado por [a-z] e \2 refere-se ao texto combinado por [0-9]. Com o nosso exemplo ‘the•the’, [A-Za-z]+ combina com o primeiro ‘the’. Ele está dentro do primeiro conjunto de parênteses, portanto, o ‘the’ combinado torna-se disponível através de \1. Se o •+ seguinte combinar, o \ 1 subsequente exigirá um outro ‘the’. Se \1 for bem sucedido, então \> garante que agora estamos no limite do final de uma palavra (o que não aconteceria se houvesse o texto ‘the•theft’). Se bem sucedido, encontramos uma palavra repetida. Nem sempre é o caso em que que há um erro (tal como com “que” nesta sentença), mas é você quem decide quando linhas suspeitas são exibidas. Quando resolvi incluir este exemplo, na verdade eu o experimentei no que tinha escrito até então. (Usei uma versão de egrep que a ambos, \<...\> e referência anterior.) Para torná-lo mais útil, de modo que ‘The•the’ também pudesse ser encontrado, usei a opção –i case-sensitive mencionada na página 13.† Eis o comando que executei: % egrep –i ‘\<([a-z]+) +\1\>’ files [arquivos]
Fiquei surpreso de encontrar catorze conjuntos de palavras erroneamente ‘repetidas-repetidas’! Eu as corrigi e, desde então, tenho montado esse tipo de expressão regular de verificação nas ferramentas que uso para produzir a saída final deste livro, para garantir que não ficasse nenhum escorregão. Ainda que essa expressão regular seja tão útil, é importante entender as suas limitações. Visto que egrep considera isoladamente cada linha, não consegue encontrar quando o final de palavra de uma linha é repetido no início da próxima. Por isso, é necessária uma ferramenta mais flexível, e no próximo capítulo veremos alguns exemplos. †Esteja ciente de que algumas versões de egrep, inclusive versões mais antigas oferecidas do popular GNU, causam um bug [mau funcionamento] com a opção –i, de modo que não se aplicam a referências anteriores. Assim ele encontra “the the”, mas não “The the”.
20
Capítulo 1: Introdução a Expressões Regulares
O Ótimo Escape Uma coisa importante que ainda não mencionei é como, de fato, combinar um caractere que, normalmente, uma expressão regular interpretaria como um metacaractere. Por exemplo, se eu buscasse por um hostname [nome de hospedeiro / domínio] da Internet ega.att.com usando ega.att.com, ele poderia acabar combinando algo como megawatt•computing. Lembre-se, . é um metacaractere que combina com qualquer caractere, inclusive um espaço. A meta string para combinar com um ponto é um ponto precedido de uma barra invertida: ega\.att\.com. A string \. é descrita como um escaped period ou escaped dot [ponto de escape] e é possível fazer isso com todos os metacaracteres normais, exceto em uma classe de caracteres.† Uma barra invertida usada dessa maneira é chamada de “escape” – quando um metacaractere escapa, ele perde o seu significado especial e se transforma em um caractere literal. Se você quiser, pode considerar a string como uma meta string especial para combinar com o caractere literal. É tudo a mesma coisa. Como um outro exemplo, você poderia usar \([a-zA-Z]+\) para combinar com uma palavra entre parênteses, tal como ‘(very)’ [muito]. As barras invertidas nas strings \ ( e \ ) removem a interpretação especial dos parênteses, deixando-os como literais para combinar com os parênteses no texto. Quando usada antes de um não metacaractere, uma barra invertida pode ter diferentes significados, dependendo da versão do programa. Por exemplo, já vimos como algumas versões tratam \<, \>, \1, etc. como meta strings. Veremos muitos outros exemplos nos próximos capítulos.
Além do Fundamental Espero que os exemplos e as explicações dadas até agora tenham ajudado a estabelecer a base para uma compreensão sólida de expressões regulares, mas, por favor, note que o oferecido não tem profundidade. Há muito mais lá fora.
Diversificação Linguística Eu mencionei uma série de recursos de expressão regular que a maioria das versões de egrep a. Existem outros recursos, alguns dos quais não são ados por todas as versões, o que deixarei para capítulos posteriores. Infelizmente, a linguagem de expressão regular não é diferente de qualquer outra, pelo fato de ter diversos dialetos e sotaques. Parece que cada novo programa empregando expressões regulares concebe seus próprios “aperfeiçoamentos”. O moderno avança continuamente, porém, com os anos as alterações resultaram em uma ampla gama de “sabores” de expressão regular. Nos próximos capítulos veremos muitos exemplos.
†A maioria das linguagens de programação e de ferramentas permite que você também escape caracteres dentro de uma classe de caracteres, porém, a maioria das versões de egrep não o faz, ao invés disso, trata ‘\’ dentro de uma classe, como uma barra invertida literal a ser incluída na lista de caracteres.
Ampliando a Base
21
O Objetivo de uma Expressão Regular A partir da ampla visão de cima para baixo, uma expressão regular ou combina dentro de um pedaço de texto (com egrep, cada linha), ou não combina. Ao moldar uma expressão regular, é preciso levar em conta o cabo-de-guerra em andamento entre fazer a sua expressão combinar com as linhas desejadas, e mesmo assim, não combinar com as linhas que você não deseja. Da mesma forma, enquanto que egrep não se preocupa onde, na linha, acontece uma combinação, essa preocupação é importante para muitas outras utilizações de expressão regular. Se o seu texto for algo como ...CEP é 44272. Se você escrever, envie $4,85 para o valor do selo...
e você só quiser encontrar as linhas que combinam com [0-9]+, não tem preocupação com quais números são combinados. No entanto, se a sua intenção for fazer alguma coisa com o número (tal como salvar um arquivo, acrescentar, substituir e etc. – veremos exemplos desse tipo de processamento no próximo capítulo), você se preocupará muito, exatamente para com quais números são combinados.
Mais Alguns Exemplos Como com qualquer linguagem, a experiência é uma coisa muito boa, assim, incluo mais alguns exemplos de expressões regulares para combinar com algumas construções comuns. Ao escrever expressões regulares, metade da batalha é conseguir combinações bem sucedidas quando e onde você as deseja. A outra metade é não combinar quando e onde você não deseja. Na prática, ambas são importantes, entretanto no momento, eu gostaria de me concentrar na questão “conseguir combinações bem sucedidas”. Ainda que os exemplos não sejam tomados em sua maior profundidade, eles ainda oferecem uma perspicácia útil.
Nomes de Variáveis Muitas linguagens de programação têm identificadores (nomes de variáveis, etc.) que só podem conter caracteres alfanuméricos e sublinhados, porém, que não podem começar com um dígito. Eles são combinados por [a-zA-Z_][a-zA-Z_0-9]*. A primeira classe de caractere combina com o que o primeiro caractere pode ser, a segunda (seguida por uma estrela), possibilita o restante do identificador. Se houver um limite no comprimento de um identificador, digamos de 32 caracteres, você pode substituir a estrela por {0,31} se a notação {min,max} for ada. (Essa construção, o quantificador de intervalo, foi mencionada rapidamente na página 18.)
Uma string dentro de aspas duplas Uma solução simples para combinar uma string [string] dentro de aspas duplas poderia ser: ”[^”]*”. As aspas duplas em cada extremidade devem combinar com as aspas duplas de abertura e fechamento da string. Entre elas, podemos não ter nada ... exceto uma outra aspa dupla! Assim, usamos [^*] para combinar todas os caracteres, exceto uma aspa dupla, e aplicar usando uma estrela para indicar que temos qualquer quantidade de tais caracteres não de aspas duplas. Uma definição mais útil (ainda que mais complexa) de uma string entre aspas duplas, permite
22
Capítulo 1: Introdução a Expressões Regulares
aspas duplas dentro da string se elas forem escapadas com uma barra invertida, tal como em “nail•the•2\”x4\”•plank”. Em futuros capítulos, veremos este exemplo diversas vezes enquanto abordarmos os muitos detalhes de como é, realmente, realizada uma combinação.
Quantia em dólar (com opção de centavos) Uma abordagem para combinar uma quantia em dólar é: \$[0-9]+(\.[0-9][0-9])?. A partir de uma perspectiva de nível superior, esta é uma expressão regular simples, com três partes: /$, ...+ e (...)?, que poderia ser vagamente parafraseada como “um sinal literal de dólar, um punhado de uma coisa e, finalmente, talvez uma outra coisa”. Nesse caso, a “uma coisa” é um dígito (com um punhado deles sendo um número), e “uma outra coisa” é a combinação de um ponto decimal seguido por dois dígitos. Este exemplo é um pouco ingênuo por diversos motivos. Por exemplo, ele considera quantias de dólar como $1000, e não como $1,000. Ele permite a opção de centavos, mas sinceramente, isso não é realmente muito útil quando aplicado com egrep. O egrep nunca se preocupa exatamente com quanto é combinado, mas apenas se há uma combinação. Para início de conversa, permitir alguma coisa ao final nunca altera se há uma combinação total. Entretanto, se você precisar encontrar linhas que contenham apenas um preço e nada mais, é possível envolver a expressão com ^...$. Nesse caso, a parte da opção de centavos se torna importante, visto que ela poderia ou não ficar entre a quantia em dólar e o fim da linha, e permiti-la ou não faz a diferença em conseguir uma combinação total. Um tipo de valor que a nossa expressão não combina é ‘$.49’. Para resolver isso, você poderia ficar tentado a mudar o sinal de adição para uma estrela, mas isso não funcionaria. Quanto ao motivo, eu o deixarei como uma provocação, até vermos um exemplo semelhante no Capítulo 5 (☞ 177).
Uma URL HTTP / HTML O formato de URLs da web pode ser complexo, assim, montar uma expressão regular para combinar com qualquer possível URL pode ser igualmente complexo. No entanto, diminuindo ligeiramente os seus padrões pode possibilitar que você combine a maioria dos URLs comuns com uma expressão bem simples. Por exemplo, um motivo comum pelo qual eu faria isso seria buscar meu arquivo de e-mail quanto a um URL que me lembro vagamente de ter recebido, mas que acredito poder reconhecer quando o vir. A forma geral de um URL HTTP /HTML é do tipo de http://hostname/path.html
embora o final com .htm também seja comum. As regras sobre o que pode e o que não pode ser um hostname [endereço na web, tal como www.yahoo.com) são complexas, mas, para as nossas necessidades, podemos perceber que se ele seguir ‘http://’, provavelmente é um nome de hospedeiro, assim, podemos ficar com algo simples, tal como [-a-z0-9_.1]+. A parte de caminho pode ser ainda mais variada, assim, para isso usaremos [-a-z0-9_:@&?=+,.!/~*%$]*. Observe que essas classes têm o traço primeiro, para garantir que ele seja tomado como um caractere literal e incluído na lista, em oposto à porção de uma faixa (☞ 8). Juntando tudo isso, poderíamos usar como nossa primeira tentativa, algo assim: % egrep –i '\< http://[-a-z0-9_.:]+/[-a-z0-9_:@&?=+,.!/~*%$]*\.html?\>’
Ampliando a Base
23
De novo, visto que tomamos liberdades e relaxamos quanto ao que iremos combinar, podemos muito bem combinar algo tal como ‘http://. . . . /foo.html’, o que certamente não é um URL válido. Estamos preocupados com isso? Tudo depende do que você está tentando fazer. Para a varredura do meu arquivo de e-mail, de fato não importa se eu tiver algumas combinações falsas. Com os diabos, possivelmente eu poderia ficar com uma coisa tão simples quanto: ... % egrep –i '\
'arquivos
Como aprenderemos ao nos aprofundar mais em como moldar uma expressão, saber os dados que você buscará é uma questão importante para encontrar o equilíbrio entre complexidade e perfeição. Voltaremos a este exemplo, em mais detalhes, no próximo capítulo.
Uma tag HTML Com uma ferramenta como egrep, não parece especialmente normal ou útil apenas combinar linhas com tags HTML. No entanto, explorar uma expressão regular que combina exatamente tags HTML pode ser proveitoso, em particular quando nos aprofundarmos em ferramentas mais avançadas, no próximo capítulo. Vendo situações simples, como ‘’ e ‘
’, poderíamos pensar em experimentar <.*>. Essa abordagem simplista é um primeiro pensamento frequente, mas com certeza não é o certo. Converter <.*> para o inglês, significa “combinar um ‘<’ seguido por muito de qualquer coisa que possa ser combinado, seguido por ‘>’”. Bem, quando dito desta forma, não se trataria de uma surpresa poder combinar com mais do que apenas uma tag, tal como a porção marcada de ‘this
short example’. Isso poderia ter sido um pouco surpreendente, porém, ainda estamos no primeiro capítulo e o nosso conhecimento, a esta altura, é apenas superficial. Mostro este exemplo aqui para destacar que as expressões regulares não são um assunto difícil, mas que elas podem ser ardilosas se você não entendê-las verdadeiramente. No decorrer dos próximos capítulos, veremos todos os detalhes exigidos para entender e solucionar este problema.
Hora do dia: tal como “9:17 am” ou “12:30 pm” A combinação de uma hora pode ser feita em níveis variáveis de rigidez. Algo como [0-9]?[0-9]:[0-9][0-9]•(am|pm)
envolve ambas, 9:17•am e 12:30•pm, mas também permite algo sem sentido, como 99:99•pm. Olhando a hora, percebemos que se ela tiver um número de dois dígitos, o primeiro dígito deve ser o um. Mas, 1?[0-9] ainda permite uma hora de 19 (e também uma hora de 9), portanto, talvez fosse melhor quebrar a parte da hora em duas possibilidades: 1[012] para horas de dois dígitos e [1-9] para horas de um único dígito. O resultado é (1[012] | [1-9]). A parte dos minutos é mais fácil. O primeiro dígito deve ser [0-5]. Para o segundo, podemos ficar com o atual [0-9]. Ao juntar tudo, isso dá (1[012] | [1-9]):[0-5][0-9]•(am | pm). Usando a mesma lógica, você pode estender isso para lidar com o horário de 24 horas, com horas de 0 a 23? Como um desafio, possibilite um zero à frente, pelo menos até 09:59. Experimente montar a sua solução e depois vire a página para conferir com a minha.
24
Capítulo 1: Introdução a Expressões Regulares
Nomenclatura de Expressão Regular Regex Como você deve ter imaginado, usar a frase completa “expressão regular” pode ficar um pouco cansativo, especialmente escrevendo. Em vez disso, geralmente eu uso “regex”. Isso sai direto da língua (e rima com “FedEX”, com um som duro de g, como “regular” e não um suave, como em “Regina”) e é receptivo a uma série de usos, como “quando você regex ...”, “principiantes em regex” e até mesmo “regexificação”.† Eu uso a frase “motor de regex” para fazer referência à parte de um programa que, de fato, faz o trabalho de executar uma tentativa de combinação.
Combinando Quando digo que uma regex “combina” com uma string, de fato, quero dizer que ela combina em uma string. Tecnicamente, a regex a não combina com cat, mas combina em um cat. Não é uma coisa que as pessoas podem confundir, mas ainda vale à pena mencionar.
Metacaractere Se um caractere é um metacaractere (ou “meta string” – eu uso as palavras alternadamente) depende de exatamente onde a regex é usada. Por exemplo, * é um metacaractere, mas, só quando não está dentro de uma classe de caractere e quando não escapado. “Escapado” significa que ele tem uma barra invertida à sua frente – em geral. A estrela está escapada em \* mas não em \\* (onde a primeira barra invertida promove a fuga da segunda), ainda que a estrela “tenha uma barra invertida à sua frente” nos dois exemplos. Dependendo do sabor da regex, existem diversas situações em que determinados caracteres são e não são metacaracteres. O Capítulo 3 aborda isto em mais detalhes.
Sabor Conforme sugeri, diferentes ferramentas usam as expressões regulares para muitas coisas diferentes e o conjunto de metacaracteres e outros recursos que cada um a podem diferir. Vejamos novamente os limites de palavra como um exemplo. Algumas versões de egrep am a notação \<...\> que vimos. No entanto, algumas não am início de palavra e final de palavra separado, mas um metacaractere que captura tudo \b (que ainda não vimos – veremos no próximo capítulo). Outras ainda am ambos e muitas outras não am nenhum. Eu uso o termo “sabor” para descrever a soma total de todas essas pequenas decisões de implementação. Na analogia de idioma, é o mesmo que um dialeto de um orador individual. Superficialmente, esse conceito refere-se a quais metacaracteres são e não são ados, porém, há muito mais nele. Mesmo se dois programas arem \<...\>, eles podem discordar sobre exatamente o que podem fazer, e não considerar ser uma palavra. Essa preocupação é importante quando você usa a ferramenta.
†Você também poderia cruzar com “regexp”, decididamente de péssima aparência. Não sei como alguém pode pronunciar isso, mas aqueles com ceceio [N. da T. - que pronunciam as palavras com a ponta da língua apoiada nos dentes] podem achar um pouco mais fácil.
25
Ampliando a Base
28
Chapter 1: Introduction to Regular Expressions
Ampliando a Regex de Tempo para Lidar com Extending the Time 24Regex Horas to noHandle Relógio a 24-Hour Clock Answeràtopergunta the question on page �Resposta da página 23.26. There ar e various solutions, we can usepodemos similar logic as before. This time, Existem diversas soluções, mas,but como antes, usar lógica semelhante. Desta 00 athr ough I’ll separaremos break the task into three groups: oneum forpara the morning vez, a tarefa em três grupos: a manhã (hours (horas 00 09, com o 09, na with thesendo leading zero being optional), onediurno for the daytime 10para zero frente opcional), um para o horário (horas 10 a (hours 19) e um ough 19),20 and oneIsso for pode the evening (hours 20 ough 23). This can be 0?[0-9] a thr noite (horas a 23). ser apresentado de thr uma forma bem direta: in a .pretty straightforward way: ! 0?[0-9]<1[0-9]<2[0-3]". | render 1[0-9] |ed2[0-3]
[01]?[0-9] NaActually, verdade,we é possível combinar as first duastwo alternativas, resultando no mais curto can combine the alternatives, resulting in the shorter | ![2[0-3] . Você precisaria pensar um pouco nisso paraabout se convencer realmen01]?[0-9]<2[0-3]" . You might need to think it a bit se to elas, convince te,yourself combinarão exatamente mesmoexactly texto, the massame elas otext, fazem. figura seguir that they’ll reallyomatch butAthey do.a The fig-pode ajudar e também abordagem. grupos sombreados ure below mightexibe help,uma andoutra it shows anotherOs approach as well. Therepresentam shaded números podemnumbers ser combinados uma única groups que represent that can por be matched byalternativa. a single alternative.
[01]?[0-9]|2[0-3]
[01]?[4-9]|[012]?[0-3]
00 01 02 03 04 05 06 07 08 09
0 1 2 3 4 5 6 7 8 9
00 01 02 03 04 05 06 07 08 09
00 01 02 03 04 05 06 07 08 09
10 11 12 13 14 15 16 17 18 19
10 11 12 13 14 15 16 17 18 19
20 21 22 23
20 21 22 23
Não confundabut “sabor” commuch “ferramenta”. Assim como ifduas podem falar o mesmo ed, there’s more to it. Even twopessoas programs both dialeto, dois programas totalmente diferentes podem ar o mesmo sabor de regex. Da ! \<˙˙˙\>", they might disagree on exactly what they do and don’t consider to be a mesma forma, dois programas com o mesmo nome (e criados para executar a mesma tareword. This concern is important when you use the tool. fa), com frequência têm sabores ligeiramente diferentes (e às vezes, nem tão ligeiramente). Dentre os diversos programas chamados existe uma sabores Don’t confuse “flavor” with “tool.” Justde asegrep, two people can ampla speak variedade the same de dialect, regex ados. two completely different programs can exactly the same regex flavor. Also, two samesignificativo name (andoferecido built to pela do the same task) often No final da programs década de with 1990, the o sabor linguagem de programação era largamente reconhecido pelo seu poder e, logo, outras linguagens estavam havePerl slightly (and sometimes not-so-slightly) different flavors. Among the various oferecendo expressões emofPerl (muitas atéed. mesmo reconhecendo programs called egr ep, regulares there is a inspiradas wide variety regex flavors a força de inspiração, rotulando-se como “compatíveis com Perl”). As que adotaram inIn thePHP, late Python, 1990s, the particularly expressive flavorFramework offered bydathe Perl programcluem muitos pacotes regex Java, .NET Microsoft, Tcl e uma mingde language wasC,widely recognized foralgumas. its power, andassim, soon todas other são languages série bibliotecas mencionando apenas Ainda diferentes em importantes. disso, as expressões(many regulares de acknowledging Perl estão evoluindo weraspectos e offering Perl-inspirAlém ed regular expressions even the e crescendo (e agora, vezes, em resposta aos avanços notados em outras inspirational sourceàs by labeling themselves “Perl-compatible”). The ferramentas). adopters Como sempre, a visão geral a sepackages, tornar mais variada e .confusa. PHP, Python, manycontinua Java regex Microsoft’s NET Framework, Tcl, include and a variety of C libraries, to name a few. Yet, all are different in important Subexpressão respects. On top of this, Perl’s regular expressions themselves are evolving and O termo “subexpressão” refere-se, à parte de uma maior, aindaAsque growing (sometimes, now, in simplesmente, response to advances seen expressão with other tools). geralmente se refira a alguma parte de uma expressão entre parênteses ou, a uma alternativa always, the overall landscape continues to become more varied and confusing. de |. Com ^(Subject|Date):•, por exemplo, Subject|Date é referenciada como uma subexpressão. Neste contexto, cada uma das alternativas, Subject e Date também são referidas como subexpressões. Tecnicamente, entretanto, S é uma subexpressão, assim como u,b e j ...
The term “subexpr simply refers to simply part of refers a larger although it Theession” term “subexpr ession” to expression, part of a larger expression, alth often refers to some of to an some expression parentheses, or to an alternative oftenpart refers part ofwithin an expression within parentheses, or to an alt of !;". For example, ˆ( Subject;Date ", the ! Subject;Date usually of !;." with For !example, with ! ˆ(): Subject;Date ): ", the " ! Sisubject;Date " is 26 Capítulo 1: Introdução a Expressões Regulares referred to as a subexpression. that, the alternatives ubject " and ! Date " are " and ! D referred to as aWithin subexpression. Within that, ! Sthe alternatives ! Subject each referred to each as subexpressions well. But technically, ! S " is technically, a subexpression, referred to as as subexpressions as well. But ! S " is a subexp as is ! u" , and ! b" , and ! j" , . . . as is ! u" , and ! b" , and ! j" , . . . Algo como 1-6 não é considerado uma subexpressão de H[1-6]•*, visto que ‘1-6’ faz parte de uma Something “unidade” inquebrável, classe de caracteres. Mas, H, [1-6] e •* são+ ",todas ! H[1-6] such Something as 1-6 aisn’t considered a subexpression since the such as 1-6 isn’t considered aofsubexpression of ! H[1-6] + ", s subexpressões de H[1-6]•*. ‘1-6’ is part of an unbreakable “unit,” the character class. ! H ", ![1-6]" ‘1-6 ’ is part of an unbreakable “unit,” the But, character class., and But, ! ! H+"," ![1-6]", Diferente de osarquantificadores de +adição e ponto de interroarealternância, all subexpressions ! H[1-6] (estrela, + ". of ! Hsinal e all of subexpressions [1-6] ". gação) sempre funcionam com a menor subexpressão imediatamente precedente. Assim, Unlike (star,ouquantifiers plus, question mark) with the work alternation, (star, plus, and always question mark) always , o alternation, + regula Unlike o quantifiers s não o mis is. and É claro que, quando o quework precede com mis+pell smallest immediately-preceding subexpression. This is why with ! m is+pell , " the smallest immediately-preceding subexpression. is why with +! mis+pel imediatamente um quantificador é uma subexpressão entre parênteses, todaThis a subexpresgover ns uma the ! s"unidade , not the(independente when what immediately precedes a gover ns! mis" the or ! s", ! is" not. Of the !m is " or ! is ." Of course, when what immediately pre são é aceita como decourse, quão complexa for). quantifier is a parenthesized entire subexpr matter quantifier is asubexpression, parenthesized the subexpression, theession entire(no subexpr ession (no Caractere how complex) ishow taken as one unit. complex) is taken as one unit.
A palavra “caractere” pode ser um termo carregado em computação. O caractere que Characteré simplesmente Characteruma questão de interpretação. Um byte com deterum byte representa minado valor tem aquele mesmo valor“character” contexto que pelo você possathat The word “character” can beema qualquer loadedcan term computing. The character The word be in a loaded termqual in computing. Thea characte considerá-lo, mas qual caractere representa qual valor depende da codificação na qual byte represents is merely a matter of interpretation. A byte with such-and-such byte represents is merely a matter of interpretation. A byte withasuch-and ele é visto.value Como exemplo concreto, dois bytes com valores deto64consider e 53 it, hasum that same in any context you decimais might wish valuevalue has that same valueininwhich any context in which you might wish to con representam os caracteres “@” e “5”, respectivamente, se considerados na codificação but which character that value repr esents depends the depends encodingon in the which it’s but which character that value repron esents encoding in w ASCII [American National Standard Code For Information Interchange, embora por outro viewed. As a concrete example, two bytes with decimal values 64 and 53 repre-64 and 5 viewed. As a concrete example, two bytes with decimal values lado sejam completamente diferentes, se considerados na codificação EBCDIC [Extended sentDecimal the characters “5”– respectively, if considered in ifthe ASCII encoding, sent“@” theand characters “@” and “5” respectively, considered Binary Coded Interchange Code Código estendido de caracteres decimais co-in the ASCII en yet on the other hand are completely differ ent if considered in the EBCDIC encodyet on the other hand are completely differ if considered in the EBCDIC dificados em binário para intercâmbio de informações] (se tiverem ument espaço e algum (they are space and are some kind of a control character). ing (they a space and some kind of a control character). tipo de uming caractere de acontrole).
thelado, thirdsehand, those two bytes are consider ed em inare one of the popular Ainda por On outro aqueles dois bytes forem considerados uma das codificações On ifthe third hand, if those two bytes consider ed in oneencodof the popula ings for Japanese characters, together they repr esent the single character a. Yet, ings for Japanese characters, together they repr esent the single character populares para caracteres em Japonês, juntos, eles representam um único caractere, to repr esent this same character in another of the Japanese encodings requir es two to repr esent this same character in another of the Japanese encodings requ . Mesmo assim, para representar esse mesmo caractere em uma outra codificação em completely differ ent bytes. Those two differ ent bytes, by the way, yield the two completely differdiferentes. ent bytes.AThose two differ the way, yield Japonês requer dois bytes completamente propósito, essesent doisbytes, bytesby difecharacters the popular but yield the one Korean characrentes possibilitam os“Àµ” dois in caracteres “Àµ” na codificação Latim-1, mas permitem characters “Àµ”Latin-1 in popular the encoding, popular Latin-1 encoding, but yield the one Korean † † ter k in ofter thekUnicode encodings. point isA this: how bytes are to be bytes a ,innas codificações deThe Unicode.† questão é esta: o único caractere emone Coreano one of the Unicode encodings. The point iscomo this: how interprser eted is a interpr matter éeted ofuma perspective (called an encoding), becodifisuccessful, os bytes devem interpretados (chamada de to uma isquestão a matterde ofperspectiva perspective (calledand an encoding), and to be su cação), e para serem sucedidos precisa garantir que aperspective suawith perspectiva esteja detaken you’ve gotbem to make sure that perspective agrees the agrees perspective you’ve gotvocê to your make sure that your with the perspectiv acordo com perspectiva assumida pela ferramenta em que estiver trabalhando. byathe tool you’re using. by the tool you’re using. Até recentemente, as ferramentas de processamento de texto tratavam, de maneira geral, seus dados como punhados de bytes ASCII, sem preocupação com a codificação que você poderia pretender. Entretanto, mais eencodings mais sistemas estão usando alguma forma † The definitive recentemente, book on multiple-byte is Ken Lunde’s CJKVis Information Processing, also † The definitive book on multiple-byte encodings Ken Lunde’s CJKVde Information Proce by dados O’Reilly. The CJKV for The Chinese, Japanese, ean,introdução and Vietnamese, which published by stands O’Reilly. CJKV stands for Kor Chinese, Japanese, Kor and are Vietnamese, Unicode parapublished processar internamente (o Capítulo 3 apresenta uma aoean, Unilanguages that tend to e multiple-byte encodings. Ken and encodings. Adobe kindly many of the that tend requir e multiple-byte Kenprovided and Adobe kindly provided m code ☞ 85). Em tais sistemas, selanguages orequir subsistema detoexpressão regular tiver sido adequadamente special fonts used in special this book. fonts used in this book. implantado, normalmente o usuário não presta muita atenção a esses aspectos. Esse é um grande “se”, razão pela qual o Capítulo 3 comenta esse aspecto em mais profundidade.
Melhorando no Status Quo Ao se aprofundar nelas, as expressões regulares não são difíceis. Porém, se você conversar com o usuário mediano de um programa ou linguagem que as a, provavelmente, 2 July 2006 02:01
2 July 2006 02:01
† O livro definitivo sobre codificações de múltiplos bytes é CJKV Information Processing de Ken Lunde, também publicado pela O’Reilly. O CJKV significa Chinês, Japonês, Coreano e Vietnamita, os idiomas que costumam exigir codificações de múltiplos bytes. Gentilmente, Ken e a Adobe forneceram muitas das fontes especiais usadas neste livro.
Ampliando a Base
27
encontrará alguém que as entende “um pouco”, mas que não se sente seguro o suficiente para, de fato, usá-las para qualquer coisa complexa ou com qualquer ferramenta, exceto aquelas usadas com mais frequência. Tradicionalmente, a documentação sobre expressão regular tende a ser limitada a uma descrição rápida e incompleta de um ou dois metacaracteres, seguidos por uma tabela do restante. Geralmente, os exemplos usam expressões regulares sem sentido, como a*((ab)*|b*) e texto como ‘a•xxx•ce•xxxxxx•ci•xxx•d’. Há também a tendência de ignorar completamente pontos sutis, porém, importantes, e geralmente, reivindicam que seu sabor é igual ao de outra ferramenta bem conhecida, esquecendo quase sempre de mencionar as exceções, onde invariavelmente, elas diferem. A posição de documentação sobre regex precisa de ajuda. Então, não quero dizer que este capítulo preenche a lacuna para todas as expressões regulares ou mesmo para as expressões regulares de egrep. Ao contrário, este capítulo apenas oferece a base sobre a qual o resto deste livro é construído. Ele pode ser ambicioso, mas espero que este livro preencha as lacunas para você. Recebi muitas respostas gratificantes para a primeira edição, e trabalhei muito duro para tornar esta ainda melhor, tanto em alcance quanto em profundidade. Talvez porque a documentação sobre expressão regular, tradicionalmente, tenha sido tão falha, eu sinto que preciso fazer um esforço extra para tornar as coisas especialmente claras. Pelo fato de querer garantir que você possa usar as expressões regulares ao máximo do potencial delas, desejo garantir que você realmente as entenda. Isso tanto é bom quanto é ruim. É bom porque você aprenderá como pensar em expressões regulares. Aprenderá com as diferenças e as peculiaridades, para observar quando confrontado com uma nova ferramenta, com um sabor diferente. Você saberá como se expressar, até mesmo com um fraco sabor de expressão regular despojada. Compreenderá o que torna uma expressão mais eficiente do que uma outra, e será capaz de contrabalançar o equilíbrio entre complexidade, eficácia e combinar os resultados. Quando diante de uma tarefa particularmente complexa, você saberá como abrir caminho através de uma expressão como o programa faria, construindo-a na medida em que prossegue. Em resumo, você ficará à vontade usando as expressões regulares ao máximo. A questão é que a curva de aprendizagem deste método pode ser bem acentuada, com três aspectos separados a atacar:
• Como são usadas as expressões regulares. A maioria dos programas usa as expressões regulares de algumas maneiras que elas são mais complexas do que egrep. Antes de podermos abordar em detalhes como escrever uma expressão realmente útil, é preciso ver as formas pelas quais as expressões regulares podem ser usadas. Começaremos no próximo capítulo.
• Recursos de expressão regular. Selecionar a ferramenta adequada para usar quando diante de um problema parece ser metade da batalha, portanto, não quero me limitar a usar apenas um utilitário neste livro. Diferentes programas, e geralmente, até diferentes versões do mesmo programa, oferecem recursos e metacaracteres diferentes. Precisamos pesquisar o campo antes de entrar nos detalhes de usá-las. Este é o assunto do Capítulo 3.
• Como as expressões regulares realmente trabalham. Antes de podermos aprender a partir de exemplos úteis (porém, com frequência, complexos), precisamos olhar “por trás” para entender exatamente como é conduzida uma busca de expressão regular. Como veremos, a ordem em que determinados metacaracteres são verificados pode ser
28
Capítulo 1: Introdução a Expressões Regulares
muito importante. Na verdade, motores de expressão regular podem ser implementados de maneiras diferentes, assim, às vezes, diferentes programas fazem coisas diversas com a mesma expressão. Examinamos esse rico assunto nos Capítulo 4, 5 e 6. O último ponto é o mais importante e o mais difícil de encaminhar. Infelizmente, a discussão às vezes é um pouco seca, com o leitor se atrapalhando um pouco até chegar à parte divertida – atacando problemas reais. No entanto, entender como realmente trabalha o motor de regex é importante para entender realmente. Você poderia argumentar que não quer aprender como um carro trabalha quando apenas deseja saber como dirigir. Mas, saber dirigir um carro é uma pobre analogia para aprender sobre as expressões regulares. O meu objetivo é ensiná-lo como solucionar problemas com as expressões regulares, e o que significa construir expressões regulares. A melhor analogia não é como dirigir um carro, mas como montar um. Antes de você poder montar um carro, precisa saber como ele trabalha. O Capítulo 2 oferece mais experiência de direção. O Capítulo 3 dá uma rápida olhada na história de direção e uma olhada detalhada no trabalho corporal do sabor de uma expressão. O Capítulo 4 observa toda a importância do motor de um sabor de regex. O Capítulo 5 mostra alguns exemplos ampliados, o Capítulo 6 mostra a você como sintonizar determinados tipos de motores, e os capítulos posteriores examinam algumas marcas e modelos específicos. Especialmente nos Capítulo 4, 5 e 6, gastaremos muito tempo em segundo plano, portanto, assegure-se de ter o seu macacão e trapos de oficina à mão.
Resumo A Tabela 1-3 resume os metacaracteres de egrep que vimos neste capítulo. Tabela 1-3: Resumo de Metacaracteres de Egrep Metacaractere . [ ] [^ ] \char
Itens a Combinar com um Único Caractere Combinações
ponto Combina com qualquer caractere classe de caractere Combina com qualquer caractere relacionado classe de caractere de ne- Combina com qualquer caractere não relacionado gação Quando char é um metacaractere, ou a combinação caractere de escape escapada não é, por outro lado, especial, combina com o char literal Itens Anexados para Fornecer “Contagem”: Os Quantificadores
? * + {min,max}
interrogação estrela adição faixa especificada†
Uma permitida, mas é opcional Qualquer quantidade permitida, mas todas são opcionais Pelo menos uma exigida; adicionais são opcionais Min exigida, max permitida
Itens que Combinam uma Posição ^ $ \< \>
circunflexo cifrão limite de palavra† limite de palavra†
Combina Combina Combina Combina
a a a a
posição posição posição posição
no no no no
início da linha final da linha início de uma palavra final de uma palavra
29
Noções Pessoais
Outros | ( ) \1, \2...
alternância parênteses referência anterior†
Combina qualquer expressão que ela separa Limita o escopo da alternância, oferece agrupamento às quantificadores e “captura” para referências anteriores Combina texto previamente combinado dentro do primeiro, segundo, etc., conjunto de parênteses. † não ado por todas as versões de egrep
Além disso, assegure-se de entender os seguintes pontos:
• Nem todos os programas de egrep são iguais. Os metacaracteres ados, assim como seus significados exatos, frequentemente são diferentes – veja a sua documentação local (☞ 20).
• Três motivos para usar os parênteses são a alternância restritiva (☞ 11), o agrupamento (☞ 14) e a captura (☞ 19).
• As classes de caracteres são especiais e têm seu próprio conjunto de metacaracteres, totalmente separados da linguagem “principal” de regex (☞ 10).
• A alternância e as classes de caracteres são fundamentalmente diferentes, fornecendo serviços não associados que parecem se sobrepor, apenas em uma situação limitada (☞ 11).
• Uma classe de caractere de negação ainda é uma “asserção positiva” – mesmo negada, uma classe de caractere precisa combinar com um caractere para ser bem sucedida. Pelo fato da listagem de caracteres a combinar ser de negados, o caractere combinado deve ser um daqueles não relacionados na classe (☞ 09).
• A útil opção –i desconsidera as letras maiúsculas durante uma combinação (☞ 11). • Existem três tipos de itens escapados: 1. A combinação de \ e um metacaractere é uma meta string para combinar com o caractere literal (por exemplo, \* combina com um asterisco literal). 2. A combinação de \ e não metacaracteres selecionados torna-se uma meta string com um significado definido por implementação (por exemplo, \< frequentemente significa “início de palavra”). 3. A combinação de \ e qualquer outro caractere padroniza simplesmente para combinar o caractere (isto é, a barra invertida é ignorada). Porém, lembre-se de que uma barra invertida dentro de uma classe de caractere não é especial em quase a totalidade das versões de egrep, assim, ela não oferece “serviços de escape” em tal situação.
• Itens controlados por um ponto de interrogação ou por uma estrela, na verdade, não precisam combinar quaisquer caracteres para “combinar com sucesso”. Eles são sempre bem sucedidos, mesmo se não combinarem com nada (☞ 15).
30
Capítulo 1: Introdução a Expressões Regulares
Noções Pessoais A tarefa de palavras duplicadas, no início deste capítulo, pode parecer desalentadora, contudo, as expressões regulares são tão poderosas que poderíamos solucionar muito do problema com uma ferramenta tão limitada quanto egrep, diretamente no primeiro capítulo. Eu gostaria de preencher este capítulo de exemplos brilhantes, mas, porque concentrei na base sólida para os últimos capítulos, receio que algumas pessoas completamente novas em expressões regulares possam ler este capítulo completo, com todos os avisos, atenções e regras, etc., e pensar “por que me preocupar?”. Certa vez, meus irmãos estavam ensinando alguns amigos a jogar schaffkopf, um jogo de cartas que está na minha família há gerações. Ele é muito mais excitante do que parece à primeira vista, mas tem uma linha de aprendizado bastante acentuada. Depois de aproximadamente meia hora, a minha cunhada Liz, normalmente um exemplo de paciência, ficou frustrada com as regras aparentemente complexas e perguntou “Não podemos simplesmente jogar canastra?” Sim, por fim todos acabaram jogando até bem tarde da noite, inclusive a Liz. Quando eles tinham sido capazes de ultraar a elevação do loop de aprendizagem, um gosto em primeira mão da excitação foi o bastante para conquistar a todos. Os meus irmãos sabiam que seria assim, mas levou algum tempo e trabalho para chegar ao ponto onde Liz e os outros, novos no jogo, pudessem apreciar em que estavam entrando. Pode demorar um pouco para ficar aclimatado com as expressões regulares, então, até que você consiga o verdadeiro gosto com a excitação de usá-las para solucionar os seus problemas, tudo pode parecer um pouco acadêmico demais. Se assim for, espero que você resista ao desejo de “jogar canastra”. Quando tiver compreendido a força que as expressões regulares oferecem, a pequena quantidade de trabalho gasto em aprendê-las parecerá, de fato, bem trivial.
5u362f