vendredi 17 septembre 2010

PostgreSQL (suite)

Dans le billet précédent nous avons créé une base de données PostgreSQL. Nous y avons ajouté et chargé les tables
frregions: table des régions
frdepartem: table des départements
frcp: table des code postaux et des localités correspondantes
Ces trois tables sont liées par des relations d'intégrités différentielles qui garantissent que chaque localité est dans un département et chaque département dans une région.
Après connexion à openoffice, on peut facilement sur base des tables frregions et frdepartem établir un rapport tel que celui-ci:
Openoffice permet notamment aussi la création de formulaires pour la visualisation où la saisie de données. Nous montrerons bientôt comment connecter openoffice à PostgreSQL. Mais auparavant, nous allons encore travailler dans un terminal interactif psql, afin notamment de créer une nouvelle table pour tester la saisie des données. En effet les 3 tables existantes sont destinées à ne pas être modifiées. Aussi voudrions-nous les rendre read only, même pour toto.
Le problème est que à l'origine (voir billet précédent), nous avons créé un toto tout puissant, quasiment l'égal de postgres, l'administrateur désigné de PostgeSQL. Nous devons d'abord lui enlever la qualité de superuser. Mais en outre, étant propriétaire-créateur, toto a tous les droits sur ces tables. On va donc procéder aux opérations suivantes:


Les commandes sont suffisamment explicites pour qu'on n'ait pas besoin d'en dire davantage.
Notons simplement toto a pu se retirer lui-même la qualité de superuser, mais il ne pourra pas faire machine arrière!

La table des ressources humaines.
Comme indiqué un peu plus haut, nous avons encore besoin d'une table supplémentaire.
Nous allons par exemple créer la table entrh (ressources humaines de l'entreprise ent) à partir du fichier Centrh:

CREATE TABLE entrh
(matricule SERIAL PRIMARY KEY,
 nom CHAR(50),
 adresse char(50),
 salaire money,
 code_postal char(5),
 cp_seq smallint,
 FOREIGN KEY (code_postal,cp_seq) REFERENCES frcp)
;

Un matricule sera créé automatiquement pour chaque personne figurant dans la table de manière à former une clef primaire et l'ensemble code_postal, cp_seq devra pointer obligatoirement vers une rangée de la table frcp (à moins d'avoir la valeur NULL).
Bien sûr, il s'agit d'une table toute simple qui bafoue d'ailleurs allègrement le principe consistant à ne pas mélanger les données signalétiques aux autres. Elle est uniquement  destinée à illustrer notre propos (au départ ce devait être seulement une table des adresses). Nous ne développons pas ici un véritable projet de gestion des ressources humaines (ce qui nécessiterait plusieurs dizaines de tables).
C'est parti:

Nous voudrions aussi que la série de matricules ne commence pas par 1:


Le salaire est une donnée confidentielle: attribuons le privilège select de manière telle que cette donnée ne soit pas accessible::

Le groupe des gestionnaires du personnel.
Il reste à créer le groupe des personnes qui va s'occuper de la gestion du personnel et à lui attribuer les droits qui conviennent sur la table entrh en tenant compte de ce que le matricule ne peut pas être mis à jour. Par contre, il faut un update sur la séquence entrh_matricule_seq afin de permettre la création des matricules:

Pour l'instant le groupe de gestion (gestionrh) ne comprend qu'une personne, titine qui a juste été créée pour l'occasion. Par la suite, on pourra facilement en ajouter d'autres (ou en retirer) avec la commande alter group:
ALTER GROUP groupname ADD USER username [, ... ]
ALTER GROUP groupname DROP USER username [, ... ]
Notons qu'il ne faut pas confondre l'instruction CREATE USER qui a créé titine, avec la commande createuser utilisée précédemment pour créer toto (voir billet précédent).

Tentative de connexion en tant que gestionnaire.
Afin de vérifier si les personnes du groupe gestionrh ont les droits suffisants, tentons de  connecter titine à la base de données toto avec la commande psql. La syntaxe de cette commande est:
psql [option...] [dbname [username]]
dbname et username sont par défaut égal au nom de l'utilisateur qui exécute la commande.
Donc si je ne suis pas titine, je dois préciser les deux paramètres:


Ça ne fonctionne pas. Essayons en activant le terminal interactif psql en tant que toto: on peut y modifier sa connexion avec la méta-commande \connect:


Nouvel échec. Comment procéder pour que toto puisse se connecter facilement en tant que titine? La solution se trouve dans le fichier de configuration pg_hba.conf.

Les fichiers pg_hba.conf et pg_ident.conf 
Le chemin vers les fichiers de configuration  de PostgreSQL, et notamment pg_hba.conf, dépend de la distribution.
Exemples de tels chemins:
/var/lib/pgsql/data/
/etc/postgresql/8.4/main/
Sur notre machine le fichier de configuration pg_hba.conf par défaut est le suivant:

