mercredi 27 mai 2015

grep history

Nous avons l'habitude d'utiliser grep pour filtrer l'output de la commande history.
Mais cette fois, ça ne fonctionne pas:


Pour une raison inconnue grep considère que le flux reçu en input est un fichier binaire.
Bien sûr le problème peut être contourné avec la commande:

[root@rigel ~]# history | grep --binary-file=text restart

Voyons ça en image:


Pourtant auparavant, on n'avait pas besoin de cette option. De plus, cette fois nous sommes root, mais pour un utilisateur ordinaire rien n'est changé: ça fonctionne comme avant!
Quel est ce mystère?
Utilisons file pour déterminer le type du fichier .bash_history:

[root@rigel ~]# file .bash_history 
.bash_history: ISO-8859 text

La clef du mystère est là, car pour toto (utilisateur lambda):

[toto@rigel ~]$ file .bash_history 
.bash_history: UTF-8 Unicode text


Grâce à iconv, nous allons convertir le fichier .bash_history:

[root@rigel ~]# iconv -f ISO-8859-1 -t UTF-8 < .bash_history  > history

et vérifier que le résultat obtenu est celui que nous attendons:


Il reste à copier history dans .bash_history:

[root@rigel ~]# cp history .bash_history 
cp : voulez-vous écraser « .bash_history » ? o
[root@rigel ~]# 

Il faut se déconnecter pour que le changement soit pris en compte: exit puis de nouveau su (sudo -s pour Ubuntu)

Et voilà:



jeudi 21 mai 2015

shebang postgresql (suite)

Dans le billet précédent nous avions écarté l'usage du shebang
#/usr/bin/psql -f
car cette ligne n'est pas  un commentaire pour psql. Elle est envoyée vers le serveur pour traitement, ce qui provoque une erreur.
Tout un coup: une illumination! Et si on commençait par vider le tampon des requêtes ?
Et ça fonctionne!
Modifions le script sbpsql1x du billet précédent et sauvegardons le sous le nom sbpsql4x:

#!/usr/bin/psql -f
\set QUIET
\r
select now()
\echo
\echo 'Sera envoyé au serveur:'
\echo
\p
\echo
\echo 'Procédons:'
\echo
;

Avant de vider le tampon des requêtes (\r), nous avons défini la variable QUIET afin d'éviter le message:

Query buffer reset (cleared).

Vérifions:



Nous pouvons donc utiliser ce shebang. Le problème est que les paramètres passés au script (comme bdtest) sont en fait des arguments pour la ligne de commande psql.

Pour pouvoir passer de vrais paramètres , il est donc préférable d'utiliser exec psql comme dans le script sbpsql5x (obtenu en modifiant le script sbpsql2x):

#!/bin/bash
exec psql bdtest -f "$0"
\set QUIET 
\echo
\echo 'Tampon initial:'
\echo
\p
\echo
\r
select now()
\echo
\echo 'Sera envoyé au serveur:'
\echo
\p
\echo
\echo 'Procédons:'
\echo
;

Nous avons ajouté l'en-tête 
#!/bin/bash
Et comme de toute façon nous allons vider le tampon, la fonction
--() { :; }
est passée à la trappe car il n'est plus besoin que la ligne avec exec soit un commentaire.

Essayons le script:



Adaptons le script dépensepx du billet script psql. Appelons la nouvelle version dépensenpx:

#!/bin/bash
exec psql bdtest -v signification="$1" -f "$0"
\set QUIET
\r
\pset numericlocale
\pset border 2
\pset linestyle u
\pset footer
\set titre 'Dépenses ':signification
\pset title :titre'\n'
\set code_u
\echo
SELECT code_u
FROM utilisations
WHERE signification = :'signification'
\gset
SELECT référence, date_exec::text as date, détails, montant
FROM dépenses
WHERE code_u = :'code_u'
UNION
SELECT 'TOTAL', '', '', sum(montant)
FROM dépenses
WHERE code_u = :'code_u'
order by 1

Ce script fonctionne très bien:



