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.
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...
Oups, merci, je suis un grand distrait ;)
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
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 !
Effectivement ! Je vais corriger. J'ai testé cela il y a bien longtemps (2008), et coreutils a certainement pas mal évolué depuis.