# TYPE  DATABASE  USER        CIDR-ADDRESS          METHOD

# "local" is for Unix domain socket connections only
local   all       all                               ident
# IPv4 local connections:
host    all       all         127.0.0.1/32          md5
# IPv6 local connections:
host    all       all         ::1/128               md5

Nous y constatons la présence de deux méthodes d'authentification md5 (par mot de passe crypté) et ident. D'autres méthodes sont possibles.
La méthode d'authentification Ident fonctionne en prenant le nom de l'utilisateur du système d'exploitation et en s'en servant comme nom d'utilisateur PostgreSQL. L'authentification échouera si l'utilisateur PostgreSQL obtenu par Ident n'existe pas, ou encore s'il est différent du nom que l'on transmet en paramètre dans la commande de connexion. C'est le cas ici. Alors comment procéder pour résoudre notre problème? Nous pourrions modifier le fichier pg_hba.conf comme ceci:

# TYPE  DATABASE  USER        CIDR-ADDRESS          METHOD

# "local" is for Unix domain socket connections only
local   all       titine                            md5
local   all       all                               ident
# IPv4 local connections:
host    all       all         127.0.0.1/32          md5
# IPv6 local connections:
host    all       all         ::1/128               md5

Pour se connecter en tant que titine, il suffit de fournir son mot de passe. Il n'y a aucun lien établi avec l'utilisateur du système d'exploitation qui demande la connexion. titine peut même ne pas exister au niveau du système d'exploitation. Et le mot de passe alors? Attention, il s'agit d'un mot de passe défini dans PostgreSQL. Mais le but de la manoeuvre étant que toto puisse se connecter facilement en tant que titine, ce n'est pas vraiment la bonne solution.
Non, la solution est de passer par l'intermédiaire du fichier pg_ident.conf afin de définir un mappage entre les utilisateurs PostgreSQL et les utilisateurs du système d'exploitation. Modifions ce fichier pour qu'il contienne ceci:

# MAPNAME     SYSTEM-USERNAME    PG-USERNAME
map01         toto               titine
map01         titine             titine

La première ligne permet à toto de se connecter en tant que titine. Il faut aussi que titine puisse se connecter, d'où l'existence de la deuxième ligne.
Il s'agit maintenant d'établir un lien entre pg_hba.conf et ce fichier pg-ident.conf, comme ceci:
# TYPE  DATABASE  USER        CIDR-ADDRESS          METHOD

# "local" is for Unix domain socket connections only
local   all       titine                            ident map=map01
local   all       all                               ident
# IPv4 local connections:
host    all       all         127.0.0.1/32          md5
# IPv6 local connections:
host    all       all         ::1/128               md5


On pourrait dans la ligne ajoutée remplacer titine par @group01, où group01 est un fichier qui se trouve dans le même dossier que pg_hba.conf et contenant le mot titine. Attention si on ajoute à group01 d'autres utilisateurs sans modifier pg_ident.conf en conséquence, ils ne pourront plus se connecter.
D'autres méthodes d'authentification sont possibles:
trust: n'importe qui peut se connecter en utilisant n'importe quel nom d'utilisateur PostgreSQL.
reject: éventuellement utile pour rejeter certains postes clients.
et beaucoup d'autres encore....

Pour que les changements introduits soient pris en compte, il faut recharger le serveur:

# service postgresql reload

Insertion dans la table des ressources humaines
Nous avons préparé une instruction SQL d'insertion d'une rangée dans la table entrh. Celle-ci se trouve dans le fichier insert01:

insert into entrh
values (default, 'Gédéon Théodore','Avenue des oiseaux rares, 28','2456,3'::money,'02200',3)
;

Cette instruction doit être exécutée par titine puisque nous voulons vérifier que les membres du groupe gestionrh ont les droits qui conviennent pour pouvoir travailler. Le problème de connexion toto -> titine étant résolu, tout devrait bien se passer:

?? Pour la connexion c'est OK, mais pour le reste...En fait, toto, propriétaire de la table frcp droit récupérer son droit d'update sur cette table. Cela permet au système de tenir un lock sur la table frcp pendant la durée de la transaction d'insertion afin de garantir l'intégrité référentielle des données. Il ne faudrait pas que quelqu'un supprime (ou modifie) la rangée de frcp vers laquelle va pointer la nouvelle rangée insérée dans entrh. Il est donc illusoire de penser que l'on peut rendre la table frcp read only pour toto. Par contre pour titine, elle le reste bel et bien (heureusement). 
Procédons:
Vérifions le résultat:
Tiens, on n'avait pas dit que les matricules commençaient à 1005?
Oui, mais lors du premier essai, au moment de l'erreur sur frcp, la séquence qui sert pour les matricules avait déjà été mise à jour.
Or il n'y a jamais de retour en arrière (rollback) pour les séquences.