Cependant ce script se termine en même temps que la commande psql.
Si nous voulons apporter certaines améliorations (utilisation de awk,envoi des messages d'erreur vers /dev/null), nous devons l'appeler à partir de dépensenp0x:

fichier=$(mktemp)
exec 7>&1
exec 1>$fichier
exec 6>&2
exec 2>/dev/null
./dépensenpx "$1"
exec 1>&7 7>&-
awk 'NR==6 {y=$0};/TOTAL/ {print y};1' $fichier
exec 2>&6 6>&-

(Toutes les explications se trouvent dans ce billet)

Ce qui donne ceci:



Avant de terminer, précisons que les tables utilisées ici ont déjà été présentées dans ce blog.
Nous avons rappelé les instructions de créations de ces tables dans le billet script psql

mercredi 20 mai 2015

shebang postgresql

Le shebang (#!) est un en-tête de fichier qui indique au système d'exploitation que ce fichier est en fait un script. Le chemin d'accès vers l'interpréteur à utiliser est indiqué juste après sur la même ligne.
Ainsi un script bash devrait avoir une première ligne du genre:

#!/bin/bash

Problème avec les scripts interprétés par psql:  pour SQL, # n'indique pas un commentaire, mais bien  --
Faisons le test dans un terminal psql:


Nous imprimons (\p) ce qui sera envoyé au serveur pour exécution avant de procéder (;). Nous constatons que le commentaire a disparu et n'est pas envoyé au serveur.
Par contre:



'# commentaire' figure bien dans ce qui est envoyé au serveur, d'où l'erreur.

Nous avons le même genre d'erreur lors de l'exécution du script sbpsql1x:

#!/usr/bin/psql -f
select now()
\echo 'Sera envoyé au serveur:'
\echo
\p
\echo
\echo 'Procédons:'
\echo
;

Vérifions:


Comme proposé ici on peut remplacer la ligne shebang par

--() { :; }; exec psql [options] -f "$0"

Notre script devient sbpsql2x:

--() { :; }; exec psql bdtest -f "$0" 
select now()
\echo 'Sera envoyé au serveur:'
\echo
\p
\echo
\echo 'Procédons:'
\echo
;

Cette fois ça fonctionne car la ligne 1 est traitée comme un commentaire par psql:


Cette ligne n'est pas vraiment un shebang (où est #! ?). Elle commence par la définition d'une fonction (notée --) associée à une commande qui ne fait rien (:). Comme la fonction n'est jamais utilisée on pourrait mettre n'importe quoi (par exemple echo 'Hello') à la place de :).
En toute rigueur on devrait commencer le script par un véritable shebang, comme ceci:

#!/bin/bash
--() { :; }; exec psql bdtest -f "$0" 
select now()
...

Mais on retombe alors sur la même erreur.

Une autre possibilité est celle utilisée dans ce script (sbpsql3x):

#!/bin/sh
exec sh -c "tail -n +3 $0 | psql bdtest -f -" 
select now()
\echo 'Sera envoyé au serveur:'
\echo
\p
\echo
\echo 'Procédons:'
\echo
;

La commande tail envoie à partir de la ligne 3 le contenu du script (dont le nom se trouve dans $0) vers la commande psql. L'option -f - signifie que psql lit les instructions depuis l'entrée standard qui dans notre cas est la sortie standard de tail (voir ici: les canaux de communication avant et après un pipe: stdout de la commande en aval est connecté à stdin de la commande en amont).



samedi 16 mai 2015

script psql

Nous avons déjà rencontré dans ce blog des scripts appelant la commande psql.
Mais cette fois nous allons partir des notions de base et entrer dans plus de détails.
psql est une commande permettant de se connecter à une base de données PostgreSQL.
Ainsi la commande

psql bdtest

ouvre un terminal interactif connecté à la base de données bdtest:


Bien sûr pour que cette commande simplifiée fonctionne il faut notamment qu'il existe un utilisateur PostgreSQL appelé toto et que le serveur PosgreSQL se trouve sur la même machine que le client.
Au début le seul utilisateur PostgreSQL existant est postgres.
Pour démarrer il faut d'abord devenir postgres et créer l'utilisateur PostgreSQL toto en lui donnant le privilège de créer des bases de données:


Etant redevenu toto après exit, il nous est alors loisible de créer bdtest avec la commande createdb.

Bref, tout cela est très loin: bdtest existe depuis longtemps et contient maintenant de multiples données qui ont déjà été présentées et utilisées dans le cadre de ce blog, notamment les tables utilisations et dépenses créées par ces instructions:

CREATE TABLE utilisations
(code_u CHAR(2) PRIMARY KEY,
 signification VARCHAR(20))
;
CREATE TABLE dépenses
(référence CHAR(9) PRIMARY KEY,
 date_exec DATE,
 montant NUMERIC(13,2),
 détails VARCHAR(50),
 code_u CHAR(2) REFERENCES utilisations)
;


Précisons encore que si nous ne sommes pas (plus) toto, il nous est possible de se connecter en tant que toto avec la commande

psql bdtest toto

(Si les fichiers pg_hba.conf et pg_ident.conf ont été correctement configurés)
Dans le terminal interactif nous pouvons exécuter des commandes ou entrer des instructions SQL telles que:

SELECT *
FROM dépenses
WHERE détails = 'Docteur Lebon'
;

Si cette instruction se trouve déjà dans un fichier (par exemple lebon.sql), il n'est nul besoin de la retaper. Il suffit de lancer la commande:

\i lebon.sql

(lebon.sql doit se trouver là où on est, c'est-à-dire dans ~/SQL/bdtest)

Si nous redirigeons l'entrée standard vers le fichier lebon.sql:

psql bdtest < lebon.sql

nous n'ouvrons pas alors de terminal interactif. L'instruction est exécutée, puis la liaison vers bdtest est coupée:



Autre possibilité:

psql bdtest <<FIN

redirige l'entré standard vers le document qui se trouve ici (here-document), donc vers ce qui sera tapé à l'écran (jusqu'au mot FIN):



Le système attend la saisie suivante.
Après envoi du mot FIN, le résultat s'affiche:


Mais quel est l'avantage de cette procédure par rapport au terminal interactif, puisqu'il faut quand même saisir l'instruction SQL?
L'avantage est que cette procédure peut être utilisée dans un script:le here-document est alors dans le script lui-même.
Soit le script lebonx:

#!/bin/bash
echo
psql bdtest <<FIN
SELECT *
FROM dépenses
WHERE détails = 'Docteur Lebon'
FIN

que nous avons rendu exécutable (et qui se trouve dans ~/SQL/bdtest).
Exécutons le:



Nous pouvons passer en paramètre la valeur du détail demandé, comme dans le script détailx:

#!/bin/bash
echo $1
echo
psql bdtest -v détails="$1" <<FIN
SELECT *
FROM dépenses
WHERE détails = :'détails'
FIN

L'option -v dans la commande crée la variable psql détails avec la valeur $1.
Procédons:


Nous aimerions maintenant un script qui établisse le relevé des dépenses pour une certaine catégorie. Nous ignorons le code lié à cette catégorie: donc on veut passer en clair le nom de cette catégorie: par exemple 'Santé' pour les dépenses de santé et non '01'.
Considérons le script dépensex:

#!/bin/bash
psql bdtest  -q \
-v signification="$1" <<FIN
\pset numericlocale
\pset border 2
\pset linestyle u
\pset footer
\pset title 'Dépenses $1 \n'
\echo
SELECT code_u
FROM utilisations
WHERE signification = :'signification'
\gset
SELECT référence, date_exec::text as date, détails, montant
FROM dépenses
WHERE code_u = :'code_u'
UNION
SELECT 'TOTAL', '', '', sum(montant)
FROM dépenses
WHERE code_u = :'code_u'
order by 1
FIN

Nous avons expliqué ici comment utiliser la commande \gset
Testons le script:


L'option -q permet d'éliminer les différents messages informatifs résultant des commandes \pset:

Title is "Dépenses Grande surface".
Showing locale-adjusted numeric output.
Border style is 2.
Line style is unicode.

Si le détail passé en paramètre n'existe pas, d'autres messages polluent l'output:

no rows returned for \gset
ERROR:  syntax error at or near ":"
LINE 3: WHERE code_u = :'code_u'
                       ^
Ces messages transitent par le canal des erreurs (canal 2). Pour les éliminer nous ajoutons les instructions suivantes au début script:

exec 6>&2
exec 2>/dev/null

Tout d'abord nous sauvegardons les caractéristiques du canal 2 dans le canal 6, ensuite nous redirigeons le canal 2 vers le néant c'est-à-dire /dev/null.
(Nous avons déjà parlé des redirections ici )

En fin de script nous avons:

 exec 2>&6 6>&-

(A partir du canal 6, remise du canal 2 dans son état initial; fermeture du canal 6)

D'autre part nous voudrions nous servir de awk pour séparer la ligne 'TOTAL' des autres. Pour ce faire nous envoyons l'output de psql dans un fichier temporaire (créé par mktemp) qui sert d'output à awk (\o | awk ... est inopérant dans ce contexte).
Le script définitif est donc:

#!/bin/bash
fichier=$(mktemp)
exec 6>&2
exec 2>/dev/null
psql bdtest toto -q \
-v signification="$1" <<FIN
\pset numericlocale
\pset border 2
\pset linestyle u
\pset footer
\pset title 'Dépenses $1 \n'
\echo
\o $fichier
SELECT code_u
FROM utilisations
WHERE signification = :'signification'
\gset
SELECT référence, date_exec::text as date, détails, montant
FROM dépenses
WHERE code_u = :'code_u'
UNION
SELECT 'TOTAL', '', '', sum(montant)
FROM dépenses
WHERE code_u = :'code_u'
order by 1
FIN
exec 2>&6 6>&-
awk 'NR==5 {y=$0};/TOTAL/ {print y};1' $fichier

awk sauvegarde la ligne 5 (instruction 1) pour l'imprimer si la ligne lue contient 'TOTAL' (instruction 2), ensuite toutes les lignes lues sont imprimées puisque la condition 1 est toujours vraie (instruction 3)

Testons:


Si le libellé n'existe pas ('grande surface'), il n'y a aucun output (les messages d'erreurs ont disparus).
Peut-être on préférerait avoir ceci:


Dans ce cas il suffit d'ajouter dans le here-document (avant le premier SELECT) la meta-commande

\set code_u

ce qui initialise la variable code_u. De ce fait l'instruction SQL qui vient après \gset ne se plante plus. 

Certains pourraient dire que les scripts présentés ici ne sont pas des scripts psql mais des scripts bash qui appellent la commande psql.
Certes, mais l'emploi de la ligne shebang

#!/usr/bin/psql bdtest -f 

conduit invariablement à une erreur.
Nous avons trouvé ici un shebang qui fonctionne pour psql:

--() { :; }; exec psql -f "$0"

Ainsi le script détailpx

--() { :; }; exec psql bdtest -v détails="$1" -f "$0"
\echo :détails
\echo
SELECT *
FROM dépenses
WHERE détails = :'détails'

donne le même résultat que le script détailx présenté plus haut.

Cependant dès l'instant où la commande psql est terminée, nous quittons le script. Il est impossible de faire transiter dans le même script l'output du SELECT par awk comme auparavant.

Si nous partons du script dépensepx:

--() { :; }; exec psql bdtest -q -v signification="$1" -f "$0"
\pset numericlocale
\pset border 2
\pset linestyle u
\pset footer
\set titre 'Dépenses ':signification
\pset title :titre'\n'
\set code_u
\echo
SELECT code_u
FROM utilisations
WHERE signification = :'signification'
\gset
SELECT référence, date_exec::text as date, détails, montant
FROM dépenses
WHERE code_u = :'code_u'
UNION
SELECT 'TOTAL', '', '', sum(montant)
FROM dépenses
WHERE code_u = :'code_u'
order by 1

et que nous voulons apporter les mêmes améliorations que pour le script dépensex, nous devons l'appeler à partir du script bash dépense0x

#!/bin/bash
fichier=$(mktemp)
exec 7>&1
exec 1>$fichier
exec 6>&2
exec 2>/dev/null
./dépensepx "$1"
exec 1>&7 7>&-
awk 'NR==6 {y=$0};/TOTAL/ {print y};1' $fichier
exec 2>&6 6>&-

Comme auparavant nous redirigeons la sortie des erreurs (canal 2) vers le néant.
De plus nous redirigeons la sortie standard (canal 1) vers le fichier temporaire créé, de sorte que toute la sortie du script dépensepx s'effectuera vers ce fichier. Bien sûr, il faut revenir à la situation initiale avant l'exécution de awk pour que l'output de ce dernier soit dirigé vers l'écran. Remarquons que awk sauvegarde la ligne 6 au lieu de la 5 comme auparavant.
Explication:
La commande \o $fichier utilisée dans le script dépensex envoie dans $fichier le résultat des instructions SQL.
En cas de redirection de la sortie standard, c'est la totalité de l'output qui part vers $fichier, donc aussi la ligne générée par \echo.