Expressions Regulars

De wikijoan
Salta a la navegació Salta a la cerca

Introducció

Acabes de veure la utilitat de les expressions regulars en el fitxer de configuració d'un proxy com el Squid. Altres vegades també hem trobat expressions regulars i hi hem passat de puntetes. Les expressions regulars (regex) serveixen per a reconeixement i detecció de cadenes (matching) i s'utilitzen sovint, per exemple en scripts bash (un exemple típic pot ser formatar la data amb el format que un vol). El primer exemple que pots provar en la consola és:

line='Today is 10/12/2010 and yesterday was 9/11/2010'
echo "$line" | sed -r 's#([0-9]{1,2})/([0-9]{1,2})/([0-9]{4})#\3-\2-\1#g'

Today is 2010-12-10 and yesterday was 2010-11-9

En aquest cas sed' és útil per transformar la cadena de text en una altra.

Teoria

Les expressions regulars (regex) estan formades per caràcters i metacaràcters. Els caràcters normals inclouen lletres (majúscules i minúscules) i números. Els metacaràcters tenen un significat especial, com es comenta més avall. En el cas més simple, una cadena com ara paper no conté metacaràcters, i coincideix amb paperot, 12paper45, però no amb PAPER.

Per veure la potència de les expressions regulars és molt important entrendre els metacaràcters:

  • . Matches any single character. For example the regular expression r.t would match the strings rat, rut, r t, but not root.
  • $ Matches the end of a line. For example, the regular expression weasel$ would match the end of the string "He's a weasel" but not the string "They are a bunch of weasels."
  • ^ Matches the beginning of a line. For example, the regular expression ^When in would match the beginning of the string "When in the course of human events" but would not match "What and When in the" .
  • *(asterisc) Matches zero or more occurences of the character immediately preceding. For example, the regular expression .* means match any number of any characters.
  • \ This is the quoting character, use it to treat the following character as an ordinary character. For example, \$ is used to match the dollar sign character ($) rather than the end of a line. Similarly, the expression \. is used to match the period character rather than any single character.
  • [ ], [c1-c2], [^c1-c2] Matches any one of the characters between the brackets. For example, the regular expression r[aou]t matches rat, rot, and rut, but not ret. Ranges of characters can specified by using a hyphen. For example, the regular expression [0-9] means match any digit. Multiple ranges can be specified as well. The regular expression [A-Za-z] means match any upper or lower case letter. To match any character except those in the range, the complement range, use the caret as the first character after the opening bracket. For example, the expression [^269A-Z] will match any characters except 2, 6, 9, and upper case letters.
  • \< \> Matches the beginning (\<) or end (\>) or a word. For example, \<the matches on "the" in the string "for the wise" but does not match "the" in "otherwise". NOTE: this metacharacter is not supported by all applications.
  • \( \) Treat the expression between \( and \) as a group. Also, saves the characters matched by the expression into temporary holding areas. Up to nine pattern matches can be saved in a single regular expression. They can be referenced as \1 through \9.
  • | Or two conditions together. For example (him|her) matches the line "it belongs to him" and matches the line "it belongs to her" but does not match the line "it belongs to them." NOTE: this metacharacter is not supported by all applications.
  • + Matches one or more occurences of the character or regular expression immediately preceding. For example, the regular expression 9+ matches 9, 99, 999. NOTE: this metacharacter is not supported by all applications.
  • ? Matches 0 or 1 occurence of the character or regular expression immediately preceding.NOTE: this metacharacter is not supported by all applications.
  • \{i\} \{i,j\} Match a specific number of instances or instances within a range of the preceding character. For example, the expression A[0-9]\{3\} will match "A" followed by exactly 3 digits. That is, it will match A123 but not A1234. The expression [0-9]\{4,6\} any sequence of 4, 5, or 6 digits. NOTE: this metacharacter is not supported by all applications.

Proves

Per començar a provar expressions regulars ho farem, per exemple, amb grep

GREP(1)                          User Commands                         GREP(1)

NAME
       grep, egrep, fgrep, rgrep - print lines matching a pattern

SYNOPSIS
       grep [OPTIONS] PATTERN [FILE...]
       grep [OPTIONS] [-e PATTERN | -f FILE] [FILE...]

DESCRIPTION
       grep  searches the named input FILEs (or standard input if no files are
       named, or if a single hyphen-minus (-) is given as file name) for lines
       containing  a  match to the given PATTERN.  By default, grep prints the
       matching lines.
...
REGULAR EXPRESSIONS

       A  regular  expression  is  a  pattern that describes a set of strings.
       Regular expressions are constructed analogously to  arithmetic  expres-
       sions, by using various operators to combine smaller expressions.
...

Les expressions regulars tenen algun comportament que depenen de l'eina amb què les avalues. Per tant, s'ha d'anar a la referència de l'eina (com ara grep, vi, awk, sed, perl,...) per tal de veure la compatibilitat (veure la taula de Regular Expressions Syntax Support a baix de tot de l'enllaç que se't proposa).