Bon, le contenu de ce billet est déjà bien assez consistant.
Nous avions annoncé que nous allions montrer comment se connecter avec openoffice.
Et si on en reparlait dans un prochain billet?

mercredi 1 septembre 2010

PostgreSQL (initiation)

PostgreSQL est une base de données moderne et puissante. Nous allons dans ce billet expliquer comment démarrer avec PostgreSQL et comment l'utiliser et ce de la manière la plus simple possible.

Installation et démarrage
En principe,  il suffit d'installer le paquet serveur (postgresql-server), le paquet client (postgresql) et les autres paquets indispensables  s'installeront alors automatiquement par le jeu des dépendances. Le nom de ces paquets peut évidemment varier suivant la distribution. Par exemple chez Ubuntu, le paquet serveur est postgresql et le paquet client postgresql-client. Une fois l'installation terminée, la première opération consiste à initialiser la base de donnée. Ensuite seulement le service peut être démarré.


Il se peut que le nom du service ne soit pas exactement postgresql. Dans ce cas, taper service post + tabulation afin de trouver le bon nom.
Chez Ubuntu, l'option initdb n'est pas reconnue. On passe alors directement à la deuxième commande.

Création du premier utilisateur PostgreSQL (autre que l'administrateur)
L'installation du paquet postgresql-server a ajouté au système d'exploitation un nouvel utilisateur: postgres, administrateur PostgreSQL. C'est le seul utilisateur connu de notre système de bases de données. Il faut donc lui ajouter d'autres utilisateurs. On est obligé de devenir postgres avant de procéder. Pour ce faire, il faut être dans un terminal avec les droits du root (obtenu avec sudo -s chez Ubuntu), ou sinon le sytème demande le mot de passe de postgres.


On a ajouté à PostgreSQL un nouvel utilisateur: toto. celui-ci pourra créer des bases de données (option -d) et ajouter des utilisateurs tout en étant super-utilisateur (option -a). toto doit être un utilisateur du système d'exploitation (ce n'est pas absolument obligatoire, mais c'est une question de facilité). Le mieux est même que toto soit notre nom d'utilisateur.
Il suffit alors d'entrer deux fois la commande exit, et voilà, on est redevenu toto.

Création de la première base de données
Reste à créer la base de données et à s'y connecter:


A des fins d'organisation, un dosssier SQL a d'abord été créé puis on s'y est rendu. La commande createdb exécutée sans arguments a créé la base de données toto. On s'y connecte avec la commande psql lancée aussi sans arguments. Quoi de plus simple. Bien sûr, il est possible faire plus compliqué.

Une première table: les régions de France
Imaginons que nous disposions par exemple d'un fichier FRregions (placé dans le dossier SQL) des différentes régions de  France:
01    Alsace
02    Aquitaine
03    Auvergne
04    Basse-Normandie
05    Bourgogne
06    Bretagne
07    Centre
08    Champagne-Ardenne
09    Corse
10    Franche-Comté
 ..................

que nous voudrions  importer dans la base de données toto.
Il nous faut en premier  créer une table où placer les données que l'on va importer. Inscrivons les instructions sql de création de la table:
CREATE TABLE frregions
(region_id CHAR(2) PRIMARY KEY,
 region_nom CHAR(30));

dans petit fichier texte appelé Cfrregions que nous mettons dans le dossier SQL (comme tous les autres fichiers dont il sera question dans la suite).
Bien sûr on pourrait taper ces commandes directement dans  le terminal psql, mais on les a mis dans un fichier. Alors, comment procéder? Il faut savoir que dans un terminal psql, on peut aussi taper des méta-commandes comme par exemple:
toto=# \i FICHIER          
qui exécute les commande du fichier.
Et bien, procédons:
Nous sommes avertis qu'un index a été automatiquement créé. Dans la foulée, nous avons importé le fichier FRregions avec la méta-commande \copy. Pour que cette commande fonctionne sans aucun autre argument, les champs du fichier ne doivent pas être séparés par des espaces mais par un caractère de tabulation qui est le séparateur par défaut.
Vérifions que tout c'est bien passé:
Et voilà le résultat:
(On s'est limité aux régions métropolitaines.)

Pour tester l'intégrité référentielle: la table des départements
Maintenant nous allons créer de la même façon une table des départements.
Le fichier des données:
01    Ain    22
02    Aisne    19
03    Allier    03
04    Alpes-de-Haute-Provence    21
05    Hautes-Alpes    21
06    Alpes-Maritimes    21
07    Ardèche    22
08    Ardennes    08
........
De nouveau, il n'y a pas de blancs mais des caractères de tabulation. Les numéros en fin de lignes se rapportent à la région.
Les instructions SQL de création de la table:
CREATE TABLE frdepartem
(departem_id CHAR(2) PRIMARY KEY,
 departem_nom CHAR(30),
 code_region CHAR(2) REFERENCES frregions);

La dernière ligne de ces instructions implique que les données du champ code_region devront absolument faire référence à une région existante. La contrainte imposée dans cette dernière ligne est appelée une contrainte d'intégrité référentielle. En clair, un département doit se trouver dans une région.
Après en avoir terminé avec la création de cette nouvelle table, essayons de muter le département des Ardennes vers la région 23:
Et si maintenant, j'essaye de détruire une rangée de la table frregions:
PostgreSQL garantit l'intégité référentielle de nos données.

Affichage html des données
Considérons la requête suivante:
select departem_id as "Numéro", departem_nom as "Département" , region_nom as "Région"
from frdepartem, frregions
where code_region = region_id
;
contenue dans le fichier sdepreg. Le terminal psql permet l'envoi du résultat de cette requête dans un fichier html:
\H bascule le format de sortie vers l'html.
Comme cela n'a pas tellement se sens d'afficher de l'html dans le terminal psql, on redirige la sortie vers le fichier depreg.html avec la commande
\ o depreg.html
Et voici ce que cela donne:

Ensuite:
\o supprime la redirection
\H revient au format de sortie précédent

Et pourquoi pas une table des codes postaux?
Notre base de données ne serait pas complète sans une table avec les codes postaux. Pour charger cette future table, nous disposons d'un fichier FRcp0 dont voici un extrait:


.......
25720   Aveney  25
25720   Beure   25
25720   Arguel  25
25720   Larnod  25
25720   Avanne-Aveney   25
25720   Pugey   25
25750   Aibre   25
25750   Le Vernoy       25
25750   Semondans       25
25750   Désandans       25
25750   Arcey   25
.......

On aimerait associer au code postal un numéro séquentiel de manière à pouvoir différencier les différentes entrées relatives à un même code postal.
Nous allons utiliser awk après avoir trié le fichier:

[toto@rigel SQL]$ sort FRcp0 | awk 'BEGIN {FS="\t"}; x==$1 {i++}; !(x==$1) {i=1}; {x=$1};{print $1"\t"i"\t"$2"\t"$3}' > FRcp1
[toto@rigel SQL]$

Dans la clause BEGIN, nous demandons que le séparateur de champ soit un caractère de tabulation. Ensuite si x est égal au champ 1 (le code postal), nous incrémentons i de 1 (évidemment pour la première ligne ce n'est pas le cas). Si x n'est pas égal au champ 1, nous donnons à i la valeur 1, puis seulement nous mettons le champ 1 dans x.
On obtient ceci:

.....
25720   1       Arguel  25
25720   2       Avanne-Aveney   25
25720   3       Aveney  25
25720   4       Beure   25
25720   5       Larnod  25
25720   6       Pugey   25
25750   1       Aibre   25
25750   2       Arcey   25
25750   3       Désandans       25
25750   4       Le Vernoy       25
25750   5       Semondans       25
.....

Et voici le fichier Cfrcp avec les instructions SQL de création de la table:

CREATE TABLE frcp
(code_postal CHAR(5),
 cp_seq smallint,
 localite_nom CHAR(50),
 code_departem CHAR(2) REFERENCES frdepartem,
 PRIMARY KEY (code_postal,cp_seq));

L'instruction PRIMARY KEY permettra la création d'une relation d'intégrité référentielle entre une future table d'adresses et la table des codes postaux. Cette table des adresses ne contiendra pas le nom de la localité, mais contiendra pour chaque rangée un code postal associé à un numéro de séquence qui devra désigner impérativement une rangée de la table des codes postaux.
Allons-y pour la création de cette table des codes postaux:


Et oui! Tout ne se passe pas toujours comme prévu. De nouveau une contrainte d'intégrité référentielle qui est violée.
L'erreur est clairement expliquée: le fichier FRcp1 comprend des lignes avec un code département qui est un blanc. Or il n'existe pas dans la table frdepartem de département sans code.
Le résultat de l'instruction select * from frcp montre qu'aucune rangée n'a été importée (en tout cas elles ne sont pas accessibles). Un comportement envisageable aurait été que seules les rangées en erreur n'aient pas été importées. Il reste à identifier les lignes fautives:

[toto@rigel SQL]$ awk '/[^0-9AB]$/{print NR}' Frcp1
5491
5493
5494
5495
9452
13868

à corriger le fichier et à recommencer l'import.
L'identification se base sur le fait que les lignes fautives ne se terminent pas par un chiffre, ni par un A, ni par un B (pour les deux départements corses).

PostgreSQL et les expressions régulières
PostgreSQL supporte l'emploi d'expressions régulières dans les instructions sql. Pourquoi ne pas essayer avec cette nouvelle table des codes postaux:

Ici nous avons recherché les noms de localité avec "bar" en sixième position. La première colonne reprend la sélection correspondant à l'expression régulière. Ensuite nous avons demandé l'affichage du nom complet de la localité ainsi que de son code postal.
Ce query est tellement extraordinaire que nous avons donné instruction de l'écrire dans le fichier sregexp. On pourra donc le ré-exécuter quand on veut avec la méta-commande: 
toto=# \i sregexp.

Pour conclure
Il existe des solutions graphiques pour accéder à une base de données PostgreSQL.
Dans un prochain billet nous verrons notamment comment procéder avec openoffice.

mercredi 25 août 2010

Expressions régulières

Une expression régulière est une suite de caractères ordinaires et de méta-caractères, utilisée en tant que critère de sélection.
Un caractère ordinaire correspond uniquement à lui-même alors qu'un méta-caractère possède une signification spéciale.

Le méta-caractère « . »
Comme premier exemple de méta-caractère, considérons le point « . ». Celui-ci correspond à n'importe quel caractère (sauf le caractère de fin de ligne).
Illustrons notre propos à l'aide d'un petit fichier col que nous créons:
$ echo -e 'calepin\nclavier\ncolis\n1 col\ncol blanc\nc\047est cool.\nalcool\ncoool!' | tee col
calepin
clavier
colis
1 col
col blanc
c'est cool.
alcool
coool!
L'utilisation de l'option -e avec la commande echo fait que \n est interprété en tant que caractère « fin de ligne » et \047 en tant qu'apostrophe. Le flux généré par echo n'est pas envoyé directement vers la sortie standard (l'écran), mais il transite d'abord par le programme tee qui en fait une copie dirigée vers col. On peut ainsi visionner directement le contenu du fichier nouvellement créé.
Nous allons maintenant utiliser grep pour afficher les éléments (lignes) du fichier qui contiennent une sélection correspondant à l'expression régulière indiquée:
$ grep 'c.l.' col
calepin
colis
col blanc
Le premier « . » de l'expression régulière « c.l. » implique l'existence d'un caractère entre le c et le l, ce qui élimine « clavier ». Le deuxième « . » impose que le l doit être suivi par un caractère, ce qui élimine «1 col ».

Le méta-caractère « * »
Il correspond à un ensemble de 0 à n occurrences du caractère précédant:
$ grep 'co*l' col
clavier
colis
1 col
col blanc
c'est cool.
alcool
coool!
clavier est conservé (0 occurrence de o entre c et l).

Le méta-caractère « + »
Il correspond à un ensemble de 1 à n occurrences du caractère précédant, ce qui devrait cette fois éliminer « clavier »:
$ grep 'co+l' col
Aucun retour! Explication: « + » est par défaut un caractère ordinaire. Mais il existe un opérateur, l'opérateur d'échappement, qui appliqué à « + » lui permet d'échapper à sa (misérable) condition de caractère ordinaire, et le transforme en méta-caractère. Ce caractère est représenté par « \ » (backslash).
$ grep 'co\+l' col
colis
1 col
col blanc
c'est cool.
alcool
coool!
Par rapport à l'output précédant, clavier est bien éliminé.

Le méta-caractère « ? »
Celui-ci correspond à au plus 1 occurrence du caractère précédant.
Attention « ? » doit lui être aussi sublimé en méta-caractère par la magie de l'opérateur d'échappement:
$ grep 'co\?l' col
clavier
colis
1 col
col blanc
Le caractère précédant un méta-caractère peut être lui-même un méta-caractère:
$ grep 'c.\?l' col
calepin
clavier
colis
1 col
col blanc

Le méta-caractère correspondant à l'opérateur OR
Il est obtenu par sublimation de « | »:
$ grep  'bla\|cla' col
clavier
col blanc

Les méta-caractères accolades
Il servent à donner des précisions sur le nombre d'occurrences désirées du caractère précédant. Attention: ils ne sont pas reconnus d'emblée comme méta-caractère (à moi l'opérateur d'échappement). Voici quelques exemples d'utilisation. On désire:
exactement 3 o:
$ grep 'co\{3\}l' col
coool!
entre 1 et 2 o:
$ grep 'co\{1,2\}l' col
colis
1 col
col blanc
c'est cool.
alcool
au minimum 2 o:
$ grep 'co\{2,\}l' col
c'est cool.
alcool
coool!
au maximum 2 o:
$ grep 'co\{,2\}l' col
grep: décompte de répétition mal formé
Un bug ? En tout cas avec sed utilisé en tant qu'émulateur de grep, ça fonctionne:
$ sed -n '/co\{,2\}l/p' col
clavier
colis
1 col
col blanc
c'est cool.
alcool
Avec grep, il faut mettre un 0 devant la virgule.

Les classes de caractères
Une classe de caractères est définie à l'aide d'une liste entre crochets reprenant tous les caractères à inclure. Cette liste peut elle même comprendre une plage de caractères: deux caractères séparés par un tiret.
Exemples:
[0123456789] ou [0-9]: tous les chiffres
$ grep '[0-9]' col
1 col
[prv]: les lettres p, r, v
[a-z]: toutes les lettres minuscules
[a-zA-Z]: toutes les lettres minuscules et majuscules.
$ grep '[a-zA-Z]\{4\}n' col
calepin
La commande sélectionne des chaînes comprenant un « n » précédé de 4 lettres. « col blanc » n'est pas retenu car l'espace compris entre « col » et « blanc » n'est pas une lettre.
Si le premier caractère qui suit le crochet d'ouverture est un « ^ », la classe ainsi définie comprend tous les caractères et toutes les plages de caractères qui ne sont pas repris dans liste.
Exemples:
[^0-9]: pas les chiffres
[^a-z]: pas les lettres minuscules.
$ grep –-color '[^aco]l' col
col blanc
On a sélectionné les chaînes contenant un « l » qui n'est précédé ni d'un « a », ni d'un « c » et ni d'un « o ». L'option –-color permet la mise en évidence de la sélection correspondant à l'expression régulière: « ol » n'est pas sélectionné mais bien « bl ».
$ grep –color 'l[^a-z]' col
col blanc
c'est cool.
coool!
On retient ici les éléments du fichier col qui contiennent un « l » suivi d'un caractère qui n'est pas une lettre minuscule. «1 col » (le 4ième élément qui vient après « colis ») n'est pas retenu car il n'est suivi par rien. Nous avons surligné la sélection en vert pâle pour montrer que l'espace entre col et blanc en fait partie.

Les classes de caractères prédéfinies
On peut trouver à l'intérieur des crochets de définition d'une classe, des classes de caractères prédéfinies. Leur nom est explicite:
[:digit:], [:alpha:], [:alnum:], [:space:],[:punct], [:cntrl:], [:lower:], [:upper] etc
Exemple:
$ grep '[[:punct:]]' col
c'est cool.
Coool!
Attention, il y a deux paires de crochets:
les crochets qui servent à définir une classe de caractère (en rouge ci-dessous)
les crochets qui font partie du nom d'une classe prédéfinie (en vert ci-dessous)
$ grep '[[:digit:]p.]' col
calepin
1 col
c'est cool.
Donc ici, on a recherché les chaînes avec un chiffre, un « p », ou un point.
Tiens, le point n'est pas un méta-caractère qui représente n'importe quel caractère?
Non, car dans une classe de caractères, il n'y a pas de méta-caractère.

La classe [:space:]
Intéressons-nous à la classe [:space:]. Il s'agit d'une classe de caractère qui comprend non seulement les espaces, mais aussi les caractères de tabulation et les retours chariot:
$ grep 'col[[:space:]]' col
col blanc
Fournissons à grep un fichier col adapté à windows, dont les lignes contiennent un caractère de contrôle \r (retour chariot) précédant le caractère \n (fin de ligne). Comme \r appartient à la classe [:space:], col de « 1 col » est maintenant lui aussi suivi d'un caractère appartenant à cette classe:
$ sed 's/$/\r/' col | grep 'col[[:space:]]'
1 col
col blanc
(La transformation par sed d'un fichier unix en fichier dos est expliquée dans un autre billet.)
Considérons maintenant l'output suivant:
$ echo -e 'abc\t1\ndef     2'
abc     1
def     2
(\t est interprété en tant que caractère de contrôle tabulation.)
Filtrons maintenant cet output en imposant comme critère de sélection: une lettre suivie d'un espace (en fait un caractère de la classe [:space:]) et un chiffre:
$ echo -e 'abc\t1\ndef     2' | grep --color '[a-z][[:space:]][0-9]'
abc     1
La deuxième ligne est éliminée car entre la lettre f et le chiffre 2, il y a plusieurs espaces. Par contre le \t est bien considéré comme un seul espace.

La classe [:cntrl]
Considérons la classe [:cntrl:], classe des caractères de contrôle. La question qui se pose est:  le caractère « fin de ligne » (\n) en fait-il partie?
Faisons un test.
$ echo -e 'abc\t1\ndef    2' | grep '[[:cntrl:]]1'
abc     1
Le caractère de contrôle avant le 1 (\t) est bien détecté, mais pas le caractère fin de ligne (\n) qui suit le 1:
$ echo -e 'abc\t1\ndef    2' | grep '1[[:cntrl:]]'
Concluant? Non, car grep (comme sed et awk) enlève le caractère \n à la lecture de chaque ligne et le remet lors du renvoi de la ligne vers la sortie standard.
On doit procéder autrement pour montrer que ce caractère de contrôle « fin de ligne » appartient bien à la classe [:cntrl:]. Pour ce faire nous allons utiliser sed:
$ sed -n '/cl/{N;s/[[:cntrl:]]//;p}' col
claviercolis
Lorsqu'une ligne contenant « cl » est rencontrée, les actions suivantes (entre les accolades et séparées par des point-virgules) sont effectuées à l'intérieur de la commande sed:
N ajoute le caractère \n puis la ligne « colis » à la ligne « clavier » qui vient d'être lue
s remplace le premier caractère de contrôle rencontré par rien, donc le supprime
p imprime le tout.
« clavier » et « colis » sont maintenant collés: cela montre que \n appartient à [:cntrl:].
D'ailleurs si on enlève l'action s le caractère fin de ligne qui sépare « clavier » et « colis » est toujours présent:
$ sed -n '/cl/{N;p}' col
clavier
colis

Classes de caractères prédéfinies hors crochets
Certaines classes de caractères prédéfinies sont désignées par un caractère promu au grade de méta-caractère par la grâce de l'opérateur d'échappement:
\W ensemble des caractères qui ne peuvent pas se trouver dans un mot:
$ grep 'co\+l\W' col
col blanc
c'est cool.
coool!
(Les caractères de ponctuation ne font pas partie des mots.)
\w: ensemble des caractères qui peuvent se trouver dans un mot. Attention: \w n'est pas l'équivalent de [a-zA-Z0-9] car on doit y ajouter l'underscore. Mettons cette différence en évidence.
$ echo -e 'colis\ncol_dur' | grep --color 'col\w'
colis  
col_dur
Les deux candidats passent le test.
$ echo -e 'colis\ncol_dur' | grep --color 'col[a-zA-Z0-9]'
colis
col_dur est recalé!
Tiens, si on ajoutait recalé au fichier:
$ echo 'recalé' >> col
Aie! Un caractère accentué. Les problèmes vont commencer.

Le problème des caractères accentués.
Vérifions si \w contient les caractères accentués:
$ grep --color 'cal\w' col
calepin
Et recalé?
Il doit y avoir un problème avec la localisation puisque \w ne comprend pas les caractères accentués.
Pourtant le terminal est en UTF-8:
$ printf '\xc3\xa9\n'
é
tout comme le fichier:
$ grep recal col | od -An -tx1
72 65 63 61 6c c3 a9 0a                          
et locale est en accord:
$ locale | grep CTYPE
LC_CTYPE="fr_FR.UTF-8"
Essayons avec [a-z]:
$ grep --color 'cal[a-z]' col
calepin
recalé
où avec [:alpha:]:
$ grep 'cal[[:alpha:]]' col
calepin
recalé
Là, ça fonctionne. Il semble que ce soit \w qui pose problème.
sed lui donne le bon résultat avec \w:
$ sed -n '/cal\w/p' col
calepin
recalé
mais pas awk:
$ awk '/cal\w/' col
calepin
Essayons ceci:
$ export LC_CTYPE=fr_FR
(alors que nous sommes en UTF-8, mais bon...).
Bingo avec \w, ça fonctionne pour grep et awk:
$ grep 'cal\w' col
calepin
recalé
$ awk '/cal\w/' col
calepin
recalé
ce qui est quand même assez étonnant.
Par contre maintenant avec [a-z], ça foire (logiquement) pour grep et sed, mais pas pour awk!
Avec [:alpha:], c'est toujours bon.
Tout ça mérite un petit tableau pour plus de clarté:


KO (sur fond rouge): le caractère accentué (ici le « é ») n'est pas reconnu comme faisant partie de la classe indiquée en tête de colonne.
OK: tout va bien!
Sur fond vert: la valeur de LC_CTYPE normale (héritée de la localisation).
Sur fond orange: valeur de LC_CTYPE ne correspondant à la localisation car on est effectivement en UTF-8.
Notons que ce tableau peut varier d'une distribution à l'autre. Bref, il convient d'être toujours attentif quant on doit traiter avec des caractères accentués.

Les bordures de mot.
Recherchons les « col » qui sont en bordure de mot, plus précisément à la fin d'un mot. N'avons-nous pas déjà la solution avec:
$ grep 'col\W' col
col blanc
Non, car le 4ième élément, «1 col », n'est pas retenu. La bonne méthode consiste à utiliser le méta-caractère \b, bordure de mot:
$ grep 'col\b' col
1 col
col blanc
On peut aussi utiliser le méta-caractère bordure de fin de mot \>:
$ grep 'col\>' col
1 col
col blanc
De même, pour une sélection en début de mot, \W ne convient pas:
$ grep '\Wco\{2,\}' col
c'est cool.
mais bien \b:
$ grep '\bco\{2,\}' col
c'est cool.
coool!
ou le méta-caractère bordure de début de mot \< :
$ grep '\
c'est cool.
coool!
(alcool est éliminé car cool n'est pas en début de ce mot).
Précisons comme on pouvait s'y attendre que la signification de \B, est NON bordure de mot:
$ grep 'col\B' col
colis
$ grep '\Bco\{2,\}' col
alcool
Les candidats éliminés précédemment sont cette fois retenus.
Notons que tous ces méta-caractères qui détectent la bordure des mots, souffrent de la même maladie que \w (si elle existe):
$ grep 'c.l\>' col
1 col
col blanc
recalé
Comme « é » n'est pas reconnu en tant que caractère constitutif d'un mot, recalé n'est pas un mot, mais bien recal et cal colle à la bordure de ce mot. Donc cal est sélectionné ce qui entraîne l'affichage de la ligne « recalé »

Les méta-caractères d'ancrage ^ et $
 ^ impose un ancrage en début de chaîne.
$ grep '^.\{2\}a' col
clavier
recherche des chaînes avec un « a » en troisième position. En effet, il doit y avoir 2 caractères entre le début de la chaîne et a.
Sans ancrage on obtient ceci:
$ grep -–color '.\{2\}a' col
clavier
col blanc
reca
$ impose un ancrage en fin de chaîne.
$ grep 'a.\{2\}$' col
col blanc
calé
recherche des chaînes avec un a en antépénultième position.
Sans ancrage:
$ grep -–color 'a.\{2\}' col
calepin
clavier
col blanc
alc
ool
recalé

Les parenthèses de mémorisation.
L'élément de la sélection correspondant à la partie de l'expression régulière comprise dans la n ième paire de méta-parenthèses est conservé dans /n pour un usage ultérieur.
echo 'Pistolets 2:,87x2 = 1,74 euros' | sed  's/\([^0-9]\)\(,[0-9]\{2\}[^0-9]\)/\10\2/'
Pistolets 2:0,87x2 = 1,74 euros
La sélection (sur fond vert pâle) est divisée en deux parties de couleurs différentes: la première correspond à l'expression régulière limitée par la paire de méta-parenthèses bleues (un non chiffre), la deuxième à l'expression régulière entourée par les méta‑parenthèses rouges (virgule + 2 chiffres suivi d'un non chiffre). La chaine de remplacement \10\2 est donc constituée de la partie 1 de la sélection + « 0 » + la partie 2.

Les expressions régulières étendues.
La multiplication des opérateurs d'échappement peut nuire à la lisibilité d'une expression régulière. Dans ce cas, il est préférable d'utiliser une expression régulière étendue. La seule différence avec les expressions régulières de base est que les caractère ?, +, {, } |, (, et ) sont par défaut des méta-caractères. On indique à grep d'utiliser des expressions régulières étendues avec l'option -E. Pour sed, c'est l'option -r:
$ echo 'Pistolets 2:,87x2 = 1,74 euros' | sed -r 's/([^0-9])(,[0-9]{2}[^0-9])/\10\2/'
Pistolets 2:0,87x2 = 1,74 euros
$ grep -E 'bla|cla' col

clavier
col blanc
awk lui utilise d'office des expressions régulières étendues:
$ awk '/bla|cla/' col
clavier
col blanc

S'échapper vers l'ordinaire.
De nombreuses personnes rêvent d'échapper à l'ordinaire de leur vie afin de mener un jour une vie de milliardaire. Mais l'inverse existe aussi: de grandes vedettes rêvent d'échapper à la notoriété afin de pouvoir mener (en tout cas pour quelques jours) une petite vie tranquille et ordinaire. L'opérateur d'échappement permet les deux: transformer un caractère ordinaire en méta-caractère, mais il permet aussi à un méta-caractère de (re)devenir un caractère ordinaire:
$ grep '\.$' col
c'est cool.
(Le « . » est cette fois un caractère ordinaire.)
Le sens dans lequel agit l'opérateur d'échappement peut varier suivant que l'on a affaire à une  expression régulière étendue ou pas:
promotion de « + » au grade (envié) de méta-caractère:
$ echo -e 'co+l\ncol' | grep 'co\+l'
col
dégradation de « + » qui redevient un caractère ordinaire:
$ echo -e 'co+l\ncol' | grep -E 'co\+l'
co+l

Expressions régulières dans les traitements de texte
Certains traitement de textes supportent l'emploi d'expressions régulières dans la fonction « Rechercher ». C'est le cas d'openoffice.
Il faut d'abord cocher la cas adéquate:


Voici quelques exemples.
Recherche de 3 caractères maximum entre c et l:

La sélection doit terminer un mot:


La sélection doit se trouver en fin de ligne:


Comme le montrent les exemples, openoffice travaille avec des expressions régulières étendues.

Utilisation avec SQL
Les base de données modernes (MySQL, PostgreSQL...) permettent l'emploi d'expressions régulières dans les instructions SQL.