jot et seq, créer des séquences en ligne de commande

Quand on écrit des scripts shell, il arrive fréquemment d'avoir besoin de générer des séquences de chiffres ou de lettres (nourrir une boucle, créer des noms de fichiers aléatoires ou non...).
Il existe pour cela deux outils, seq et jot. seq est la commande GNU, elle est donc très répandue (Linux). jot est une commande originaire du monde BSD (FreeBSD, Mac OS X, ...). Elle est moins répandue, et surtout moins connue, ce qui est bien dommage. En effet, jot est nettement plus puissante et versatile que seq.

Voyons quelques cas d'utilisation concrets de ces deux commandes, avec pour commencer une boucle for classique. Note : toutes les commandes présentées ici ont été testées dans un shell bash. La syntaxe des différentes boucles peut varier dans d'autres shells.
Imaginons que j'ai besoin d'une boucle qui tourne sur une liste de chiffres de 1 à 10, je peux très simplement faire ceci, et obtenir à chaque fois le même résultat exactement :

# syntaxe basique
for i in 1 2 3 4 5 6 7 8 9 10; do
	# mon travail
	echo $i
done

# syntaxe avancée
for ((a=1; a<=10 ; a++)); do
	# mon travail
	echo $a
done

# autre syntaxe avancée
for a in {1..10}; do
	# mon travail
	echo $a
done

# syntaxe avec seq
for b in $(seq 1 10); do
	# mon travail
	echo $b
done

# syntaxe avec jot
for c in $(jot 10); do
	# mon travail
	echo $c
done

Pour ces cas simples, l'intérêt des commandes seq et jot n'est pas immédiat, d'autant que si la syntaxe basique est hors jeux pour les listes longues (1 à 1000 par exemple), les syntaxes avancées sauront en général s'en tirer.
Voyons comment faire maintenant si je souhaite faire une liste de 01 à 10 :

La syntaxe basique fonctionne toujours, mais je dois taper à la main 01, 02, ... C'est fastidieux. Les syntaxes avancées ne savent pas faire directement. Je peux par contre invoquer printf pour faire une réécriture de mes valeurs :

for a in {1..20}; do
	a=$(printf "%02d" $a)
	# mon travail
	echo $a
done

Pour faire ce type d'itération sans étapes intermédiaires il reste seq et jot.
L'ajout de l'option w à la commande seq permet de forcer le formatage de sorte que tous les nombres affichés aient la même largeur. En cas de besoin, un ou plusieurs 0 sont ajoutés devant le nombre pour compléter :

seq -w 1 10
# résultat :
01
02
...
09
10

Pour jot, le padding n'est pas complètement automatique, il faut en régler la largeur via le formatage de type printf. On obtient le même résultat qu'avec seq :

jot -w '%02d' 10

