Copyright © 2004 William Park
Copyright © 2004 Antonin Mellier
Copyright © 2004 Joëlle Cornavin
Article paru dans le n°109 de la Gazette Linux de décembre 2004.
Traduction française par Antonin Mellier
<antonin POINT mellier CHEZ laposte POINT net>
.
Relecture de la traduction française par Joëlle Cornavin
<jcornavi CHEZ club TIRET internet POINT fr>
.
Article publié sous Open Publication License. La Linux Gazette n'est ni produite, ni sponsorisée, ni avalisée par notre hébergeur principal, SSC, Inc.
strcat
, strcpy
, strlen
et strcmp
Dans un précédent article, j'ai présenté des fonctions shell qui émulent les fonctions C strcat(3)
, strcpy(3)
, strlen(3)
et strcmp(3)
. Comme la tâche principale du shell est d'analyser du texte, une solution shell pure était possible pour les opérations sur les chaînes de caractères trouvées dans <string.h>. Néanmoins, c'est rare. Il n'est pas toujours possible d'émuler du C en shell, en particulier pour accéder à des bibliothèques système bas niveau et à des applications tierces. Même si c'était possible, vous réinventeriez la roue en ignorant le travail qui a été effectué pour les bibliothèques C. De plus, les scripts shell sont, sans exception, des ordres de grandeur plus lents. Cependant, le shell a l'avantage de permettre un développement et une maintenance aisés, parce qu'il est plus facile à écrire et à lire.
Ce dont on a besoin, ensuite, est la capacité d'écrire un script d'invocation (shell wrapper) avec des liaisons vers des routines C. Un mécanisme shell qui permet d'écrire une extension C est appelé builtin, par exemple read, echo et printf, etc. Quand certaines fonctionnalités requièrent des changements dans la façon dont le shell interprète une expression, alors des modifications doivent être apportées dans le code d'analyse syntaxique (parsing) du shell. Quand vous avez besoin de vitesse, une extension C est ce qu'il y a de mieux.
Mon correctif (patch) pour le shell Bash 3.0 est disponible aux adresses suivantes :
http://freshmeat.net/projects/bashdiff/ : présentation
http://home.eol.ca/~parkw/index.html#bash : documentation
La dernière archive tar (tarball) bashdiff-1.11.tar.gz contient deux fichiers diff :
bashdiff-core-1.11.diff porte sur les fonctionnalités qui seront compilées statiquement dans le shell. Il en ajoute de nouvelles en modifiant le code d'analyse syntaxique de Bash. Cette opération est entièrement réversible en ce sens qu'aucune signification existante n'est changée. Donc, ce qui fonctionne dans votre ancien shell, fonctionnera aussi dans le nouveau. Par exemple, il ajoute :
une nouvelle expansion d'accolades {a..b} : génération d'entiers et de lettres, paramètres positionnels et expansion des tableaux
une nouvelle expansion de paramètres ${var|...} : filtrage du contenu, inclusion des listes (comme Python)
une nouvelle substitution de commandes $(=...) : ancrage de la virgule flottante dans Awk
une extension des instructions case : expressions régulières, suites, sections then/else
une extension pour les boucles while/until : sections then/else, variables multiples pour les boucles for
des blocs de test avec exception d'entier (comme en Python)
un nouvel opérateur <<+ here-document : indentation relative
bashdiff-william-1.11.diff porte sur les builtins dynamiquement chargeables (loadables) qui sont disponibles à part dans votre session shell. Il ajoute de nouvelles commandes, pour faire l'interface avec les bibliothèques système/applications et pour fournir un wrapper plus rapide pour les opérations courantes. Par exemple, il ajoute :
une extension aux builtins read/echo : lignes DOS
des wrappers sscanf(3)
, <string.h> et <ctype.h>, la conversion ASCII/chaînes de caractères
un nouveau builtin raise
pour les blocs try
une découpe/un collage de tableaux, un filtre/map/zip/unzip de tableaux (comme en Python)
des opérations sur la fonction regex(3)
: correspondance, découpage, recherche, remplacement, rappel
un générateur de modèles HTML (comme PHP, JSP, ASP)
une interface pour les base de données GDBM, SQLite, PostgreSQL, et MySQL
une interface d'analyseExpat XML
opérations piles/files d'attente sur les tableaux et les paramètres positionnels
graphique/tracé x-y
Toutes les fonctionnalités sont décrites dans les fichiers d'aide internes du shell auquel on peut accéder avec la commande help.
Avant de vous lancer dans un shell corrigé, vous devez savoir comment compiler depuis le source, étant donné que le correctif s'effectue par rapport à l'arborescence source. Voici les étapes nécessaires pour télécharger et compiler le shell standard Bash 3.0 :
wget ftp://ftp.gnu.org/pub/gnu/bash/bash-3.0.tar.gz tar -xzf bash-3.0.tar.gz cd bash-3.0 ./configure make |
On a maintenant un bash binaire exécutable qui est comme votre bash actuel, normalement /bin/bash . Vous pouvez l'essayer en saisissant :
./bash # en utilisant un Bash-3.0 fraîchement compilé date ls exit # revient à votre session de shell antérieure |
Pour compiler mon shell corrigé, les étapes sont sensiblement les mêmes que précédemment. Téléchargez une archive tar (tarball), appliquez mon correctif dans l'arborescence source (à partir des étapes précédentes) et compilez. bashdiff.tar.gz pointera toujours sur le dernier correctif, qui est ici bashdiff-1.10.tar.gz.
wget http://home.eol.ca/~parkw/bashdiff/bashdiff-1.10.tar.gz tar -xzf bashdiff-1.10.tar.gz mv bash-3.0 bash # ce n'est plus un Bash-3.0 standard cd bash make distclean patch -p1 < ../bashdiff-core-1.10.diff patch -p1 < ../bashdiff-william-1.10.diff autoconf ./configure make make install # en tant que root cd examples/loadables/william make make install # en tant que root ldconfig # en tant que root |
Maintenant, vous avez :
bash, qui est le shell principal exactement comme avant ; il sera installé dans /usr/local/bin/bash et
william.so, qui est un objet partagé contenant des modules chargeables à la demande (loadables) ; il sera installé dans /usr/local/lib/libwilliam.so avec un lien symbolique vers /usr/local/lib/william.so. Dans la version 1.10, il y a 33 builtins chargeables à la demande, à savoir :
Lsql
Msql
Psql
gdbm
xml
array
arraymap
arrayzip
arrayunzip
basp
match
vplot
pp_append
pp_collapse
pp_flip
pp_overwrite
pp_pop
pp_push
pp_rotateleft
pp_rotateright
pp_set
pp_sort
pp_swap
pp_transpose
sscanf
strcat
strcpy
strlen
strcmp
tonumber
tostring
chnumber
isnumber
Si votre shell comporte enable -[fd]
, alors vous pouvez charger/décharger des commandes builtin dynamiquement, d'où le nom. La procédure est simple. Par exemple :
enable -f william.so vplot |
chargera la commande vplot depuis la bibliothèque partagée william.so que vous venez de compiler et d'installer. Utilisez ./william.so si vous ne l'avez pas encore installée. Une fois chargée, vous pouvez l'utiliser exactement comme les builtins standard qui sont liées statiquement dans le shell. Donc :
help vplot help -s vplot |
affichera la version longue et courte du fichier d'aide de la commande, alors que :
vplot [-0 -x columns -y lines -X xtitle -Y ytitle] {xy | x y | x1 y1 x2 y2 ...} |
et
x=( `seq -100 100` ) y=( `for i in ${x[*]}; do echo $((i*i)); done` ) # y = x^2 vplot x y |
affichera le tracé x-y d'une parabole dans votre terminal. Pour la décharger, saisissez :
enable -d vplot |
Les modules chargeables à la demande (loadables) sont pratiques si vous voulez juste charger les builtins dont vous avez besoin et si vous ne souhaitez ou ne pouvez pas changer votre shell de connexion. De plus, les modules chargeables sont plus faciles à compiler à partir d'une version précédente (incrémentiellement), ce qui est important puisque de nouveaux builtins sont ajoutés ou mis à jour plus souvent que le code d'analyse principal du shell.
Néanmoins, vous pouvez être amené à compiler et inclure le tout dans un seul exécutable, comme sous Windows par exemple. Pour compiler un fichier binaire « tout-en-un », vous devez saisir quelques lignes de code supplémentaires. Il faut générer le fichier binaire par défaut du bash, car nous avons besoin de tous les fichiers .h et .o.
cd bash make bash make bash+william # tout en un make install-bin # installe seulement 'bash', 'bashbug', 'bash+william' |
Ici, bash+william est identique au bash, sauf que tous les builtins sont liés statiquement dedans. Je recommande le fichier binaire simple bash+william aux débutants, car il évite d'avoir à se rappeler de ce qu'il faut charger et décharger. Tout est là sous la main.
strcat
, strcpy
, strlen
et strcmp
Dans un apam_count, ham_count, atime) VALUES ('1','1–/:','0','1','1224827014') ON DUPLICATE KEY UPDATE spam_count = GREATEST(spam_count + '0', 0), ham_count = GREATEST(ham_count + '1', 0), atime = GREATEST(atime, '1224827014')¶`I ¾ à fE @ std pourriels INSERT INTO bayes_token (id, token, spam_count, ham_count, atime) VALUES ('1','eQË','0','1','1224827010') ON DUPLICATE KEY UPDATE spam_count = GREATEST(spam_count + '0', 0), ham_count = GREATEST(ham_count + '1', 0), atime = GREATEST(atime, '1224827010')¶`I ¾ ÂÄ iE @ std pourriels INSERT INTO bayes_token (id, token, spam_count, ham_count, atime) VALUES ('1',' ;.q ','0','1','1224827012') ON DUPLICATE KEY UPDATE spam_count = GREATEST(spam_count + '0', 0), ham_count = GREATEST(ham_count + '1', 0), atime = GREATEST(atime, '1224827012')¶`I ¾ €Æ jE @ std pourriels INSERT INTO bayes_token (id, token, spam_count, ham_count, atime) VALUES ('1','¶{² ','0','1','1224827014') ON DUPLICATE KEY UPDATE spam_count = GREATEST(spam_count + '0', 0), ham_count = GREATEST(ham_count + '1', 0), atime = GREATEST(atime, '1224827014')¶`I ¾ >È fE @ std pourriels INSERT INTO bayes_token (id, token, spam_count, ham_count, atime) VALUES ('1','øê=q','0','1','1224827010') ON DUPLICATE KEY UPDATE spam_count = GREATEST(spam_count + '0', 0), ham_count = GREATEST(ham_count + '1', 0), atime = GREATEST(atime, '1224827010')¶`I ¾ üÉ iE @ std pourriels INSERT INTO bayes_token (id, token, spam_count, ham_count, atime) VALUES ('1','íã(Œ','0','1','1224827012') ON DUPLICATE KEY UPDATE spam_count = GREATEST(spam_count + '0', 0), ham_count = GREATEST(ham_count + '1', 0), atime = GREATEST(atime, '1224827012')¶`I ¾ ºË jE @ std pourriels INSERT INTO bayes_token (id, token, spam_count, ham_count, atime) VALUES ('1','D‡Âa(','0','1','1224827014') ON DUPLICATE KEY UPDATE spam_count = GREATEST(spam_count + '0', 0), ham_count = GREATEST(ham_count + '1', 0), atime = GREATEST(atime, '1224827014')¶`I ¾ xÍ iE @ std pourriels INSERT INTO bayes_token (id, token, spam_count, ham_count, atime) VALUES ('1','¬Wk¦','0','1','1224827012') ON DUPLICATE KEY UPDATE spam_count = GREATEST(spam_count + '0', 0), ham_count = GREATEST(ham_count + '1', 0), atime = GREATEST(atime, '1224827012')¶`I ¾ 6Ï fE @ std pourriels INSERT INTO bayes_token (id, token, spam_count, ham_count, atime) VALUES ('1','¬Wk¦','0','1','1224827010') ON DUPLICATE KEY UPDATE spam_count = GREATEST(spam_count + '0', 0), ham_count = GREATEST(ham_count + '1', 0), atime = GREATEST(atime, '1224827010')¶`I ¾ ôÐ jE @ std pourriels INSERT INTO bayes_token (id, token, spam_count, ham_count, atime) VALUES ('1','Ì|·','0','1','122482701ajuscule un mot, par exemple, est extrêmement verbeux dans un shell normal :
word=abc123 first=`echo ${word:0:1} | tr 'a-z' 'A-Z'` rest=`echo ${word:1} | tr 'A-Z' 'a-z'` echo $first$rest |
ce qui ne fonctionne que dans les paramètres régionaux (locales) en anglais, à cause des registres explicites [a-z] et [A-Z]. En revanche, en C, il suffit d'appeler isupper(3)
, islower(3)
, toupper(3)
et tolower(3)
, qui fonctionnent avec tous les paramètres régionaux que la bibliothèque C prend en charge.
Ce dont nous avons besoin, ce sont des scripts d'invocation shell wrappers) pour les fonctions C standard suivantes : toupper(3)
, tolower(3)
, toascii(3)
, toctrl()
, isalnum(3)
, isalpha(3)
, isascii(3)
,isblank(3)
,iscntrl(3)
, isdigit(3)
, isgraph(3)
, islower(3)
, isprint(3)
, ispunct(3)
, isspace(3)
, isupper(3)
, isxdigit(3)
et isword()
. La plupart d'entre elles étant définies dans <ctype.h>, les opérations sur les caractères peuvent être effectuées simplement et efficacement.
tonumber chaine...
tostring [-v var] nombre...
J'ai décidé de suivre la même méthode que la commande od et de convertir les chaînes de caractères en suites de nombres ASCII (0-255). tonumber affiche le nombre ASCII de chaque caractère d'une chaine, un peu comme od -A n -t dC. Ces nombres sont maintenant séparés par des espaces, ce qui facilite les choses pour travailler avec en shell. À l'inverse, tostring convertit chaque nombre en caractère ASCII. Si l'option -v
est spécifiée, la sortie sera enregistrée dans la variable var. Par exemple :
tonumber ABC # 65 66 67 tostring 65 66 67 # ABC |
Une des caractéristiques remarquables de tostring est qu'elle peut interpréter l'octet nul (\0), si bien que l'on peut écrire des scripts shell pour décrire des données binaires, comme :
tostring 65 00 97 | od -c # A \0 a |
chnumber { toupper | tolower | toascii | toctrl } [nombre...]
Versions shell de toupper(3)
, tolower(3)
et autres, incluses dans <ctype.h>. Celles-ci lisent les nombres et les affichent après leur conversion en fonction d'options qui ont le même nom que celles des fonctions C correspondantes, par exemple :
tonumber aA # 97 65 chnumber toupper 97 65 # convertir en majuscules : 65 65 chnumber tolower 97 65 # convertir en minuscules : 97 97 |
isnumber { alnum | alpha | ascii | blank | cntrl | digit | graph | lower | print | punct | space | upper | xdigit | word } [number...]
Versions shell de isupper(3)
, islower(3)
, et autres incluses dans <ctype.h>. Elles lisent les nombres et renvoient succès (success) ou échec (failure) selon les options qui ont été choisies. Ces fonctions sont les mêmes que les fonctions C correspondantes avec le préfixe is en moins..
isnumber upper 65 # 'A' est-il en majuscules ? isnumber upper 97 # 'a' est-il en majuscules ? isnumber alnum 97 98 99 49 50 51 # 'abc123' est-il alphanumérique ? |
Ainsi, l'exemple précédent de mise en majuscules d'un mot devient :
set -- `tonumber abc123` set -- `chnumber toupper $1; shift; chnumber tolower $*` tostring $* 10 # ajouter \n à la fin |
ce qui est beaucoup plus rapide et compréhensible.
Maintenant que Bash couvre plutôt bien les fonctions de <string.h> et <ctype.h>, vous pouvez effectuer des opérations sur des caractères et des chaînes de caractères dans un script shell pratiquement de la même manière que dans du code C. Le texte ainsi que les données binaires sont gérées avec facilité et cohérence. Ces quelques modifications à elles seules représentent déjà une grande amélioration par rapport au shell standard.
Une des premières choses que l'on apprend dans tout langage est la lecture et l'affichage. En C, on utilise les fonctions printf(3)
, scanf(3)
et autres définies dans <stdio.h>. Pour afficher dans le shell, on fait appel aux builtins echo et printf. Curieusement, pourtant, il manque une version shell de scanf(3)
. Par exemple, pour analyser les 4 nombres de 11.22.33.44, vous pouvez saisir :
IFS=. read a b c d <<< 11.22.33.44 |
Cependant, si votre champ n'est pas aussi bien délimité que ci-dessus, les choses se compliquent.
J'ai ajouté la version shell de la fonction C sscanf(3)
:
sscanf input format var1 [... var9]
Du fait que le shell n'a que des données de type chaînes de caractères, il ne prend en charge que des formats de chaînes, c'est-à-dire %s, %c, %[...], %[^...] et jusqu'à 9 variables. Donc, vous pouvez analyser des chaînes formatées tout comme vous le feriez en C, par exemple :
sscanf 11.22.33.44 '%[0-9].%[0-9].%[0-9].%[0-9]' a b c d declare -p a b c d # a=11 b=22 c=33 d=44 sscanf 'abc 123 45xy' '%s %s %[0-9]%[a-z]' a b c d declare -p a b c d # a=abc b=123 c=45 d=xy |
De temps en temps, il est nécessaire d'afficher et de lire des lignes DOS qui se terminent par \r\n (CR/NL). Bien que vous puissiez afficher \r explicitement, l'insertion automatique de \r juste devant \n est difficile à réaliser en shell. Pour la lecture, vous devez explicitement supprimer le \r de fin.
J'ai créé un correctif des builtins standard echo et read pour qu'ils puissent lire et écrire des lignes DOS :
echo [-....] -D
[arg ...]
read [-...] -D
[nom ...]
Par exemple :
echo abc | od -c # a b c \n echo -D abc | od -c # a b c \r \n read a b <<< $'11 22 \r' # a=11 b=$'22 \r' read -D a b <<< $'11 22 \r' # a=11 b=22 |
Souvent, il est nécessaire d'analyser des lignes et de travailler avec des variables de type Awk comme NF, NR, $1, $2, ..., $NF
. Cependant, quand vous utilisez Awk, il est difficile de ramener ces variables dans le shell ; vous devez les écrir en syntaxe shell dans un fichier temporaire pour ensuite le reprendre. Cela rend fastidieux de faire des allers-retours entre le shell et Awk.
J'ai créé un correctif du builtin standard read pour offrir une émulation simple de Awk, en créant les variables NF
et NR
, et en affectant les champs à $1
, $2
, ..., $NF
.
read [-...] -A
[nom ...]
Par exemple :
IFS=. read -A <<< 11.22.33.44 echo $#: $* # 4: 11 22 33 44 declare -p NF NR |
En outre, comme dans Awk, chaque appel pour lire -A incrémentera NR
.
<< est l'opérateur de redirection d'entrée, où l'entrée standard est tirée du texte même du source du script. << conservera les espaces blancs de début et <<- supprimera toutes les tabulations de début. Le problème avec <<- est que l'indentation relative est perdue.
J'ai ajouté un nouvel opérateur <<+ qui conserve l'indentation par tabulation du here-document par rapport à la première ligne. Il est accessible directement via le shell (c'est-à-dire ./bash ou /usr/local/bin/bash), parce que le correctif est intégré dans le code d'analyse syntaxique principal. Donc :
cat <<+ EOF first line second line EOF |
affichera
first line second line |
Bash 3..0 (et Zsh) comportent l'expression {a..b} qui génère une séquence d'entiers en tant qu'élément de l'expansion d'accolades, mais on ne peut pas utiliser la substitution de variables car l'expression {a..b} doit contenir explicitement des entiers.
Mon correctif étend l'expansion d'accolades de façon à inclure la substitution de variables, de paramètres et de tableaux, de même qu'un générateur de séquences de lettres uniques. Par exemple ;:
a=1 b=10 x=a y=b echo {1..10} echo {a..b} echo {!x..!y} # utiliser 'set +H' pour supprimer l'expansion de ! set -- `seq 10` echo {**} echo {##} echo {1..#} z=( `seq 10` ) echo {^z} |
donneront toutes le même résultat, c'est-à-dire 1 2 ... 10. D'autres détails sont disponibles dbayes_token (id, token, spam_count, ham_count, atime) VALUES ('1',' «Ôä[','0','1','1224827012') ON DUPLICATE KEY UPDATE spam_count = GREATEST(spam_count + '0', 0), ham_count = GREATEST(ham_count + '1', 0), atime = GREATEST(atime, '1224827012')¶`I ¾ ,ž jE @ std pourriels INSERT INTO bayes_token (id, token, spam_count, ham_count, atime) VALUES ('1','_š)','0','1','1224827014') ON DUPLICATE KEY UPDATE spam_count = GREATEST(spam_count + '0', 0), ham_count = GREATEST(ham_count + '1', 0), atime = GREATEST(atime, '1224827014')¶`I ¾ êž fE @ std pourriels INSERT INTO bayes_token (id, token, spam_count, ham_count, atime) VALUES ('1','Btj6','0','1','1224827010') ON DUPLICATE KEY UPDATE spam_count = GREATEST(spam_count + '0', 0), ham_count = GREATEST(ham_count + '1', 0), atime = GREATEST(atime, '1224827010')¶`I ¾ ¨ž iE @ std pourriels INSERT INTO bayes_token (id, token, spam_count, ham_count, atime) VALUES ('1','¨4•H','0','1','1224827012') ON DUPLICATE KEY UPDATE spam_count = GREATEST(spam_count + '0', 0), ham_count = GREATEST(ham_count + '1', 0), atime = GREATEST(atime, '1224827012')¶`I ¾ fž fE @ std pourriels INSERT INTO bayes_token (id, token, spam_count, ham_count, atime) VALUES ('1','&P¤¼Í','0','1','1224827010') ON DUPLICATE KEY UPDATE spam_count = GREATEST(spam_count + '0', 0), ham_count = GREATEST(ham_count + '1', 0), atime = GREATEST(atime, '1224827010')¶`I ¾ $ž jE @ std pourriels INSERT INTO bayes_token (id, token, spam_count, ham_count, atime) VALUES ('1',';,`Œ7','0','1','1224827014') ON DUPLICATE KEY UPDATE spam_count = GREATEST(spam_count + '0', 0), ham_count = GREATEST(ham_count + '1', 0), atime = GREATEST(atime, '1224827014')¶`I ¾ âž iE @ std pourriels INSERT INTO bayes_token (id, token, spam_count, ham_count, atime) VALUES ('1','šÔÊ4ÿ','0','1','1224827012') ON DUPLICATE KEY UPDATE spam_count = GREATEST(spam_count + '0', 0), ham_count = GREATEST(ham_count + '1', 0), atime = GREATEST(atime, '1224827012')¶`I ¾ ž fE @ std pourriels INSERT INTO bayes_token (id, token, spam_count, ham_count, atime) VALUES ('1','õ’&â}','0','1','1224827010') ON DUPLICATE KEY UPDATE spam_count = GREATEST(spam_count + '0', 0), ham_count = GREATEST(ham_count + '1', 0), atime = GREATEST(atime, '1224827010')¶`I ¾ ^ž jE @ std pourriels INSERT INTO bayes_token (id, token, spam_count, ham_count, atime) VALUES ('1','QöÔ^','0','1','1224827014') ON DUPLICATE KEY UPDATE spam_count = GREATEST(spam_count + '0', 0), ham_count = GREATEST(ham_count + '1', 0), atime = GREATEST(atime, '1224827014')¶`I ¾ !ž iE @ std pourriels INSERT INTO bayes_token (id, token, spam_count, ham_count, atime) ="literal" >?, alors la chaîne originelle est incluse dans l'expansion de paramètres uniquement si command var renvoie success (0). Si tel n'est pas le cas, alors elle est supprimée de l'expansion. Le contenu reste inchangé mais pouvez décider de l'inclure ou non. Par conséquent, ${var|?true} sera équivalent à ${var}, puisque true renvoie toujours success (0). Par exemple :
b=( `date` ) func () { [[ $1 == [A-Z]* ]]; } echo ${b[*]|?func} # seulement des mots en majuscules set -- `date` func () { [[ $1 == *[!0-9]* ]]; } echo ${*|?func} # seulement des caractères autres que des nombres |
${var|/regex}
${var|=glob}
${var|~regex}
${var|!glob}
Si l'on veut faire un filtrage spécial, vous pouvez spécifier un motif [pattern] glob(7)
ou regex(7)
à faire correspondre par rapport à certains élements de la variable : ${var|=glob} et ${var|/regex} inclueront la chaîne uniquement s'il y a une correspondance ; à l'inverse, ${var|!glob} et ${var|~regex} inclueront la chaîne uniquement s'il n'y a pas de correspondance. Les
exemples ci-dessus peuvent s'écrire sous la forme suivante :
b=( `date` ) echo ${b[*]|=[A-Z]*} # seulement des mots en majuscules set -- `date` echo ${*|=*[!0-9]*} # seulement des caractères autres que des nombres |
${var|:a:b}
Vous pouvez extraire une plage [a:b] de style Python à l'aide de ${var|:a:b}, qui est identique à la syntaxe shell standard ${var:a:n}. Si var est une chaîne, alors ce sera une sous-chaîne ; si var est une liste, alors ce sera une sous-liste. Par exemple :
a=0123456789 echo ${a|::3} ${a|:-3:} ${a|:1:-1} # 012 789 12345678 set -- {a--z} echo ${*|::3} ${*|:-3:} ${*|:1:-1} |
affichera, respectivement, les trois premiers caractères ou éléments de la liste, les trois derniers et tous les éléments, sauf le premier et le dernier.
${var|*n}
Si vous devez dupliquer une chaîne ou une liste, alors ${var|*n} copiera la chaîne ou la liste n fois. Par exemple :
a=abc123 echo ${a|*3} # 3 fois set -- a b c echo ${*|*2+3} # 5 fois |
La syntaxe standard de l'instruction case est la suivante :
case WORD in glob [| glob]...) COMMANDS ;; ... esac |
J'ai étendu la syntaxe à :
case WORD in glob [| glob]...) COMMANDS ;; regex [| regex]...)) COMMANDS ;; ... esac |
de sorte que la liste motif sera interprétée en tant que regex si l'expression se termine par une double parenthèse )). Pour le reste, le fonctionnement reste le même.. Bien que Bash 3.0 ait [[ string =~ regex ]], une instruction case est encore une meilleure syntaxe pour deux motifs ou plus, ou si vous devez tester à la fois glob et regex dans le même contexte.
Alors que glob reconnaît la chaîne entière pour renvoyer success, regex peut reconnaître une sous-chaîne. S'il y a une correspondance, alors la variable tableau SUBMATCH
contiendra la sous-chaîne correspondante dans SUBMATCH[0] et tout groupe entre parenthèses dans le motif regex dans SUBMATCH[1], SUBMATCH[2], etc. Par exemple :
case .abc123. in '([a-z]+)([0-9]+)' )) echo yes ;; esac declare -p SUBMATCH |
aboutira à une correspondance, et
SUBMATCH[0]=a<bc123, l'expression régulière entière ([a-z]+)([0-9]+)
SUBMATCH[1]=abc, le premier groupe ([a-z]+)
SUBMATCH[2]=123, le second groupe ([0-9]+)
En Zsh et en Ksh, vous pouvez continuer avec la liste de commandes suivante si vous utilisez ;& au lieu de ;;. Donc :
case WORD in motif1) commande1 ;;& motif2) commande2 ;; ... easc |
command1 sera exécutée si pattern1 est vérifié. Ensuite, l'exécution continuera avec command2, puis la liste de commande suivante, tant qu'elle ne rencontrera pas de double point-virgule. Or, avec le Bash c'est également possible.
De plus, si vous terminez la liste de commande par ;;&,
case WORD in motif1) commande1 ;;& motif2) commande2 ;; ... easc |
commande1 s'exécutera si motif1 correspond. L'exécution continuera ensuite en testant motif2 au lieu de sortir de l'instruction case. Par conséquent, il testera toute la liste de motifs de la liste, qu'il y ait ou non une correspondance avérée. Zsh et Ksh n'offrent pas cette fonctionnalité.
Souvent, il est nécessaire de connaître la condition de sortie d'une instruction case. Vous pouvez utiliser *) comme motif par défaut, mais il n'est pas évident de déterminer s'il y a eu une correspondance puisque vous sortez de l'instruction case. Avec mon correctif, vous pouvez ajouter une section then et else optionnelle à la fin de l'instruction case juste après esac et de traiter l'instruction case comme si c'était une vraie instruction if . Voici à quoi ressemblera la nouvelle syntaxe :
case ... in case ... in case ... in ... ... ... esac then esac then esac else COMMANDS COMMANDS COMMANDS else fi fi COMMANDS fi |
où esac then et esac else ne peuvent pas être séparés par ; ou par un retour à la ligne. Le then-COMMANDS sera exécuté si il y a une correspondance, sinon ce sera else-COMMANDS s'il n'y en a pas.
Par exemple :
case abc123 in [A-Z]*) echo matched ;; esac then echo yes else echo no # pas de correspondance fi |
affichera no, alors que
case Xabc123 in [A-Z]*) echo matched ;; # correspondance esac then echo yes # correspondance else echo no fi |
affichera matched et yes.
Dans le shell standard, on ne peut utiliser qu'une seule variable dans une boucle for. J'ai ajouté une syntaxe à plusieurs variables, de sorte que
for a,b,c in {1..10}; do echo $a $b $c done |
affichera
1 2 3 4 5 6 7 8 9 10 |
comme vous l'espérez. Ici, les variables doivent être séparées par des virgules. S'il y a trop peu d'éléments à affecter dans la dernière itération, les variables restantes seront affectées à la chaîne vide (null).
Comme dans l'instruction case, vous devez souvent savoir si vous êtes sorti de la boucle normalement ou en employant l'instruction break. Avec mon correctif, vous pouvez ajouter des sections then et else optionnelles à la fin des boucles for, while et until juste après done. Voici à quoi ressemblera la nouvelle syntaxe :
[for|while|until] ...; do [for|while|until] ...; do [for|while|until] ...; do ... ... ... done then done then done else COMMANDS COMMANDS COMMANDS else fi fi COMMANDS fi |
où done then et done else ne peuvent pas être séparés par ; ou par un retour à la ligne. Ici, then-COMMANDS sera exécuté si la boucle s'est terminée normalement, sinon else-COMMANDS sera exécuté si break a été utilisé. Par « normalement », je veux dire que la boucle for a épuisé toute la liste d'éléments, que le test sur le while a échoué ou que le test until a réussi.
Par exemple :
for i in 1 2 3; do echo $i break done then echo normal else echo used break # 1 fi |
affichera 1 uniquement pour la première itération, puis il sortira de la boucle. Mais :
for i in 1 2 3; do echo $i done then echo normal # 1 2 3 else echo used break fi |
affichera 1 2 3 et la condition de sortie s'effectuera normalement. Il en sera de même pour les boucles while et until.
La capacité de tester la condition de sortie améliore la lisibilité des scripts shell, car il n'est plus nécessaire d'utiliser une variable comme drapeau (flag). Python possède un mécanisme similaire pour tester la condition de sortie d'une boucle, mais il utilise la valeur de retour du test. On a donc une sortie de la boucle while quand le test échoue et Python utilise else comme condition normale de sortie, ce qui crée un peu de confusion.
En pratique, tous les langages modernes ont la capacité de lever une exception pour sortir d'un code très imbriqué, traiter les erreurs ou faire des sauts multipoint. J'ai ajouté un nouveau bloc try au Bash qui saisira les exceptions levées par le nouveau builtin raise.
raise [n]
try
COMMANDS done in NUMBER [| NUMBER].... ) COMMANDS ;; ... esac |
où done in ne peut pas être séparé par ; ou par un retour à la ligne. En outre, la liste de motif, dans l'instruction de type « instruction case », doit être explicitement un nombre entier.
Ceci combine les éléments des boucles, le builtin de sortie et l'instruction cas. À l'intérieur d'un bloc try, le builtin raise peut être utilisé pour lever une exception sur un entier. L'exécution sortie ensuite du bloc try, exactement comme pour quitter les boucles for/until/while. On peut utiliser une instruction de type case pour prendre capturer l'exception. Si l'exception est capturée, alors elle est réinitialisée et l'exécution continue en suivant le bloc try. Si l'exception n'est pas capturée, alors l'exécution repart jusqu'à ce qu'elle soit capturée ou jusqu'à ce qu'il n'y ait plus de blocs try.
Par exemple :
try echo a while true; do # infinite loop echo aa raise echo bb done echo b done |
affichera a aa et
try echo a raise 2 echo b done in 0) echo normal ;; 1) echo raised one ;; 2) echo raised two ;; # raise 2 esac |
affichera a et l'exception sera 2.
Dans le prochain article, j'aborderai les builtins dynamiquement chargeables liés aux tableaux, le découpage des expressions régulières, l'interfaçage avec des bibliothèques externes comme une base de données SQL et un analyseur syntaxique XML et je traiterai aussi d'autres applications intéressantes comme les modèles (templates) HTML et d'un système de blocage de spam POP3.
J'ai appris Unix avec le shell Bourne originel. Et, après une expédition dans la jungle des langages, je suis revenu au shell. Dernièrement, j'ai contribué à de nouvelles fonctionnalités de Bash, rendant la monnaie de leur pièce aux autres langages de scriptage. Slackware est ma distribution favorite depuis le début, parce que je peux saisir au clavier. Dans ma boite à outils, j'ai Vim, Bash, Mutt, Tin, Tex/Latex, Python, Awk et Sed. Même ma ligne de commande est en mode Vi.