Bonjour,
Je vais vous parler d'un sujet qui m'intéresse, le shell. A mon avis le shell actuel qu'on a sur nos machines (souvent bash) a deux gros problèmes, et j'aimerais bien ouvrir une discussion là dessus à l'occasion d'un développement récent que j'ai fait.
1. L'expansion des variables
Tout d'abord, le premier problème du shell sh ou compatible est à mon avis, outre sa syntaxe parfois bizarre, le problème de l'expansion des variables. Pour justifier mes griefs sur la syntaxe, je dirais simplement que je ne vois pas pourquoi les routines sont délimitées par des { } et pas les constructions if/for/case, pourquoi le if se termine par fi, le case par esac et le for/while par done et non pas rof/elihw.
penchons-nous sur le problème de l'expansion des variables. Le problème c'est que le shell découpe les mots après l'expansion des variables, c'est à dire que :
var="mot1 mot2"
cmd a b${var}c d
est équivalent à :
cmd a bmot1 mot2c d
et non pas :
cmd a "bmot1 mot2c" d
Alors que dans le code source on ne voit que 3 arguments passés à la commande cmd (a, b${var}c, d), il y en a en fait réellement 4 (a, b$mot1, mot2c, d).
Ce comportement peut être désirable dans certains cas, mais parfois vraiment malvenu. Par exemple lorsqu'on manipule des noms de fichiers contenant des espaces. En gros cela impose lorsqu'on ne sait pas précisément ce qu'il y a dans la variable à toujours l'entourer de quotes, c'est à dire écrire "$var" au lieu de $var.
Non seulement les débutants qui commencent ne comprennent pas toujours cette subtilité et oublient les quotes, mais bien souvent aussi, des gens expérimentés les oublient en les pensant inutiles.
Un exemple concret. la distribution ArchLinux utilise des script bash pour compiler les logiciels et créer des paquets, on les appelle PKGBUILD (un peu similaires aux ebuilds de gentoo). L'exemple le plus courrant et qui sert de modèle à tous les PKGBUILD est¹:
build() {
cd $startdir/src/$pkgname-$pkgver
./configure --prefix=/usr
make || return 1
make prefix=$startdir/pkg/usr install
}
Où $startdir est le dossier dans lequel les sources se trouvent, et où les fichiers sont installés.
Ici on peut voir que $startdir n'est jamais entre quotes, c'est à dire qu'il est impossible de compiler un package si le chemin courrant contient des espaces !!! En effet, la première commande (cd) échoue déjà car au moins deux arguments lui sont passés en paramètres.
C'est ma première critique concernant la robustesse des script shell.
2. La lenteur
Bash, c'est lent. On le voit avec les script d'init, et je l'ai remarqué plus récamment en remplaçant mon script monolithique ~/.xsession par de nombreux scripts modulaires qui me permet à chaque démarrage de:
- me demander mon mot de passe ssh
- lancer quelques programmes comme xbindkeys, mpd
- demander quelle session lancer (E17, GNOME, KDE, Terminal ...)
Et je constate facilement que c'est lent.
Je sais bien que si on veut faire quelque chose de rapide, on utilise plus quelque chose comme python que bash, mais le shell a tout de même l'avantage de pouvoir facilement lancer des applications tierces facilement, éventuellement de manière asynchrone, en modifiant l'environnement, et en redirigeant les entrées/sorties. Si on veut faire la même chose avec un langage classique, je pense que c'est d'un coup moins facile (si on omet la fonction system() qui utilise d'ailleurs le shell).
Sans compter des projets complexe entèrement programmés en bash. Par exemple le gestionnaire de paquets de Nasgaïa est comme ça (ca a peut être changé depuis). Et ne serait-ce qu'afficher l'aide prend un temps fou.
je me demande alors dans quelle mesure (et je vous pose la question) il ne serait pas intéressant d'avoir un interpréteur shell (type sh ou autre) constamment chargé en mémoire qui pourrait rapidement exécuter n'importe quel script. Bien entandu une attention particulière doit être portée à la sécurité.
3. mon code
je vois ai dit que j'avais codé quelque chose, mais quoi ? J'ai commencé un shell basé sur le langage jam². Jam est un remplaçant à make(1) et possède un langage de script qui a les facultés suivantes :
- les variables ne contiennent pas une seule valeur, mais des listes de chaînes de caractères.
- lors de l'expansion des variables est le produit de tous les termes. Pour illustrer cela, je prend juste l'exemple donné dans la documentation³:
$(X) -> a b c
t$(X) -> ta tb tc
$(X).txt -> az bz cz
$(X)-$(X) -> a-a a-b a-c b-a b-b b-c c-a c-b c-c
- la ponctuation n'est pas reconnue si elle n'est pas entourée d'espaces. Cela permet d'avoir de la ponctuation sans problèmes à l'intérieur des mots.
Ce que j'ai fait à partir de ça, c'est relativement simple. J'ai supprimé toute la partie qui s'occupait de la compilation, de la gestion des dépendances entre les cibles, pour ne garder que le langage Jam et faire en sourte que si une fonction n'est pas reconnue de manière interne par Jam, le programme correspondant dans le $PATH soit exécuté. Et cela n'a pris que quelques heures.
Mon code est disponnible sur launchpad⁴ et si cela vous intéresse, plus de détails dans mon e-mais sur la mailing list de jam⁵.
Des remarques ?
¹ http://wiki.archlinux.org/index.php/ABS_-_The_Arch_Build_Sys(...)
² http://freetype.sourceforge.net/jam/index.html
³ http://public.perforce.com/public/jam/src/Jam.html
⁴ https://code.launchpad.net/~mildred/+junk/jam-shell
⁵ http://maillist.perforce.com/pipermail/jamming/2007-November(...)
Je vais vous parler d'un sujet qui m'intéresse, le shell. A mon avis le shell actuel qu'on a sur nos machines (souvent bash) a deux gros problèmes, et j'aimerais bien ouvrir une discussion là dessus à l'occasion d'un développement récent que j'ai fait.
1. L'expansion des variables
Tout d'abord, le premier problème du shell sh ou compatible est à mon avis, outre sa syntaxe parfois bizarre, le problème de l'expansion des variables. Pour justifier mes griefs sur la syntaxe, je dirais simplement que je ne vois pas pourquoi les routines sont délimitées par des { } et pas les constructions if/for/case, pourquoi le if se termine par fi, le case par esac et le for/while par done et non pas rof/elihw.
penchons-nous sur le problème de l'expansion des variables. Le problème c'est que le shell découpe les mots après l'expansion des variables, c'est à dire que :
var="mot1 mot2"
cmd a b${var}c d
est équivalent à :
cmd a bmot1 mot2c d
et non pas :
cmd a "bmot1 mot2c" d
Alors que dans le code source on ne voit que 3 arguments passés à la commande cmd (a, b${var}c, d), il y en a en fait réellement 4 (a, b$mot1, mot2c, d).
Ce comportement peut être désirable dans certains cas, mais parfois vraiment malvenu. Par exemple lorsqu'on manipule des noms de fichiers contenant des espaces. En gros cela impose lorsqu'on ne sait pas précisément ce qu'il y a dans la variable à toujours l'entourer de quotes, c'est à dire écrire "$var" au lieu de $var.
Non seulement les débutants qui commencent ne comprennent pas toujours cette subtilité et oublient les quotes, mais bien souvent aussi, des gens expérimentés les oublient en les pensant inutiles.
Un exemple concret. la distribution ArchLinux utilise des script bash pour compiler les logiciels et créer des paquets, on les appelle PKGBUILD (un peu similaires aux ebuilds de gentoo). L'exemple le plus courrant et qui sert de modèle à tous les PKGBUILD est¹:
build() {
cd $startdir/src/$pkgname-$pkgver
./configure --prefix=/usr
make || return 1
make prefix=$startdir/pkg/usr install
}
Où $startdir est le dossier dans lequel les sources se trouvent, et où les fichiers sont installés.
Ici on peut voir que $startdir n'est jamais entre quotes, c'est à dire qu'il est impossible de compiler un package si le chemin courrant contient des espaces !!! En effet, la première commande (cd) échoue déjà car au moins deux arguments lui sont passés en paramètres.
C'est ma première critique concernant la robustesse des script shell.
2. La lenteur
Bash, c'est lent. On le voit avec les script d'init, et je l'ai remarqué plus récamment en remplaçant mon script monolithique ~/.xsession par de nombreux scripts modulaires qui me permet à chaque démarrage de:
- me demander mon mot de passe ssh
- lancer quelques programmes comme xbindkeys, mpd
- demander quelle session lancer (E17, GNOME, KDE, Terminal ...)
Et je constate facilement que c'est lent.
Je sais bien que si on veut faire quelque chose de rapide, on utilise plus quelque chose comme python que bash, mais le shell a tout de même l'avantage de pouvoir facilement lancer des applications tierces facilement, éventuellement de manière asynchrone, en modifiant l'environnement, et en redirigeant les entrées/sorties. Si on veut faire la même chose avec un langage classique, je pense que c'est d'un coup moins facile (si on omet la fonction system() qui utilise d'ailleurs le shell).
Sans compter des projets complexe entèrement programmés en bash. Par exemple le gestionnaire de paquets de Nasgaïa est comme ça (ca a peut être changé depuis). Et ne serait-ce qu'afficher l'aide prend un temps fou.
je me demande alors dans quelle mesure (et je vous pose la question) il ne serait pas intéressant d'avoir un interpréteur shell (type sh ou autre) constamment chargé en mémoire qui pourrait rapidement exécuter n'importe quel script. Bien entandu une attention particulière doit être portée à la sécurité.
3. mon code
je vois ai dit que j'avais codé quelque chose, mais quoi ? J'ai commencé un shell basé sur le langage jam². Jam est un remplaçant à make(1) et possède un langage de script qui a les facultés suivantes :
- les variables ne contiennent pas une seule valeur, mais des listes de chaînes de caractères.
- lors de l'expansion des variables est le produit de tous les termes. Pour illustrer cela, je prend juste l'exemple donné dans la documentation³:
$(X) -> a b c
t$(X) -> ta tb tc
$(X).txt -> az bz cz
$(X)-$(X) -> a-a a-b a-c b-a b-b b-c c-a c-b c-c
- la ponctuation n'est pas reconnue si elle n'est pas entourée d'espaces. Cela permet d'avoir de la ponctuation sans problèmes à l'intérieur des mots.
Ce que j'ai fait à partir de ça, c'est relativement simple. J'ai supprimé toute la partie qui s'occupait de la compilation, de la gestion des dépendances entre les cibles, pour ne garder que le langage Jam et faire en sourte que si une fonction n'est pas reconnue de manière interne par Jam, le programme correspondant dans le $PATH soit exécuté. Et cela n'a pris que quelques heures.
Mon code est disponnible sur launchpad⁴ et si cela vous intéresse, plus de détails dans mon e-mais sur la mailing list de jam⁵.
Des remarques ?
¹ http://wiki.archlinux.org/index.php/ABS_-_The_Arch_Build_Sys(...)
² http://freetype.sourceforge.net/jam/index.html
³ http://public.perforce.com/public/jam/src/Jam.html
⁴ https://code.launchpad.net/~mildred/+junk/jam-shell
⁵ http://maillist.perforce.com/pipermail/jamming/2007-November(...)
> Lire le journal (36 commentaires, moyenne: 3,2).
Vous avez demandé le commentaire #880999.



Y a pas que Bash dans la vie
Y'a TCSH !
http://ethtezahl.over-blog.com/
http://www.grattadom.com
[^]Re: Y a pas que Bash dans la vie
Quitte à citer un shell Unix autre que Bash, autant citer zsh ;-)
[^]Re: Y a pas que Bash dans la vie
Je ne sais pas pour tcsh, mais je sais que zsh (que j'utilise) a les mêmes problèmes que bash.
La Roue du Temps
[^]Re: Y a pas que Bash dans la vie
Dieu merci, tcsh ne souffre pas des mêmes problèmes que bash. A la place il souffre des mêmes problèmes que csh.
[^]Re: Y a pas que Bash dans la vie
Bah tu dois très certainement mal utiliser zsh ou l'utiliser à la bash.
Car zsh permet autrement plus de choses que bash notamment en ce qui concerne la programmation. Une bonne lecture (longue) de http://zsh.dotsrc.org/Doc/ pourra te donner un bon apperçu.
La lenteur du shell est relative, comme indiqué plus bas on passe souvent par des appels à des programmes externes (grep awk sed cat etc.) là où ce n'est absolument pas nécessaire en tout en zsh pour ce que je connais. Pour convertir souvent des script bash/ksh en zsh je peux te garantir que quand tu vires which/grep et consors tu as souvent un gain de performances important.
d'ailleurs dans la foulée : dans la version de dev de zsh, un nouveau module est en train de faire son apparition : zcurses => exit dialog \0/.
[^]Re: Y a pas que Bash dans la vie
pourquoi utiliser dialog ?
J'ai un collègue qui a fait de très belles choses en jouant avec les séquences terminfo...
En plus c'est portable, ce qui n'est pas forcément le cas de dialog et encore moins du module nzcurses...
[^]Re: Y a pas que Bash dans la vie
c'est plus portable, mais c'est surtout plus bas-niveau, donc plus chiant à faire quoi que ce soit.
[^]Re: Y a pas que Bash dans la vie
Deja en (ba|k)sh, quand on commence à virer les "cat | grep" ou les series de "grep | grep | grep -v | cut | sed' par un bon vieux awk, on gagne en rapidité d'exécution.
[^]Re: Y a pas que Bash dans la vie
Je suis assez d'accord.
Quand j'ai débuté la programmation shell j'enchainais beaucoup les |, et c'est vrai que les performances étaient pas au rendez-vous.
Utiliser correctement le shell, c'est surtout limiter le nombre de process et utiliser toutes ls fonctionnalités offertes par ce dernier.
Jusqu'au jour où finit par utiliser awk parce que le shell est trop pauvre, ensuite on découvre perl, et on fait des scripts perl standalone.
Aujourd'hui quand je veux faire un truc un minimum compliqué, je ne pense même pas à le faire en shell, c'est trop inmaintenable, mais bon y en a qui aiment se faire chier alors bon...
[^]Re: Y a pas que Bash dans la vie
Si je suis bien le raisonnement, tu fais donc désormais des scripts en Perl pour que cela soit plus maintenable.
C'est moi où il y a beaucoup d'ironie dans cette phrase ? :)
[^]Re: Y a pas que Bash dans la vie
Parce que pour toi un programme Perl est difficilement maintenable...?
J'ai ecris récemment un soft de 10 000 lignes environs de Perl, et c'est
tres maintenable et structuré.
Tout langage, mal ecris, donne du code difficilement maintenable...
You got the money, I got the soul.
[^]Re: Y a pas que Bash dans la vie
C'est toi.
Tu peux avoir l'opinion que tu veux dans la guéguerre (stupide) perl/python/ruby (ou une autre qui m'échappe), tu ne trouveras malgré tout pas grand monde pour prétendre qu'un gros projet bash est plus maintenable qu'un gros projet Perl.
[^]Re: Y a pas que Bash dans la vie
Il n'a juste jamais prit le temps de lire le code source de FreeNX :) (c'est un gros script bash de je sais plus combien de ligne...)
[^]Re: Y a pas que Bash dans la vie
Aujourd'hui quand je veux faire un truc un minimum compliqué, je ne pense même pas à le faire en shell, c'est trop inmaintenable, mais bon y en a qui aiment se faire chier alors bon...
Le shell, c'est bien lorsque tu te retrouve dans un environnement ou tu n'es pas sur d'avoir Perl à disposition partout (anciennes installations d'Unix proprios, NetBSD qui ne dispose pas de Perl dans une installation de base ou certaines boites qui interdisent l'utilisation de ce langage), ou quand tu développe des scripts de démarage par exemple.
Puis il ya des cas ou il est plus simple de s'en sortir avec un awk bien senti qu'avec Perl (cela dit je suis un peu rouillé en Perl, faudrait que je m'y remette).
[^]Re: Y a pas que Bash dans la vie
Pour grep, il est souvent possible de s'en passer quand il sagit seulement
de savoir si (une ligne, un mot, un process) est présent:
C'est nettement plus rapide que:
Mais c'est dangereux si le fichier est gros.
Pour traiter un fichier ligne par ligne:
C'est plus rapide que:
Il y a pas mal de bidouilles pour gagner en perfs, par exemple avec
les trucs décrits dans la section 'Parameter Expansion' on peut couvrir
pas mal de cas en ce qui concerne le traitement de chaines de caractères.
[^]Re: Y a pas que Bash dans la vie
Le while read ligne est une abomination côté perfs.
Imagine que dans ton traitement tu fais appel a des grep, cut ou sed, sur un fichier de plusieurs milliers de lignes c'est l'horreur.
awk est un outil qui est justement fait pour traiter les fichiers ligne à ligne, c'est beaucoup plus efficace.
[^]Re: Y a pas que Bash dans la vie
Je ne cherchais pas à dire que c'est mieux que awk ou autre,
mais simplement de montrer que l'on peut se passer des pipes en bash et le gain en performances en est conséquent.
Sur de gros fichiers, il est évident que de faire des | grep , | awk ça rox.
Mais sur de petits fichiers et une machine lente, le pipe c'est lent (car fork si je ne me trompe pas).
Et si le processing fait appelle à d'autres outils du script bash pour inserer des trucs dynamiquement , définir des variables pour bash en fonction de certaines lignes, remplir un tableau (si si ça existe en bash) etc ..
Une pile de grep/awk n'est pas forcement plus efficace.
Et puis, le plus pénible avec ces tubes étant la difficulté à obtenir le code de retour de la première commande ce qui rend encors plus complexe la détection d'erreurs dans le script qui sont trop souvent négligées sous pretexte que ça n'arrive jamais.
>imagine que dans ton traitement tu fais appel a des grep, cut ou sed, sur un fichier de plusieurs milliers de lignes c'est l'horreur.
Si je montre comment éviter les grep, cut & autre, ce n'est pas pour en mettre dans le 'processing' de chaques lignes. Suis pas gros débile non plus, hein :-)
Utiliser un script shell pour traiter plusieurs milliers de lignes, c'est pas une super bonne idée non-plus.