Le programme cat
Le programme cat prend en argument un ou des noms de fichiers, et envoie sur la sortie standard le contenu desdits fichiers. Son nom vient du verbe anglais concatenate, « concaténer, mettre bout à bout ».
Envoyer un ou des fichiers sur la sortie standard
Lançons cat avec un fichier en argument, puis avec trois :
$ echo "un" > fichier-1.txt
$ echo "deux" > fichier-2.txt
$ echo "trois" > fichier-3.txt
$ cat fichier-1.txt
un
$ cat fichier-1.txt fichier-2.txt fichier-3.txt
un
deux
trois
L’ordre des fichiers sur la ligne de commande correspond à celui de la sortie :
$ cat fichier-3.txt fichier-1.txt fichier-2.txt
trois
un
deux
Le programme peut lire depuis l’entrée standard
Si on fournit à cat l’argument -
, le programme affiche en sortie ce qu’il reçoit sur son entrée standard (c’est un comportement fréquent chez les programmes qu’on peut lancer dans un shell) :
$ echo "envoyé par echo" | cat fichier-1.txt - fichier-3.txt
un
envoyé par echo
trois
On peut aussi entrer le texte au clavier. Une fois la dernière ligne écrite, taper Ctrl-D pour terminer la saisie :
$ cat -
entrée standard
entrée standard
Le premier entrée standard
est celui que j’ai tapé, et le second celui que cat a envoyé sur la sortie standard.
Le comportement de cat est le même si on ne lui fournit aucun nom de fichier :
$ cat
entrée standard
entrée standard
La commande peut aussi lire un document en ligne (here document) :
$ nom=toi
$ cat << EOF
> Salut $nom !
> La forme ?
> EOF
Salut toi !
La forme ?
Penser aux expansions du shell
On peut mettre à profit les diverses expansions de Bash pour fournir à cat les noms des fichiers à traiter.
Voici d’abord un exemple faisant appel à l’expansion des noms de fichiers. On affiche ici le contenu de tous les fichiers du répertoire dont le nom se termine par .txt
:
$ cat *.txt
un
deux
trois
Voici un autre exemple utilisant cette fois-ci l’expansion des accolades:
$ cat fichier-{3..1}.txt
trois
deux
un
À quoi cat ne sert pas : le useless use of cat
Si l’on souhaite envoyer le contenu d’un unique fichier sur la sortie standard, le recours à cat
est probablement inutile. Comparer ces trois commandes :
$ cat fichier-1.txt | grep 'un'
un
$ grep 'un' < fichier-1.txt
un
$ grep 'un' fichier-1.txt
un
La première ligne pose problème. On demande en effet au shell de lancer cat, qui va effectuer :
- soit une opération que le shell peut effectuer – à savoir envoyer le contenu d’un fichier sur l’entrée standard d’une commande (comme sur la deuxième ligne) ;
- soit une opération que grep peut effectuer – à savoir ouvrir un fichier et lire son contenu (comme sur la troisième ligne).
Si la sortie de ces trois commandes est équivalente, la première revient à lancer deux programmes en plus de notre shell, et à créer un tube (|
) entre les deux, alors que seule la commande grep est lancée dans les deuxième et troisième lignes.
Les utilitaires en ligne de commande prennent ainsi souvent en argument le nom d’un ou plusieurs fichiers, rendant inutile le recours à cat. C’est par exemple le cas de cut, sed, head ou sort.
D’autres commandes ne prennent pas de nom de fichier en argument, mais lisent toutefois depuis l’entrée standard. Cela permet, là encore, d’utiliser l’opérateur de redirection du shell (< fichier.txt
) plutôt que cat.
C’est par exemple le cas de tr. On écrira tr ' ' '\n' < fichier.txt
plutôt que cat fichier.txt | tr ' ' '\n'
.
Bien sûr, rien n’interdit de faire cat fichier | tr ' ' '\n'
quand on bricole dans son shell. Cela marche tout autant. Mais cette façon de faire indique que quelque chose nous échappe dans le fonctionnement de notre ligne de commande. Et si l’on tombe sur un informaticien un peu snob ou pointilleux, il est tout à fait possible qu’il nous signale que nous venons de nous livrer à un useless use of cat (« utilisation inutile de cat ») et nous renvoie vers telle ou telle page Web pour de plus amples informations.
À quoi cat peut servir
Afficher le contenu d’un fichier dans le terminal
La commande convient bien à l’affichage de fichiers très courts, et dans lesquels on ne veut pas intervenir. Sinon, mieux vaut se tourner vers un visionneur (pager) comme less, vers un éditeur de texte ou vers des commandes comme head ou tails.
Combiner plusieurs fichiers en un seul
Imaginons que je travaille à un texte assez long, composé de plusieurs parties, chacune enregistrée dans un fichier à part (1.txt
, 2.txt
, etc.). Avec cat, je peux rassembler toutes ces parties en un fichier unique :
$ cat *.txt > texte-complet.txt
Je peux après coup ajouter une nouvelle partie :
$ cat nouvelle-partie.txt >> texte-complet.txt
C’est plus rapide et moins risqué que de faire des copier-coller.
Attention toutefois à la structure des noms de fichiers qu’on veut traiter :
lorsque le shell procède à l’expansion des noms de fichiers, il insère dans la ligne de commande le nom des fichiers triés par ordre alphabétique. Si nous disposons de dix fichiers, allant de 1.txt
à 10.txt
, nous rencontrerons un problème :
$ printf "%s\n" *.txt | head -n3
1.txt
10.txt
2.txt
La chaîne de caractères 10.txt
vient avant 2.txt
dans l’ordre alphabétique. Avec une commande comme cat *.txt > texte-complet.txt
, l’ordre des chapitres sera faussé.
Pour obtenir le résultat attendu, il faut numéroter les fichiers sur deux chiffres, de 01.txt
à 10.txt
.
Visualiser des caractères non affichables et autres fonctionnalités ésotériques
Les implémentations de cat proposées sur macOS ou GNU/Linux (les deux systèmes que j’utilise) disposent de plusieurs options permettant de modifier le comportement du programme. Ces options ne figurent toutefois pas dans le jeu d’options de la norme POSIX.
Je ne rentre pas dans les détails, mais voici quelques exemples rapides.
Les options -n
et -b
numérotent les lignes, en prenant ou pas en compte les lignes vides. Mais on peut obtenir le même résultat avec la commande nl
.
Une option -s
permet de remplacer une suite de plusieurs lignes vides par une unique ligne vide.
D’autres options permettent de visualiser des caractères non affichables, tels que les tabulations, les retours chariot, les saut de ligne, etc.
$ printf 'a\tb\r\nc\r\n' | cat
a b
c
$ printf 'a\tb\r\nc\r\n' | cat -v
a b^M
c^M
$ printf 'a\tb\r\nc\r\n' | cat -vt
a^Ib^M
c^M
$ printf 'a\tb\r\nc\r\n' | cat -vte
a^Ib^M$
c^M$
L’existence de ces options a suscité un article célèbre de Rob Pike et Brian Kernighan, paru en 1984 et intitulé Program Design in the UNIX Environment. Les deux auteurs y critiquent l’ajout de ces fonctionnalités.
Such a modification confuses what cat’s job is – concatenating files – with what it happens to do in a common special case – showing a file on the terminal. A UNIX program should do one thing well, and leave unrelated tasks to other programs. cat’s job is to collect the data in files. Programs that collect data shouldn’t change the data; cat therefore shouldn’t transform its input.
Ce genre de modification crée une confusion entre le propos de cat – concaténer des fichiers – et ce qu’il fait dans une situation précise mais fréquente – afficher dans le terminal le contenu d’un fichier. Un programme UNIX devrait effectuer proprement une seul tâche, et laisser d’autre programmes se charger de celles qui sont sans rapport avec celle-ci. Le boulot de cat, c’est de récupérer les données qui se trouvent dans des fichiers. Un programme qui récupère des données ne devrait pas modifier ces données ; aussi cat ne devrait-il pas modifier son entrée.
Entrer rapidement du texte dans fichier
On peut enfin utiliser cat pour placer rapidement du texte dans un fichier (penser à entrer Ctrl-D pour signaler la fin de notre saisie) :
$ cat > la-conscience.txt
L’œil était dans la tombe et regardait Caïn.
$ cat la-conscience.txt
L’œil était dans la tombe et regardait Caïn.
Post-scriptum : la commande tac
Je signale l’existence de la commande tac, qui envoie sur la sortie standard des fichiers en inversant l’ordre des lignes :
$ cat fichier.txt
ceci
cela
$ tac fichier.txt
cela
ceci
L’ordre d’affichage des fichiers demeure toutefois celui de leur noms sur la ligne de commande :
$ cat fichier2.txt
ici
là
$ tac fichier.txt fichier2.txt
cela
ceci
là
ici