Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Script shell, découper la gestion des options en 2 parties

26 réponses
Avatar
Francois Lafont
Bonsoir à tous,

J'ai écrit toute un série de scripts shell (en l'occurrence c'est du
sh, ie le dash d'une Debian) qui se lancent en ligne de commandes avec
des options --foo --bar etc. Tous les scripts ont une série d'options en
commun et ensuite chacun a en plus des options qui lui sont spécifiques.

Pour la gestion des options en shell, perso j'utilise getopt qui me
va très bien jusque là. Pour un script, j'utilise le schéma classique
que l'on retrouve dans les docs sur getopt, à savoir celui-ci :

-----------------------------------------------------
#!/bin/sh

SCRIPT_NAME=${0##*/}
export LC_ALL=C
export PATH='/usr/sbin:/usr/bin:/sbin:/bin'

print_help () {
cat <<EOF
The syntax is:
$SCRIPT_NAME --common1='<value1>' --common2='<value2>' --foo='<bar>'
EOF
}


options_list='help,common1:,common2:,foo:'

if ! TEMP=$(getopt -o "h" -l "$options_list" -n "$SCRIPT_NAME" -- "$@")
then
echo "Syntax error with $SCRIPT_NAME command." >&2
exit 1
fi

eval set -- "$TEMP"
unset TEMP

while true
do
case "$1" in
--common1)
common1="$2"
shift 2
;;

--common2)
common2="$2"
shift 2
;;

--foo)
foo="$2"
shift 2
;;

--help|-h)
print_help
exit 0
;;

--)
shift 1
break
;;
esac
done

# Et ensuite, j'ai mes variables common1, common2 et foo
# à utiliser dans la suite du script pour que celui-ci
# fasse ce que je veux qu'il fasse...
-----------------------------------------------------

Vous voyez ci-dessus, j'ai deux options --common1 et
--common2 qui sont des options communes à tous mes scripts
et l'option --foo, elle, est spécifique au script. Je simplifie
ici car dans mon cas perso il y a plus que 2 options en commun
et en plus ces options communes nécessitent un petit traitement
a posteriori etc. Bref, autant de code que je suis obligé de
dupliquer d'un script à l'autre. Et dans ce cas, toute modification
au niveau de la gestion des options communes devient pénible à gérer
puisqu'il faut modifier sur chaque script sans se tromper.

Ce que je voudrais, c'est avoir cette arborescence :

mes_scripts/
|-- script1.sh
|-- script2.sh
|
| [etc.]
|
|-- scriptN.sh
`-- share/
`-- common_options.sh

où je factoriserais tout le code en rapport avec les options
communes dans share/common_options.sh et dans chaque script
à la racine je ferais quelque choses comme ça :

-----------------------------------------------------
#!/bin/sh

SCRIPT_NAME=${0##*/}
export LC_ALL=C
export PATH='/usr/sbin:/usr/bin:/sbin:/bin'

# On va sourcer le fichier common_options.sh de sorte
# qu'ensuite on peut bénéficier des variables common1
# et common2 etc.
. ./share/common_options.sh

# Maintenant les variables common1 et common2 sont
# bien définies dans le script.

# Dans la suite du script, on va gérer avec getopt les
# options propres au script et uniquement celles-ci
# (ie l'option --foo dans le cas du script donné en
# exemple ci-dessus).
-----------------------------------------------------

Mon problème est que je n'arrive pas à avoir cela.
Je pensais par exemple faire un getopt dans common_options.sh
qui ne gère que les options communes et un getopt dans chaque
script qui ne gère que les options spécifiques mais ça ne marche
pas car dans common_options.sh le getopt plante car il voit
des options qu'il ne reconnaît pas (forcément puisqu'elles seront
traitées après dans le script appelant). Si getopt avait une option
pour lui dire « les options que tu ne connais pas, mets-les dans un
coin mais ne lève pas d'erreur », je pourrais peut-être m'en sortir
ainsi mais getopt ne possède pas une telle fonctionnalité malheureusement.
Apparemment, quand getopt est appelé il faut lui indiquer toutes
les options, pas seulement un bout, ce qui ne facilite pas le
découpage que je souhaite faire.

Après, peut-être qu'il y a une astuce à laquelle je n'ai pas
pensé. Peut-être une « siouxerie » à base de eval... Je ne sais
pas. En tout cas, je n'ai pour l'instant absolument rien trouvé
qui me permette de factoriser le code au niveau des la gestion
des options communes.

Voilà, j'espère avoir été a peu près clair, si vous avez des idées
je suis preneur. Merci d'avance pour votre aide.

--
François Lafont

6 réponses