fitxer test.txt:

he is a rat
he is in a rut
the food is Rotten
I like root beer

El punt . (amb anglès period) representa qualsevol caràcter. Si volem trobar totes les línies que continguin r.t (una paraula de tres caràcters que comenci per r i acabi per t):

$ cat test.txt | grep r.t
he is a rat
he is in a rut

o bé

$ grep r.t test.txt
he is a rat
he is in a rut

Si volem que no sigui sensible a majúscules per a la r farem:

$ grep [Rr].t test.txt
he is a rat
he is in a rut
the food is Rotten

Per detectar una cadena al principi de la línia fem servir el ^ (circunflex o caret):

$ grep ^ĥe test.txt
he is a rat
he is in a rut

fixem-nos que no detecta the

L'ús del circumflex té un altre ús: quan utilitzem ^ com a primer caràcter dins dels brackets [] vol dir detectar qualsevol caràcter que no estigui en el rang. Per exemple, per detectar he però que no sigui the o she farem:

$ grep [^st]he test.txt

no troba el the (però tampoc troba he is a rat i he is in a rut perquè espera que hi hagi un caràcter abans del he).

[A-Za-z] detecta qualsevol lletra de l'alfabet, sigui majúscula o minúscula. L'expressió regular [A-Za-z][A-Za-z]* detecta una lletra seguida de 0 (cap) o més lletres.

$ grep [o]* test.txt -> mostra les coincidències de ''cap o alguna o''
 he is a rat
he is in a rut
the food is Rotten
I like root beer
$ grep [o][o]* test.txt -> mostra les coincidències de ''cap o alguna o, després d'una o''
the food is Rotten
I like root beer

Per especificar el número d'ocurrències que es detecten podem utilitzar les claus. Per exemple, per detectar les instàncies de 100 i 1000, però no 10 i 10000 farem: 10\{2,3\}.

$ echo 10 100 1000 10000 | grep "10\{2,3\}"

resultat: 10 100 1000 10000

Fixa't bé amb la diferència amb el següent cas:

$ echo 10 100 1000 10000 | grep "[10]\{2,3\}"

10 100 1000 10000

$ echo 10 100 1000 | grep "[[:digit:]]\{2,3\}"

resultat: 10 100 1000

Un exemple aclaridor: (\{i\} Match a specific number of instances or instances within a range of the preceding character)

$ echo 10000 | grep "[10]\{1\}" -> els números sempre es repeteixen ells mateixos

10000

$ echo 10000 | grep "[10]\{2\}" -> detecta l'expressió que és un número que al seu darrere hi ha un altre número (0 o 1)

10000

$ echo 10000 | grep "[10]\{3\}" -> detecta el número que es repeteix amb 1 o 0 tres vegades

10000

$ echo 10000 | grep "[10]\{4\}" -> detecta el número que es repeteix amb 1 o 0 quatre vegades

10000

$ echo 10000 | grep "[10]\{5\}" -> detecta el número que es repeteix amb 1 o 0 cinc vegades

10000

$ echo 10000 | grep "[10]\{6\}"

100000

Com pots comprovar, les Expressions Regulars poden ser un bon embolic... però realment són molt útils i s'utilitzen molt.

Corolari (el que potser utilitzaràs més)

Moltes vegades es planteja trobar dins d'una col.lecció de fitxers totes les vegades que surt una determinada paraula. Imagina't que comets habitualment l'error ortogràfic lampara (en comptes de làmpara). Imaginem que tenim dos fitxers dins una carpeta (però podrien ser 1000 fitxers amb una estructura de carpetes i subcarpetes).

Crea la carpeta lampara i fica-hi aquests dos fitxers:

fitxer1.txt

la lampara d'Aladí s'ha fos
no trobo la lampara del Pere

fitxer2.txt

he anat a la botiga
a comprar una lampara i una taula
$ find /home/joan/lampara/ -type f -print | xargs grep -i lampara | more
/home/joan/lampara/fitxer1.txt:la lampara d'Aladí s'ha fos
/home/joan/lampara/fitxer1.txt:no trobo la lampara del Pere
/home/joan/lampara/fitxer2.txt:a comprar una lampara i una taula

És una combinació de les comandes find, xargs i grep:

  • find: search for files in a directory hierarchy
  • xargs: build and execute command lines from standard input
  • grep: grep, egrep, fgrep, rgrep - print lines matching a pattern (en aquest cas el pattern, l'expressió regular, és senzillament lampara)

Així et serà fàcil subsanar tots els errors.

Hi ha alguna manera de reemplaçar totes les ocurrències? (que no sigui amb un editor gràfic tipus gedit o notepad++, sinó amb CLI). vi/vim ho pot fer:

:%s/lampara/làmpara/g

on %s és la comanda de substitució del vi

Com fer-ho automàticament per a tots els fitxers? (TBD)

Altres referències