C'est plus délicat, mais cela nous permet de mettre le doigt sur une première limitation de seq : ce dernier ne sait pas utiliser d'autres options de printf que %e, %f et %g.
Ainsi, pour produire une liste de 00001 à 00010, je peux utiliser jot avec un formatage printf ou seq avec un formatage automatique (a priori, c'est une nouveauté apparue depuis la rédaction de cet article), mais pas seq :

jot -w '%05d' 10
# résultat :
00001
00002
...
00009
00010
seq -w 00010
# résultat :
00001
00002
...
00009
00010

Les deux commandes sont bien sûr capables de gérer une borne inférieure et une borne supérieure, mais leur gestion des incréments est une de leurs différences fondamentales. Vous pouvez indiquer à chacune de travailler de 1 à 1000 mais seq ne sait travailler que par incrément fixe :

# seq sait aller de 1 à *maximum* 1000 par saut de 250 :
seq 1 250 1000
# résultat :
1
251
501
751

Alors que jot travaille par défaut sur le nombre d'étapes :

# jot sait aller de 1 à 1000 strictement, en 5 étapes :
jot 5 1 1000
# résultat :
1
251
500
750
1000

jot fonctionne en fait en calculant l'incrément optimal. Ce dernier comporte parfois des décimales. Comme le format par défaut pour jot est l'entier, le résultat ci-dessus n'est qu'un arrondi des valeurs réelles de travail de jot. Si on force l'affichage des décimales, on obtient bien une série mathématiquement satisfaisante :

jot -w '%g' 5 1 1000
# résultat :
1
250.75
500.5
750.25
1000

Il sait aussi travailler sur un incrément explicite, comme seq :

# et jot donne alors le même résultat que seq :
jot 5 1 1000 250
# résultat :
1
251
501
751

Nous laissons définitivement seq derrière nous, car il ne sait travailler qu'avec des chiffres, alors que jot peut utiliser tout l'ASCII, générer des listes aléatoires, écrire une chaîne donnée x fois. Et si vous le jumelez avec son ami rs, il sait générer des matrices, des séries de mots de passe...
Voici quelques exemples tirés du man et d'ailleurs :

# les 128 caractères ASCII :
jot -c 128 0

# une ligne de 40 points : 
jot -s "" -b . 40

# l'alphabet en majuscule :
jot -c 26 A

# créer 5 noms de fichiers partiellement aléatoires :
jot -r -w '/tmp/fichier-%05d' 5 0 10000

# 10 mots de passe de 12 caractères ASCII aléatoires entre ! et ~ :
jot -r -c 120 33 126 | rs -g0 -w12

# une matrice 10x10, de 100 nombres tirés au hasard entre 1 et 100
jot -r 100 | rs 10 10 

Un peu d'histoire pour finir. Le nom de la commande jot ne cesse de faire râler les linuxiens qui doivent un jour ou l'autre travailler sur un système BSD. Il est indéniable que seq est un nom qui tombe sous le sens, de sequence à seq, il n'y a qu'un pas. Pour jot, ce n'est pas aussi immédiat.
Le nom de jot signifie en anglais "très petite quantité", et vient du latin iota. John A. Kunze, qui a développé jot, mais aussi rs et lam, m'a expliqué qu'il a écrit ces trois commandes il y'a très longtemps, en s'inspirant des opérateurs iota, reshape, et laminate du langage APL. Il a alors décidé de donner à ses commandes des noms typiquement "unixiens". Ainsi sont nées jot, rs et lam.

edit 1 : ajout du résultat pour certains exemples, correction d'une typo dans une commande.
edit 2 : correction d'un exemple, suppression de certaines boucles for inutiles, ajout de précision sur les incréments décimaux.
edit 3 : ajout des séquences du type {1..10} gérées par bash.
edit 4 : mise à jour au suejt de la capacité de seq à gérer le padding automatique.

À lire aussi :

5 comments

  1. Merci beaucoup pour cet article très plaisant à lire, et surtout très instructif. Je suis émerveillé de découvrir chaque jour ce que l'on peut faire avec le terminal et les commandes livrées en standard sous Mac OS X !. Toujours est il qu'en essayant ceci (jot -r -c 120 33 126 | rs -g0 -w12), j'ai obtenu 10 mots de 12 caractères...

  2. bonjour et bravo pour votre billet très intéressant et bien écrit.
    J'avais un petit trou de mémoire concernant les boucles et je suis tombé sur votre autre article "Quelques notions sur les boucles dans bash", laquelle m'a conduit ici pour la découverte de `jot`.

    P.S. : sous Debian et ArchLinux, le paquet à installer semble être athena-jot

  3. Merci pour ce comparatif, c'est tres instructif, et révélateur des differences subtiles entre les environnements unix et gnu.
    Neanmoins, j'ai noté une inexactitude (je n'ais pas cherché a savoir si il y en avais d'autres non plus...) concernant les possibilitées de seq.
    il est dit qu'on ne peut générer de suite de 00001 a 00010, avec du padding. hors la commande seq, dans son fonctionnement de formatage automatique utilise le format le plus long pour determiner le retour optimal de toute la suite.
    la commande "seq -w 00010" suffit a générer cette suite sus-mentionnée 'impossible".
    Cela a été testé avec GNU coreutils 8.12.197-032bb.
    Vous noterez egalement qu'il n'y as pas necessité de borne de depart, tel qu'illustré par cet exemple.
    Bravo, merci, et continuez a nous instruire !

  4. Effectivement ! Je vais corriger. J'ai testé cela il y a bien longtemps (2008), et coreutils a certainement pas mal évolué depuis.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *