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 :

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