1 2 3
Avatar
Nicolas George
Francois Lafont , dans le message
<533c065b$0$2285$, a écrit :
Si j'ai bien suivi, tu penches plutôt pour de meilleures perfs
avec du Perl qu'avec du Shell.



Oui, pour l'exécution d'un script non trivial.

Mais dans ce cas comment expliquer
le fait que Benoît et moi-même constatons des temps d'exécution
relativement plus rapide en Shell qu'en Perl (pour des scripts
à peu près équivalents) ?



Ce que tu mesures, ce n'est pas la vitesse d'exécution du code perl mais le
temps de lancement de perl lui-même.
Avatar
Benoit Izac
Bonjour,

le 02/04/2014 à 15:20, Nicolas George a écrit dans le message
<533c0e93$0$2060$ :

Si j'ai bien suivi, tu penches plutôt pour de meilleures perfs
avec du Perl qu'avec du Shell.



Oui, pour l'exécution d'un script non trivial.

Mais dans ce cas comment expliquer
le fait que Benoît et moi-même constatons des temps d'exécution
relativement plus rapide en Shell qu'en Perl (pour des scripts
à peu près équivalents) ?



Ce que tu mesures, ce n'est pas la vitesse d'exécution du code perl
mais le temps de lancement de perl lui-même.



On est bien d'accord mais il ne peut pas faire autrement puisque son
logiciel de supervision exécute un script pour chaque requête.

Ce qu'il serait intéressant de mesurer c'est le même script Perl dans
deux versions différentes, l'une avec une requête en Perl et l'autre
avec un appel à snmpget. Si je t'ai bien suivi, les temps dûs à l'appel
de snmpget devraient être supérieurs à ceux de le la requête Perl et
donc perdre tout son bénéfice.

--
Benoit Izac
Avatar
Francois Lafont
Bonjour,

