lundi 18 octobre 2010

stderr, stdout et redirections

En vue de réaliser quelques expériences, nous créons un dossier test puis nous nous y rendons:

[toto@rigel ~]$ mkdir test
[toto@rigel ~]$ cd test
[toto@rigel test]$

Définissons l'alias ll pour le cas où il n'existerait pas:

[toto@rigel test]$ alias ll='ls -l'
[toto@rigel test]$

Commençons les expériences:

[toto@rigel test]$ touch toto 
[toto@rigel test]$ ll toto aime titine
ls: ne peut accéder aime: Aucun fichier ou dossier de ce type
ls: ne peut accéder titine: Aucun fichier ou dossier de ce type
-rw-rw-r--. 1 toto toto 0 oct 17 11:11 toto
[toto@rigel test]$

Nous créons le fichier toto et demandons via la commande ll des informations sur les fichiers toto, aime et titine. L'ordre d'arrivée à l'écran des communications envoyées par ll est assez interpelant. On s'attendrait à trouver quelque chose comme:

-rw-rw-r--. 1 toto toto 0 oct 17 11:11 toto
ls: ne peut accéder aime: Aucun fichier ou dossier de ce type
ls: ne peut accéder titine: Aucun fichier ou dossier de ce type

ce qui correspond à l'ordre des arguments de la commande.
L'expérience montre que les messages arrivent sur l'écran par deux canaux différents dont l'un, celui par lequel passent les messages d'erreur, est cette fois prioritaire. Mais attention, ce n'est pas toujours le cas et si l'ordre naturel avait été respecté, on n'aurait rien pu en déduire.

Interceptons le flux sortant de ll par un pipe afin de le faire traiter par le programme tr. Le traitement consistera (par exemple) à remplacer tous les "t" par des "b".

[toto@rigel test]$ ll toto aime titine | tr t b
ls: ne peut accéder aime: Aucun fichier ou dossier de ce type
ls: ne peut accéder titine: Aucun fichier ou dossier de ce type
-rw-rw-r--. 1 bobo bobo 0 ocb 17 11:11 bobo
[toto@rigel test]$

Nous constatons que les messages d'erreur ne sont pas passés par la moulinette tr. Il y a bien deux canaux. Le canal par lequel transite l'output normal est la sortie standard (stdout). L'autre canal, pour les messages d'erreur est l'erreur standard (stderr). Le pipe à intercepté uniquement stdout.
Ce qu'il advient de l'output normal après l'exécution d'une commande est géré par le descripteur 1. A ce descripteur correspond un canal, le canal 1 (stdout). De même, ce qu'il advient des messages d'erreur est géré par le descripteur 2 auquel correspond le canal 2 (stderr).
On peut rediriger stderr vers stdout, comme ceci:

[toto@rigel test]$ ll toto aime titine 2>&1 | tr t b
ls: ne peub accéder aime: Aucun fichier ou dossier de ce bype
ls: ne peub accéder bibine: Aucun fichier ou dossier de ce bype
-rw-rw-r--. 1 bobo bobo 0 ocb 17 11:11 bobo
[toto@rigel test]$

L'instruction 2>&1 affecte au descripteur 2 le canal correspondant au descripteur 1.
En clair, stderr est redirigé vers stdout. Grâce au traitement par tr, on constate que cette fois, contrairement à ce qui se passait dans le cas précédent, la totalité des messages transite par le pipe.
Maintenant, nous aimerions trouver comment procéder pour que seul stderr transite par le pipe.
Tout d'abord, montrons que dans le cas de plusieurs redirections successives , l'ordre dans lequel elles interviennent est primordial:

[toto@rigel test]$ ll toto aime titine > info 2>&1
[toto@rigel test]$ cat info
ls: ne peut accéder aime: Aucun fichier ou dossier de ce type
ls: ne peut accéder titine: Aucun fichier ou dossier de ce type
-rw-rw-r--. 1 toto toto 0 oct 17 11:11 toto
[toto@rigel test]$

