mardi 30 novembre 2010

Espace disque

Météo.
Nous créons le dossier test et nous y plaçons le fichier "météo" dont voici le contenu:
Belle journée ensoleillée.
Températures de 24 à 28 degrés.
Vent modéré.
Examinons maintenant le contenu du dossier avec la commande ls et les options:
g: pour un affichage au format long mais sans le propriétaire
o: idem sans le groupe
a: pour afficher les fichiers cachés
i: pour afficher le numéro d'inode
s: pour afficher la taille en blocs
toto@rigel:~$ ls -goais test
total 12
1308818 4 drwxr-xr-x  2 4096 2010-11-26 10:10 .
1308748 4 drwxr-xr-x 39 4096 2010-11-26 10:04 ..
 442198 4 -rw-r--r--  1   79 2010-11-26 10:10 météo
La première colonne contient les identifiants numériques (numéro d'inode) des fichiers. Ensuite vient une colonne avec l'espace disque occupé par chaque fichier, en blocs de 1024 octets. Ces blocs n'ont aucun rapport avec les blocs du système de fichier (ext3, taille 4096 octets), ni avec les blocs du disque (taille 512 octets). A gauche des dates, on trouve une colonne avec des tailles en octets: 79 pour le fichier météo. Pour ceux qui veulent compter, ne pas oublier les caractères de contrôle "fin de ligne". Ne pas oublier, aussi que les caractères accentués comptent double (Nous sommes en UTF-8).
Les deux premières lignes de la liste se rapportent à des dossiers comme l'indique le premier caractère de la colonne 3, c'est-à-dire d.
Quels sont ces dossiers?
La commande find permet de faire une recherche sur les identifiants numériques:
toto@rigel:~$ find ~/ -inum 1308818 ; find ~/ -inum 1308748
/home/toto/test
/home/toto/
Le premier fichier, "." n'est rien d'autre que le dossier test lui-même, tandis que le deuxième fichier, ".." est le dossier parent. Ce sont des résultats bien connus.

Ce n'est pas mon frère, c'est moi.
Procédons à quelques manipulations avant de relancer notre commande ls:
toto@rigel:~$ cd test
toto@rigel:~/test$ ln météo prévisions ; cp météo le_temps
toto@rigel:~/test$ ls -goais
total 20
1308818 4 drwxr-xr-x  2 4096 2010-11-26 12:36 .
1308748 4 drwxr-xr-x 39 4096 2010-11-26 10:04 ..
 442199 4 -rw-r--r--  1   79 2010-11-26 12:36 le_temps
 442198 4 -rw-r--r--  2   79 2010-11-26 10:10 météo
 442198 4 -rw-r--r--  2   79 2010-11-26 10:10 prévisions
Nous avons maintenant semble-t-il, 3 fichiers identiques de 79 octets.
Mais attention, les apparences sont trompeuses. En effet l'identifiant 442198 est répété. Donc nous avons dans le répertoire, deux entrées, deux noms différents (météo et prévisions) pour le même fichier (ce qui est indiqué par le chiffre 2 qui précède la taille en octets).
Vérifions cela:
toto@rigel:~/test$ echo 'Bonne soirée' >> prévisions
toto@rigel:~/test$ ls -goais
total 20
1308818 4 drwxr-xr-x  2 4096 2010-11-26 12:36 .
1308748 4 drwxr-xr-x 39 4096 2010-11-26 10:04 ..
 442199 4 -rw-r--r--  1   79 2010-11-26 12:36 le_temps
 442198 4 -rw-r--r--  2   93 2010-11-26 12:50 météo
 442198 4 -rw-r--r--  2   93 2010-11-26 12:50 prévisions
La taille de météo et de prévisions passe à 93, ce qui montre bien qu'il s'agit de deux noms portés par un même fichier. Le lien existant entre météo et prévisions est appelé un lien en dur.
Mais attendez, cela implique que le total repris en ligne 1 de l'output est faux, ou en tout cas qu'il ne correspond pas au nombre de blocs (de 1 K) occupés par les fichiers du dossier, puisque le fichier 442198 est compté deux fois.
Pour un total correct, il faut procéder avec la commande du. Utilisons l'option -a pour que les fichiers comptabilisés apparaissent dans l'output:
toto@rigel:~$ du -a test
4       test/météo
4       test/le_temps
12      test
Le chiffre 12 correspond au volume occupé par le fichier 442198 (météo), le fichier 442199 (le_temps) et le fichier 1308818 (le dossier test lui-même). L'entrée "prévisions" est ignorée. Le fichier 1308748, qui correspond au répertoire parent n'est pas repris. Deux entrées en moins (celles qui ne sont pas surlignées en saumon), à 4 K l'entrée: voilà qui explique pourquoi le total passe de 20 K à 12 K. Quant au fichier 1308748 (répertoire parent), du en tiendra compte uniquement pour le calcul du volume occupé par /home/toto ou par tout autre répertoire supérieur.

Pour vivre heureux, vivons cachés.
Effectuons encore quelques manipulations avant d'exécuter de nouveau la commande ls (avec l'option supplémentaire -R pour la récursivité):
toto@rigel:~/test$ mkdir test.d1 test.d2
toto@rigel:~/test$ mv prévisions test.d1 ; mv météo test.d2
toto@rigel:~/test$ ls -Rgoais ~/test
/home/toto/test:
total 20
1308818 4 drwxr-xr-x  4 4096 2010-11-27 11:42 .
1308748 4 drwxr-xr-x 39 4096 2010-11-26 10:04 ..
 442199 4 -rw-r--r--  1   79 2010-11-26 12:36 le_temps