Merci Nicolas pour la précision sur ce qui est mesuré. Effectivement,
le chargement de l'interpréteur lui-même (shell ou perl) compte beaucoup
(surtout dans mon cas comme l'a fait remarquer Benoît) et c'est vrai que
je n'avais pas cela en tête du tout : avant d'exécuter le code il faut
bien que l'interpréteur soit chargé. :)

C'est vrai qu'idéalement il faudrait que j'ai un interpréteur chargé
en daemon et qui exécute des scripts qu'on lui donne en entrée. :)

Le 02/04/2014 18:42, Benoit Izac a écrit :

Ce qu'il serait intéressant de mesurer c'est le même script Perl dans
deux versions différentes, l'une avec une requête en Perl et l'autre
avec un appel à snmpget.



Dès que possible, je poste ici des scripts (les plus équivalents possible)
avec :
- un script shell basé sur snmpget
- un script Perl « pur jus » (ie avec des requêtes snmp via des lib perl)
- un script Perl avec appel de la commande snmpget.

François
À+
Avatar
Benoit Izac
Bonjour,

le 02/04/2014 à 19:38, Francois Lafont a écrit dans le message
<533c4abe$0$2180$ :

Dès que possible, je poste ici des scripts (les plus équivalents possible)
avec :
- un script shell basé sur snmpget
- un script Perl « pur jus » (ie avec des requêtes snmp via des lib perl)
- un script Perl avec appel de la commande snmpget.



J'ai fait :
=========================================================================== #!/usr/bin/perl
use strict;
use warnings;
use Net::SNMP;

my $OID_sysUpTime = '1.3.6.1.2.1.1.3.0';
my $type = shift;
if (defined $type && $type eq "perl") {
my ($session, $error) = Net::SNMP->session(
-hostname => shift || 'oak',
-community => shift || 'public',
);

if (!defined $session) {
printf "ERROR: %s.n", $error;
exit 1;
}

my $result = $session->get_request(-varbindlist => [ $OID_sysUpTime ],);
if (!defined $result) {
printf "ERROR: %s.n", $session->error();
$session->close();
exit 1;
}

printf "The sysUpTime for host '%s' is %s.n",
$session->hostname(), $result->{$OID_sysUpTime};

$session->close();
} else {
my $ret = `snmpget -v2c -c public oak $OID_sysUpTime`;
print $ret;
}
exit 0;
===========================================================================
puis :
% time zsh -c 'for i in {1..1000}; do ./snmpget.pl; done >/dev/null'
59.71s user 6.66s system 90% cpu 1:13.60 total
% time zsh -c 'for i in {1..1000}; do ./snmpget.pl perl; done >/dev/null'
62.47s user 4.93s system 89% cpu 1:15.43 total

Résultat : c'est équivalent et c'est bien l'appel à l'interpréteur perl
qui consomme de la ressource et qui explique la différence avec un
script shell.

--
Benoit Izac
Avatar
Sergio
Le 02/04/2014 21:26, Benoit Izac a écrit :

% time zsh -c 'for i in {1..1000}; do ./snmpget.pl; done >/dev/null'
59.71s user 6.66s system 90% cpu 1:13.60 total
% time zsh -c 'for i in {1..1000}; do ./snmpget.pl perl; done >/dev/null'
62.47s user 4.93s system 89% cpu 1:15.43 total

Résultat : c'est équivalent et c'est bien l'appel à l'interpréteur perl
qui consomme de la ressource et qui explique la différence avec un
script shell.



Ainsi que la compilation du code : Perl fait une compilation puis interprète le code "à la volée". Un shell de base ne compile pas,
il interprète à la volée. Ce qui fait que pour des tâches simples, le shelle est plus performant. Si tu avais fait ta boucle for
dans le Perl, ça aurait été plus rapide...

--
Serge http://leserged.online.fr/
Mon blog: http://cahierdesergio.free.fr/
Soutenez le libre: http://www.framasoft.org
Avatar
Francois Lafont
Bonsoir,

Le 02/04/2014 21:26, Benoit Izac a écrit :

Résultat : c'est équivalent et c'est bien l'appel à l'interpréteur perl
qui consomme de la ressource et qui explique la différence avec un
script shell.



J'arrive exactement à la même conclusion ce qui n'est pas une surprise
bien sûr mais avec la nuance que c'est surtout que chargement des modules
Perl qui coûte cher, plus encore que le démarrage de l'interpréteur.

Avec un snmpget en v3 à destination de localhost soixante fois d'affilée,
j'ai ceci :

1. Avec sh et la commande externe snmpget.

$ time ./bench-local.sh shell >/dev/null
real 0m3.223s
user 0m2.324s
sys 0m0.248s


2. Avec du Perl à 100% (le GET SNMP est fait avec la lib Perl Net::SNMP).

$ time ./bench-local.sh perl >/dev/null
real 0m12.416s
user 0m10.961s
sys 0m1.244s


3. Avec du Perl « hypbride » (le GET snmp est fait avec un appel à la
commande externe snmpget).

$ time ./bench-local.sh perl-hybrid >/dev/null

real 0m5.045s
user 0m4.180s
sys 0m0.476s


J'ai mis le code ici :
http://sisco.laf.free.fr/divers/perl-vs-shell_2.tar.gz

Dans chacun des 3 cas, j'ai tenté de factoriser la gestion des options.
Si vous avez des remarques, notamment par rapport au code Perl où je
débute, vraiment je suis preneur.

Comme je disais plus haut, il me semble que c'est le chargement
des lib Perl qui coûte cher plus que l'interpréteur lui-même.
En effet, dans mon implémentation 100% Perl, j'utilise les modules
Getopt::Long et Net::SNMP et c'est surtout le deuxième qui pèse pas
mal dans la balance. Par exemple, si je prends ce simple code Perl :

----------------------------
#!/usr/bin/perl

use strict;
use warnings;
use 5.010;

#use Getopt::Long;
#use Net::SNMP;

say "Hello!";
----------------------------

Alors, j'ai :

$ time for i in $(seq 60); do ./test.pl > /dev/null; done
real 0m0.464s
user 0m0.192s
sys 0m0.144s

Donc là, ça dépote. Ensuite, je décommente le premier « use »
(celui de Getopt::Long) :

$ time for i in $(seq 60); do ./test.pl > /dev/null; done
real 0m1.528s
user 0m1.156s
sys 0m0.232s

Ça augment un peu, mais ça reste correct. Et enfin, je
décommente les 2 « use »... là ça fait boum !

$ time for i in $(seq 60); do ./test.pl > /dev/null; done
real 0m7.015s
user 0m6.104s
sys 0m0.784s

Perso, j'aimerais vraiment pouvoir me diriger vers Perl pour les
raisons que j'ai déjà données dans des messages précédents (notamment
le fait de disposer de structures d'un peu plus haut niveau en Perl
qu'en shell -- comme les tableaux etc). Mais au niveau temps d'exécution
c'est quand même nettement plus long qu'en shell, sauf peut-être pour
la version « hybride » à la limite.

Y a-t-il des choses qui m'auraient échappées du côté de Perl et
qui pourraient me permettre de diminuer le temps d'exécution ?
Une option au niveau de l'interpréteur Perl ? etc.
N'est-il pas possible de charger l'interpréteur Perl une bonne
fois pour toutes avec ses modules et ensuite de lui demander
d'exécuter tel ou tel scripts avec tels ou tels arguments ?

Merci d'avance pour votre aide.

--
François Lafont
1 2 3