ce qui correspond au schéma:
L'instruction > info est mise pour 1> info (1 est le descripteur de fichier par défaut).
Elle affecte au descripteur 1 le fichier info: stdout est redirigé vers info.
Ensuite 2>&1 affecte au descripteur 2 le canal correspondant au descripteur 1. Ce canal a déjà été changé. Ce n'est plus le canal 1 initial. En fait de canal, il s'agit maintenant du fichier info:
stderr est à son tour redirigé vers le fichier info.
Rien n'arrive sur l'écran.
Permutons les deux redirections:

[toto@rigel test]$ ll toto aime titine 2>&1 > info
ls: ne peut accéder aime: Aucun fichier ou dossier de ce type
ls: ne peut accéder titine: Aucun fichier ou dossier de ce type
[toto@rigel test]$ cat info
-rw-rw-r--. 1 toto toto 0 oct 17 11:11 toto
[toto@rigel test]$

Le schéma correspondant est celui- ci:

2>&1 affecte au descripteur 2 le canal 1 qui est le canal initial correspondant au descripteur 1. Celui-ci n'a pas encore changé: stderr est redirigé vers ce canal 1. Ensuite > info (c'est-à-dire 1> info) affecte au descripteur 1 le fichier info: stdout est redirigé vers info. En fait la première redirection ne sert absolument à rien car de toute façon, stderr serait arrivé à l'écran. Oui mais cette fois stderr arrive sur l'écran via le canal 1, ce qui nous donne l'occasion de le faire transiter par tr, comme représenté sur ce schéma:




Essayons la formule:

[toto@rigel test]$ ll toto aime titine 2>&1 > info | tr t b
ls: ne peub accéder aime: Aucun fichier ou dossier de ce bype
ls: ne peub accéder bibine: Aucun fichier ou dossier de ce bype
[toto@rigel test]$ cat info
-rw-rw-r--. 1 toto toto 0 oct 17 11:11 toto
[toto@rigel test]$

Bingo, ça fonctionne! Cependant, on voudrait une solution sans redirection vers un fichier, on voudrait que tout arrive à l'écran, non seulement stderr mais aussi stdout. Pour cela nous avons besoin d'un nouveau descripteur de fichier: 3.

[toto@rigel test]$ echo toto >&3
-bash: 3: Mauvais descripteur de fichier
[toto@rigel test]$

Et bien oui: on veut rediriger stdout vers le canal 3, c'est-à-dire affecter au descripteur 1, le canal correspondant au descripteur 3. Mais ce descripteur n'existe pas, pas encore.

[toto@rigel test]$ exec 3>&1
[toto@rigel test]$

Maintenant, il existe:

[toto@rigel test]$ echo toto >&3
toto
[toto@rigel test]$

Remarquons que

[toto@rigel test]$ echo toto >&3 | tr t r
toto
[toto@rigel test]$

Alors que

[toto@rigel test]$ echo toto | tr t r
roro
[toto@rigel test]$

Nous avons notre solution:

[toto@rigel test]$ ll toto aime titine 2>&1 1>&3 | tr t b
ls: ne peub accéder aime: Aucun fichier ou dossier de ce bype
ls: ne peub accéder bibine: Aucun fichier ou dossier de ce bype
-rw-rw-r--. 1 toto toto 0 oct 17 11:11 toto

qui correspond au schéma:


jeudi 14 octobre 2010

Formulaires dans OOo Base