1309068 4 drwxr-xr-x  2 4096 2010-11-27 11:42 test.d1
1309591 4 drwxr-xr-x  2 4096 2010-11-27 11:42 test.d2

/home/toto/test/test.d1:
total 12
1309068 4 drwxr-xr-x 2 4096 2010-11-27 11:42 .
1308818 4 drwxr-xr-x 4 4096 2010-11-27 11:42 ..
 442198 4 -rw-r--r-- 2   93 2010-11-26 12:50 prévisions

/home/toto/test/test.d2:
total 12
1309591 4 drwxr-xr-x 2 4096 2010-11-27 11:42 .
1308818 4 drwxr-xr-x 4 4096 2010-11-27 11:42 ..
 442198 4 -rw-r--r-- 2   93 2010-11-26 12:50 météo
Nous avons explicitement avec la commande "ln météo prévisions", créé un lien en dur. Mais de tels liens sont naturellement présents dans le système. Ainsi pour le fichier 1308818, le nombre 4 - en rouge, colonne 4 - indique qu'il lui correspond 4 entrées, dont 3 figurent dans l'output ci-dessous (il manque l'entrée dans /home/toto). Le fichier 1308818 contribue donc à chacun des 3 totaux.
Que donne la commande du?
toto@rigel:~/test$ du -a ~/test
4       /home/toto/test/test.d1/prévisions
8       /home/toto/test/test.d1
4       /home/toto/test/le_temps
4       /home/toto/test/test.d2
20      /home/toto/test
Remarquons en premier que si le total /home/toto/test est le même dans les deux cas (20) il s'agit d'un hasard: vu l'existence de sous-dossiers (non-vides), les deux nombres ont une signification différente. D'un côté  (commande ls), il s'agit du volume occupé par les fichiers qui ont une entrée dans le répertoire test et de l'autre (commande du), il s'agit du volume occupé par le dossier test et tout ce qu'il contient. Le fichier 1308748 (/home/toto) est bien comptabilisé par ls, mais pas par du (comme précédemment). Pour le fichier 442198 (prévisions), il contribue au total calculé par du, mais pas au total 20 de ls puisqu'il n'y a plus d'entrée pour ce fichier au niveau du répertoire test. Il suffirait que 442198 occupe plus de 4 K pour que les deux totaux ne soient plus égaux.
Logiquement, du, contrairement à ls, ne tient pas compte du fichier 1308818 (/home/toto/test) pour le calcul du volume occupé par test.d1 et test.d2 (il en tient compte pour le volume occupé par test). Comme précédemment, nous avons surligné (en saumon) les items de l'output de ls dont la commande du tient compte.

C'est trop injuste!
Nous constatons que cette fois "météo" est ignoré par du au profit de "prévisions". Évidemment, il y a là un certain arbitraire. Pourquoi compter 8 K pour test.d1, 4 K pour test.d2 et non pas le contraire? D'ailleurs, si on a:
toto@rigel:~/test$ du -ac test.d1 test.d2
4       test.d1/prévisions
8       test.d1
4       test.d2
12      total
on a aussi:
toto@rigel:~/test$ du -ac test.d2 test.d1
4       test.d2/météo
8       test.d2
4       test.d1
12      total
Mais le principal est que le total des blocs occupés (affiché grâce à l'option -c) soit le bon. Attention, pour obtenir 12, il ne faut pas tenir compte des 4 K du fichier prévisions (météo) puisque ceux-ci sont inclus dans  les 8 K du dossier test-d1 (test-d2).
Donc si on pouvait avoir l'output sans les fichiers, ce serait cool. Et bien il suffit de supprimer l'option -a:
toto@rigel:~/test$ du -c test.d1 test.d2
8       test.d1
4       test.d2
12      total
toto@rigel:~/test$ du -c test.d2 test.d1
8       test.d2
4       test.d1
12      total
Mais avec la commande du ~/test, on a un problème:
toto@rigel:~/test$ du ~/test
8       /home/toto/test/test.d1
4       /home/toto/test/test.d2
20      /home/toto/test
Le fichier le_temps a disparu, alors pour expliquer le 20 final...
Comment procéder pour afficher le_temps sans pour autant faire réapparaitre prévisions, c'est-à-dire sans afficher le détail de test.d1 et test.d2 ?
La solution se trouve dans l'emploi de l'option --max-depth:
toto@rigel:~/test$ du ~/test -a --max-depth=1
8       /home/toto/test/test.d1
4       /home/toto/test/le_temps
4       /home/toto/test/test.d2
20      /home/toto/test

Creuser, toujours creuser!
L'option --max-depth=1 n'empêche pas du d'explorer l'arborescence plus en profondeur.
Ainsi, après avoir procédé comme ci-dessous,
toto@rigel:~/test$ mkdir test.d1/test.d3
toto@rigel:~/test$ echo salut > test.d1/test.d3/salut
toto@rigel:~/test$ echo hello > test.d1/test.d3/hello
ce qui a ajouté test.d3 à l'arborescence:
.
├── le_temps
├── test.d1
│   ├── prévisions
│   └── test.d3
│       ├── hello
│       └── salut
└── test.d2
    └── météo
nous constatons que l'output de la commande du, même avec --max-depth=1, est modifié en conséquence
toto@rigel:~/test$ du ~/test -a --max-depth=1
20      /home/toto/test/test.d1
4       /home/toto/test/le_temps
4       /home/toto/test/test.d2
32      /home/toto/test
On peut utiliser l'option -S. De la sorte, le volume figurant devant chaque nom de dossier se rapportera seulement au volume de ce dossier et aux fichiers qu'il contient: le volume devant test.d1 retrouvera son ancienne valeur (celle d'avant la création de test.d3). Par contre, le total qui peut être obtenu en ajoutant l'option c se rapportera à toute l'arborescence:
toto@rigel:~/test$ du ~/test -acS --max-depth=1
8       /home/toto/test/test.d1
4       /home/toto/test/le_temps
4       /home/toto/test/test.d2
8       /home/toto/test
32      total
Si seul le total nous intéresse et en aucune façon une quelconque ventilation, il suffit d'utiliser l'option s:
toto@rigel:~$ du -s ~/test
32      /home/toto/test

Quelle belle partition!
Parmi les options de la commande du, on peut encore citer:
-h pour afficher les volumes de manière facilement lisible
-x pour rester dans le même système de fichiers.
Exemple:
root@rigel:~# du -hx --max-depth=1 /
3,4G    /usr
76K     /tmp
8,0K    /home
15M     /etc
4,0K    /srv
2,4G    /var
0       /sys
12K     /mnt
275M    /opt
6,6M    /bin
255M    /lib
8,0K    /media
45M     /boot
4,0K    /cdrom
4,0K    /selinux
7,6M    /sbin
0       /dev
0       /proc
16K     /lost+found
628K    /root
6,3G    /
La commande demande un certain temps pour s'exécuter. Rappelons que l'option --max-depth=1 ne limite nullement la profondeur du scan.
Le volume de 8 K qui apparait devant /home montre bien que la partition montée en ce point n'a pas été explorée, et ce grâce à l'option -x.
En ce qui concerne le total de la partition, un résultat peut être obtenu beaucoup plus rapidement avec la commande df:
toto@rigel:~$ df -h /
Sys. de fich.       Taille  Uti. Disp. Uti% Monté sur
/dev/sdc1              11G  6,5G  3,6G  65% /
df n'effectue aucun scan et tire directement ses informations du superbloc. Le montant obtenu n'est pas le même (6,5 G au lieu de 6,3 G). Pour df, il s'agit de l'espace occupé par le système de fichier et non de l'espace occupé par l'ensemble des fichiers. Et pour un système de fichier qui vient d'être créé, l'espace disponible n'est déjà plus égal à la taille.

mardi 9 novembre 2010

Les secrets de cat

La commande cat copie vers la sortie standard le contenu du ou des fichiers indiqués sur la ligne de commande. Une simple redirection permet donc d'obtenir un fichier qui est la concaténation de plusieurs fichiers:

toto@rigel:~/test$ cat garçons
Toto
Riri
toto@rigel:~/test$ cat filles
Tata
Titine
toto@rigel:~/test$ cat garçons filles > toutlemonde
toto@rigel:~/test$ cat toutlemonde
Toto
Riri
Tata
Titine
toto@rigel:~/test$

C'est la finalité première de cat comme d'ailleurs annoncé au début de la page man:

cat - concatenate files and print on the standard output

Si aucun fichier n'est indiqué sur la ligne de commande, cat copie alors l'entrée standard vers la sortie standard:

cat est en attente de ce que nous allons saisir au clavier (entrée standard) pour l'envoyer vers la sortie standard.
Nous saisissons "hello"


et tapons sur Enter pour envoyer le texte saisi vers cat:

A l'écran figure ce que nous avons tapé au clavier, mais aussi ce qui a été renvoyé par cat via stdout (c'est évidement le même texte). cat continue à attendre la suite.
Nous interrompons le flux d'entrée par appui sur CTRL-D.
On peut s'amuser de la même façon avec tr par exemple:



Seul un appui sur Enter sépare les deux dernières copies d'écran.
Ce genre de manipulation est d'une utilité toute relative sauf si nous redirigeons stdout nous donnant ainsi la possibilité de créer un petit fichier texte:

Cette fois évidemment les lignes n'apparaissent pas en double puisque l'output se fait vers le fichier salut. Comme précédemment, indiquons à cat avec CTRL-D qu'il ne doit plus rien attendre et puis vérifions (avec cat toujours) que le fichier salut contient bien ce qu'il est censé contenir.


On peut intercaler la lecture de stdin entre la lecture de plusieurs fichiers en utilisant le signe "-":


cat a lu le premier fichier, envoyé son contenu dans toutlemonde et est en attente de la suite qui doit venir de stdin. Saisissons "++++++", puis appuyons sur Enter:


"++++++" se trouve déjà dans toutlemonde. Il reste à appuyer sur CTRL-D pour terminer:


Attention, il s'agit de terminer proprement et non pas en utilisant CTRL-C, faute de quoi cat ne lira pas le second fichier.

Considérons maintenant cette commande:


Aucun nom de fichier n'est indiqué, donc cat devrait lire l'entrée standard. Mais l'instruction de redirection << FIN signifie à bash que ce qui suit, donc ici ce qui sera écrit à l'écran, constituera le flux d'entrée et ce jusqu'au mot FIN. cat va donc lire non pas l'entrée standard, mais le contenu de l'écran jusqu'à la rencontre du délimiteur FIN:




Un simple appui sur Enter sépare ces deux copies d'écran.
Le texte

hello
ola
hi

est ce qui s'appelle un here document.

L'emploi de here documents est surtout utile dans les scripts: il permet d'éviter la multiplication des commandes echo.
Soit le script courses:

#!/bin/bash

echo
echo Bonjour $USER

cat  << COURSES

Voici la liste des courses:

Riz
Grains
Saucisses
Tomates
Piments verts

echo 1
COURSES

echo 2

Voici ce que donne son exécution:


Nous avons volontairement, pour des raisons pédagogiques, pollué le script avec "echo 1" et "echo 2".
"echo 1" est lu par cat et ensuite renvoyé vers stdout.
"echo 2" est une commande qui est exécutée.

lundi 1 novembre 2010

redirections (encore)

Les descripteurs de fichiers
Le répertoire /proc contient un sous-répertoire pour chaque processus qui tourne sur le système, sous-répertoire nommé d'après le pid de ce processus.
Chacun de ces répertoires contient à son tour un sous-répertoire fd où se trouvent les descripteurs de fichier ouverts par le processus.
Sachant que echo $$ renvoie le pid du shell bash courant, examinons quels y sont les descripteurs de fichier ouverts:

/proc/1897/fd/0 correspond à l'entrée standard (stdin)
/proc/1897/fd/1 correspond à la sortie standard (stdout)
/proc/1897/fd/2  correspond à l'erreur standard (stderr)

Ce sont les descripteurs de fichiers par défaut.
Nous avons déjà traité de stdout et stderr dans le billet précédent.
Quant à l'entrée standard, elle correspond au clavier.
Lorsque nous exécutons une commande, il lui correspond un processus qui est différent du processus du shell dans lequel la commande a été lancée. Ce processus hérite des descripteurs tels qu'ils existent au niveau du shell, puis les redirections éventuelles sont effectuées. Mais comment avoir un état de la situation? Il faudrait connaître le pid de ce processus! Et bien pas vraiment: il suffit d'examiner le contenu du répertoire /proc/self/fd:


Nous voyons que /proc/self/fd est en fait /proc/2310/fd où 2310 est le pid du processus d'exécution de la commande. Ce répertoire /proc/2310/fd n'existe que pendant la durée de vie du processus. Les messages d'erreurs de la première commande ls (comme celui généré par le fait que le fichier tata est inexistant) n'apparaissent pas à l'écran. Ils ont été redirigés vers le fichier erreurs, ce que montre bien l'examen du contenu de /proc/2310/fd. La deuxième commande ls a hérité des descripteurs du shell qui sont restés inchangés et cette fois le message d'erreur (prouvant que /proc/2310 n'existe plus) arrive à l'écran.

Les canaux de communication avant et après un pipe.
Dans le cas d'un pipe, chacune des deux commandes, avant et après le pipe, a son propre  système de canaux de communication. En principe stdout de la commande en aval est connecté à stdin de la commande en amont, comme dans ce cas simple où l'entrée standard de la commande tr n'est plus le clavier, mais la sortie standard de la commande cat:


Mais en cas de redirections, la situation peut-être plus compliquée:


Ici, stdout en aval (processus 2109) a été redirigé vers le fichier output et c'est le descripteur 2 auquel le canal 1 a été affecté qui est connecté au pipe. En amont (processus 2110), stdin (fd/0) est connecté au pipe. Bien sûr, le pipe ici ne sert absolument à rien. En effet la commande ll en aval ne produit aucun message d'erreur qui serait envoyé dans le canal 1. Donc rien ne transite par le pipe et de toute façon ls en amont ignore stdin. Cette ligne de commandes n'a d'autre utilité que de permettre la visualisation de connections en amont et en aval d'un pipe.

Filtrer stderr.
Imaginons maintenant que nous désirions filtrer les messages d'erreurs avec grep. Nous voudrions aussi que tout arrive à l'écran, aussi bien stderr que stdout. Dans le billet précédant nous avons proposé une solution à ce genre de problème. En voici une autre:


correspondant au schéma:
Cette solution, comme celle du billet précédant, demande un descripteur supplémentaire (3). Mais cette fois, il est créé au niveau de la commande et il n'existe que pendant la durée d'exécution de la commande, alors qu'auparavant il avait été défini au niveau du shell.
Il ne faut pas se laisser leurrer par le schéma et lui accorder une signification qu'il n'a pas: les différentes instructions de redirections s'évaluent bien de gauche à droite. La première instruction 3>&1 établit un lien du descripteur 3 vers le canal 1, lien vers le canal 1 qui sans cela serait perdu suite à la deuxième instruction qui lie le descripteur 1 au canal 2. La dernière instruction 2>&3  affecte au descripteur 2 le canal précédemment affecté au descripteur 3, c'est-à-dire le canal 1.

Rediriger stdout au niveau du shell
Si nous redirigeons stdout vers un fichier au niveau d'un shell bash, chaque commande lancée dans ce shell, héritant des descripteurs existants, enverra son output dans ce fichier. Plus rien n'arrivera à l'écran, sauf les messages d'erreurs. Avant de faire pointer le descripteur 1 vers le fichier, il convient de sauvegarder le lien vers le canal 1:
toto@rigel:~/test$ exec 3>&1 1> output
De la sorte on pourra restaurer la situation initiale en faisant pointer le descripteur 1 vers le canal associé au descripteur 3 et en clôturant ensuite ce descripteur 3:
toto@rigel:~/test$ exec 1>&3 3>&-
Procédons:



Rediriger stdin
Nous n'avons pas jusqu'à présent donné d'exemple de redirection de stdin.
Revenons sur la commande où nous avons mis en majuscules le contenu du fichier erreurs. On aurait pu procéder comme ceci:


< erreurs est mis pour 0< erreurs: l'entrée standard a été redirigée vers le fichier erreurs. On peut enchaîner les redirections comme ceci:


La sortie standard est redirigée vers Erreurs qui va donc contenir le message modifié.
Ce genre d'expression:
commande < fichier-in > fichier-out
est relativement courant.
Continuons avec un autre exemple:


La commande read est en attente de ce que nous allons saisir au clavier pour le mettre dans la variable readvar. Nous saisissons par exemple "hello", puis nous tapons sur Enter afin d'envoyer le texte saisi. Puis nous vérifions le contenu de readvar:

Si nous voulons lire le fichier toto dont voici le contenu:

Je m'appelle Toto.
Je vais à l'école avec Tata
Nous allons à pied car ce n'est pas loin.

nous devons rediriger stdin vers ce fichier.
Procédons:

readvar contient la première ligne du fichier toto. Si nous relançons la commande read rien ne changera. Ce sera encore la première ligne qui sera lue, car à chaque fois le fichier toto est réouvert et la lecture reprend au début . En fait ce qu'il faudrait, c'est pouvoir rediriger stdin vers le fichier toto au niveau du shell bash, comme cela a été fait pour stdout. Mais c'est impossible (plantage garanti). Par contre nous pouvons procéder au niveau d'un script qui contiendra les commandes read, comme dans le script suivant (appelé readtoto):

#!/bin/bash

exec 4<&0 < toto # sauvegarde du lien vers le canal 0
                 # redirige stdin vers toto
sleep 10
read readvar
echo $readvar
read readvar
echo $readvar

exec 0<&4 4<&-   # retablit stdin et clôture fd/4

La commande sleep est là pour nous permettre d'examiner la situation depuis un autre shell bash. Procédons après avoir rendu le script exécutable (chmod +x readtoto):

echo $PPID nous a donné le pid du processus parent et pendant que le script était arrêté sur la commande sleep nous avons lancé la commande pstree (dans un autre shell).


La commande sleep tourne dans un processus à part, de même que les deux commandes read suivantes qui vont à chaque fois hériter des canaux de communication définis au niveau du processus 2569 (readtoto). Comme toto est ouvert une seule fois en début de script, les différentes lignes du fichier sont lues les unes après les autres.

Ouverture en lecture avec un descripteur
Il existe une autre façon de procéder pour arriver au même résultat: ouvrir toto en lecture en le reliant à un nouveau descripteur qui est alors créé et ajouter après chaque read une instruction redirigeant stdin vers le fichier lié à ce nouveau descripteur.



(La dernière commande est la commande de fermeture du fichier)

De nouveau le fichier est ouvert une seule fois ce qui nous permet de lire au delà de la première ligne.

Ouverture en écriture avec un descripteur
Nous ouvert le fichier toto en lecture via un descripteur.
Il est possible de procéder de la sorte pour ouvrir un fichier en écriture.
Ouvrons par exemple le fichier erreurs en écriture en l'affectant au descripteur 4.
Attention, de ce fait nous supprimons le contenu éventuel du fichier.
Nous commençons par écrire dans le fichier via une redirection de stdout vers le fichier affecté au descripteur 4, ensuite via une redirection de stderr:


On pourrait arriver au même résultat sans utiliser fd/4 et en remplaçant chaque >&4 par >> erreurs.