Dans le présent billet, nous allons nous intéresser à la création (et à l'utilisation) de formulaires dans OpenOffice.org Base (OOo Base). Pour ce faire, nous allons travailler avec la base de données postgreSQL créée par toto (voir billets précédents). On peut créer des formulaires qui servent uniquement à encoder de nouveaux enregistrements et d'autres qui servent à la consultation et à la mise à jour. Mais comment procéder pour afficher à l'écran l'enregistrement voulu? En effet: soit une table avec la clef primaire "matricule". Si nous saisissons un numéro de matricule dans le champ "matricule" du formulaire, OOo Base réagit comme si nous voulions mettre à jour le le matricule de l'enregistrement courant:
La  mise à jour n'a pas eu lieu car nous nous sommes connecté en tant qu'utilisateur aux droits limités, ce qui est toujours préférable. Le mieux est d'ailleurs en guise de protection supplémentaire de mettre le champ matricule du formulaire en "lecture seule".
Mais comment procéder alors? On peut ajouter au formulaire ce qui s'appelle un "contrôle de table":

Il suffit alors de cliquer sur le numéro de dossier souhaité pour l'amener instantanément à l'écran. Cela fonctionne très bien tant que le nombre d'enregistrements n'est pas trop important. Dans le cas contraire, faire défiler un grand nombre de matricules jusqu'à trouver celui qui nous intéresse peut s'avérer fastidieux. Dans le formulaire que nous allons construire nous utiliserons une technique basée sur l'utilisation d'une table intermédiaire, à une seule rangée qui sera constamment mise à jour.
D'autre part, il faut savoir que les différents éléments d'un formulaire sont regroupés dans un à plusieurs forms, chaque form étant lié à une et une seule table. L'assistant de création de formulaire permet seulement la création de formulaires à deux forms maximum. Nous verrons comment nous affranchir de cette limite.
Nous montrerons comment utiliser ListBox, ComboBox, Bouton radio etc...
Mais pour pouvoir utiliser au maximum les possibilités offertes, nous devons d'abord encore effectuer quelques aménagements dans notre base de données.
Encore quelques aménagements.
Tout d'abord, travaillant dans un terminal psql, créons la table services:
CREATE TABLE services
(service_id CHAR(2) PRIMARY KEY,
 dénomination CHAR(25))
;
Nous y mettons les données suivantes:
00    Direction
01    Secrétariat
02    Gestion du personnel
03    Relations publiques
04    Production
05    Recherches
06    Ventes 
figurant dans le fichier services par utilisation de la méta-commande:
toto=> \copy services from services
Reste à accorder le privilège qui convient:
toto=> grant select on services to public;
La table services va nous permettre de montrer l'utilité du contrôle "zone de liste" (ListBox).
Ajoutons également quelques colonnes à la table entrh:
ALTER TABLE entrh
ADD COLUMN service CHAR(2)
REFERENCES services
;
ALTER TABLE entrh
ADD COLUMN sexe CHAR(1)
CHECK (sexe IN ('M','F'))
;
ALTER TABLE entrh
ADD COLUMN naissance DATE
;
Les privilèges sur cette table ayant été accordés sur base des colonnes, nous devons les mettre à jour:
GRANT SELECT (service,sexe,naissance)
ON entrh TO public
;
GRANT UPDATE (service,sexe,naissance)
ON entrh TO gestionrh
;
Rappelons que le groupe gestionrh contient les utilisateurs habilités à traiter les données (et que ceux-ci n'ont pas le droit de modifier le matricule).
On peut aussi recréer la table entrh et accorder les privilèges adéquats en exécutant les commandes:
DROP TABLE entrh
;
CREATE TABLE entrh
(matricule SERIAL PRIMARY KEY,
 nom CHAR(50),
 adresse CHAR(50),
 code_postal CHAR(5),
 cp_seq SMALLINT,
 naissance DATE,
 sexe CHAR(1) CHECK (sexe IN ('M','F')),
 service CHAR(2) REFERENCES services,
 salaire numeric(13,2),
 FOREIGN KEY (code_postal,cp_seq) REFERENCES frcp)
;
ALTER SEQUENCE entrh_matricule_seq RESTART 1005
;
GRANT SELECT (matricule, nom, adresse, code_postal, cp_seq, naissance, sexe, service)
ON TABLE entrh TO PUBLIC
;
CREATE ROLE gestionrh
;
GRANT SELECT,INSERT ON entrh TO gestionrh
;
GRANT UPDATE (nom, adresse, code_postal, cp_seq, naissance, sexe, service, salaire)
ON entrh TO gestionrh
;
GRANT UPDATE ON SEQUENCE entrh_matricule_seq TO gestionrh
;              
Si ces commandes sont contenues dans le fichier Centrh-nd, il suffit de faire:
toto=> \i Centrh-nd
Bien sûr, les autres tables référencées doivent avoir été créées suivant les instructions qui figurent dans ce billet et dans le billet intitulé 'PostgreSQL initiation" .
Si le groupe gestionrh existe déjà, l'exécution des commandes du fichier produira une erreur sans conséquences. S'il vient juste d'être créé, il sera évidemment vide et il faudra y placer un où plusieurs gestionnaires.
Par exemple:
toto=> ALTER GROUP gestionrh ADD user titine
Pour travailler dans open office, il est préférable de se connecter à notre base de données en utilisant le nom d'un utilisateur du groupe gestionrh. De la sorte les changements que nous pourrions apporter aux données seront encadrés.

La requête Rcpdepreg
Dans le formulaire que nous allons créer, nous aimerions y voir figurer en clair, le nom de la localité et pourquoi pas aussi le nom du département et même de la région. Pour ne pas avoir à multiplier les sous-formulaires, nous allons créer, en mode ébauche, une requête que nous utiliserons dans le formulaire.
On ajoute les tables frcp, frdepartem,  frreg. Les liens s'établissent automatiquement:

Reste à choisir les colonnes figurant dans la requête:

et à enregistrer la requête sous le nom Rcpdepreg.
Voilà, nous sommes prêts à entamer la création de notre formulaire de gestion de la table entrh.


Le formulaire entrh
Utilisons l'assistant de création de formulaires
A l'étape N°1 choisissons la table entrh et sélectionnons tous les champs disponibles pour être inclus dans le formulaire.

A l'étape N°2 cochons "Ajouter un sous-formulaire"

A l'étape N° 3 sélectionnons la requête Rcpdepreg. Nous ne prenons pas code_postal et cp_seq en tant que champ du formulaire: inutile qu'ils apparaissent deux fois.
A l'étape N°4: l'assistant ne permet pas l'établissement des jointures car code_postal et cp_seq n'ont pas été repris dans l'étape précédente:
Nous établirons la jointure plus tard.
A l'étape N°5, notre choix s'effectue comme montré ci-dessous:

A l'étape N°6  cochons "Ne pas autoriser la suppression des données existantes".

Ensuite nous gardons les options par défaut. Nous créons le formulaire en demandant de le modifier avant toute utilisation:

En priorité, il convient d'établir un lien entre les deux parties du formulaire, ce qui n'a pas été possible dans l'assistant. Ouvrons le navigateur de formulaires. On le trouve dans la barre d'outils horizontale en bas de la fenêtre de travail:
Cliquons droit sur SubForm et par la voie du menu contextuel qui surgit, affichons ses propriétés. Dans l'onglet "Données", nous avons alors la possibilité d'établir le lien voulu en appuyant sur le bouton aux 3 petits points qui se trouve à droite de l'item "Établir un lien depuis":
SubForm correspond à des données qui ne sont pas censées être modifiées: code postaux, noms des localités etc. Aussi profitons que nous avons à l'écran les propriétés de SubForm, pour indiquer que nous ne voulons pas d'ajouts et pas de modifications (pas de suppression est déjà mentionné):
Affichons maintenant depuis le navigateur de formulaire les propriétés de MainForm afin de pouvoir trier les enregistrements suivant le matricule. On procède comme précédemment, appui sur le bouton aux trois petits points:

Occupons nous maintenant des différents champs.
Cliquons sur la zone de saisie salaire, CTRL étant enfoncé, de manière à la sélectionner sans son étiquette. Dans le menu contextuel obtenu par clic droit, choisissons "Remplacer par", puis "Champ monétaire":
Ensuite dans la fenêtre "Propriétés" (obtenue via menu contextuel puis "Contrôle"), demandons pour avoir un séparateur de millier:
 Nous avons même la possibilité de placer le symbole monétaire (€) devant le nombre.
(Dans le billet précédent, nous avons indiqué une autre méthode pour formater le champ salaire)
En ce qui concerne la donnée sexe, nous allons utiliser le contrôle "bouton radio".
On le trouve avec les autres contrôles dans la barre d'outils verticale de gauche:

Supprimons la zone de saisie sexe et plaçons en dessous de son étiquette deux boutons radio côte à côte. Nous les configurons via le panneau de leurs propriétés: étiquette et valeur référentielle: M pour un, F pour l'autre:


Supprimons également la zone de saisie service et mettons à la place une zone de liste (ListBox). Pourquoi ne pas utiliser "Remplacer par" ? Et bien parce que dans ce cas l'assistant ne s'ouvre pas. Ici l'assistant s'ouvre dès que nous avons terminé de dimensionner notre zone de liste. Voici en images les différentes étapes:


 
En ce qui concerne le champ matricule, n'oublions pas de le mettre en lecture seule car nous avons prévu une génération automatique des matricules:

Plaçons sur le formulaire un bouton appartenant à SubForm: étiquette "Actualiser localité" et
action "Rafraîchir le formulaire". Pour vérifier l'appartenance du bouton, on le sélectionne, le navigateur de formulaire étant ouvert. SubForm devrait alors se déployer:

Si ce n'est pas le cas, il suffit de repérer l'icône "PushButton" dans le navigateur de formulaires et de faire un "glisser-déposer" sur l'icône SubForm".
Ce bouton est utile pour rafraîchir l'écran en cas de modification du code postal et (ou) de la séquence qui suit.
Nous disposons et redimensionnons les différents champs du formulaire à notre guise, modifions les étiquettes.
Il reste à adapter l'ordre d'activation.
On trouve l'outil qui convient dans le bas de la fenêtre de travail:



Tri automatique devrait donner un résultat qui convient:

Lors de chaque appui sur "Enter", le curseur sautera d'une zone à l'autre suivant l'ordre indiqué ici. Donc si on commence par naissance, le curseur ira ensuite sur salaire, puis service (c'est la ListBox), puis nom etc. A la fin du cycle, les données sont enregistrées puis on passe à l'enregistrement suivant.
Sauvegardons notre travail avant d'ouvrir le formulaire pour utilisation. On devrait pouvoir obtenir quelque chose qui ressemble à ceci:

C'est une première étape, mais le travail est loin d'être fini. Nous aimerions que l'encodeur puisse disposer d'une liste donnant les diverses possibilités de localités pour un code postal donné. Pour ce faire nous allons ajouter un nouveau sous-formulaire à notre formulaire. Rappelons que l'assistant permet de créer des formulaires avec seulement un sous-formulaire.
Dans le navigateur de formulaire, cliquons droit sur SubForm, ensuite dans le menu contextuel nous choisissons Nouveau, puis Formulaire:
Nous renommons le sous-formulaire créé en CpForm. Cliquant droit dessus nous arrivons à la fenêtre Propriétés. Le contenu de CpForm viendra de la table frcp. Le lien avec SubForm sera basé sur code_postal. Et bien sûr, pas d'ajouts, modifications, suppressions:
Nous imposons également un tri sur cp_seq:
Cliquer sur "Autres contrôles" dans la barre d'outils verticale, ouvre une nouvelle boite d'outils où se trouve le contrôle "Contrôle de table":

Déployons ce contrôle dans la partie inférieure du formulaire. Un assistant s'ouvre dont le rôle sera très bref: il permet juste de choisir les colonnes à afficher:

Ajustons les propriétés du contrôle: pas de Barre de navigation et pas de Marqueur d'enregistrement:
Cliquer droit sur l'entête de la colonne et choisir colonne dans le menu contextuel, permet d'afficher les propriétés de la colonne: 
Pour la colonne cp_seq, on veillera à mettre le nombre de décimales à zéro:
Plaçons également dans la partie MainForm du formulaire, un autre contrôle de table avec une seule colonne: matricule. On ajuste ses propriétés comme pour le contrôle de table précédent. Utilisant le formulaire après une sauvegarde des modifications, on obtient quelque chose qui pourrait ressembler à:
Mais comme déjà indiqué au début de ce billet, la sélection des enregistrements par un contrôle de table tel que celui qui figure ici (en haut à droite), n'est pas vraiment un bon choix.
Aussi nous allons garder ce formulaire uniquement pour encoder de nouveaux enregistrements en modifiant les propriétés de MainForm:

"N'ajouter que des données" est basculé sur Oui.
De la sorte, seuls seront visibles les enregistrements encodés pendant la session en cours.

La table "Trucs et astuces" selection.
En vue de sélectionner les enregistrements à afficher nous allons utiliser une table intermédiaire que nous appellerons selection. Cette table ne contiendra jamais qu'une seule rangée avec deux colonnes: une colonne id, et une colonne qui contiendra le matricule du dossier sélectionné.. Procédons à la création de la table selection dans un terminal psql et exécutons ensuite les instructions SQL qui conviennent:


On peut se poser la question: pourquoi une clef primaire?
Et bien parce que en l'absence de celle-ci, la table s'ouvre en read only dans openoffice base!
La technique d'utilisation de la table selection est la suivante: un formulaire peut contenir plusieurs forms, chacun étant associée à une et une seule table. Le form principal sera associé à la table selection. La table que l'on veut gérer sera associé à un form secondaire lié au form principal par le champ matricule. A chaque sélection de fichier, la colonne matricule de la table selection sera mise à jour, et ensuite l'enregistrement correspondant de entrh sera affiché..

Le formulaire entrh pour consulter et mettre à jour
Bien sûr nous n'allons pas repartir de rien. Faisons une copie du formulaire précédant que nous collons sous un autre nom.
Basculons sur Non dans MainForm "N'ajouter que des données" et "Autoriser les ajouts" et supprimons le Contrôle de table sur matricule.
Nous allons ajouter au formulaire un form maître duquel va dépendre tous les autres et dont la table selection constituera le contenu.
Cliquons droit sur Formulaire dans le navigateur et créons un nouveau formulaire:
En fait nous créons un nouveau form (appelé Standard par défaut) qui est sur un pied d'égalité avec MainForm:
Faisons un glisser-déposer de MainForm dans Standard de manière à arriver à ceci:

MainForm porte maintenant bien mal son nom, mais qu'importe!
Reste à fixer les propriétés de Standard: Table selection, Ajouts Non, Suppressions Non:
Il faut encore lier Standard à MainForm:
Plaçons là où se trouvait le contrôle de table qui a été supprimé une boite combinée (Combox). Si Standard est sélectionnée dans le navigateur, cette boite combinée devrait lui appartenir. Lorsqu'on a fini de la dimensionner l'assistant s'ouvre. La table cible est selection, table du sous-formulaire Standard: on n'a pas le choix. La table source sera entrh:
le champ d'affichage entrh.matricule:
qui sera enregistré dans selection.matricule:
On termine en ajustant les propriétés de la zone combinée.
"Déroulante" Non:
Tri sur matricule:
On peut déjà utiliser le formulaire: il suffit après avoir choisi un dossier dans la boite combinée de cliquer sur "Rafraîchir le contrôle", puis "Actualiser":

Le mieux est de remplacer ces deux actions par un appui sur un seul bouton qui sera ajouté à MainForm: étiquette "Valider", action "Rafraîchir le formulaire". Il est important de vérifier que ce bouton fait bien partie de MainForm et non pas de Standard.
Et voilà:
L'utilisation de la boite combinée est la suivante: on commence à taper le matricule souhaité dans la zone de saisie supérieure et dès que celui-ci apparaît dans la liste on clique dessus plutôt que de continuer à le taper, ce qui peut être très intéressant dans le cas d'un matricule à rallonge (par exemple 12 caractères).