Traduit par Lansciac pour Madchat.org
Un shell est un interpreteur de lignes de commande. Il recoit les commandes et les execute. Cela peut s'apparenter a un langage de programmation. Le Bourne shell (ndt:bsh) est utilise pour creer des scripts shell -- en d'autres termes, des programmes interpretes/executes par le shell. Vous pouvez ecrire des scripts shell avec du C-shell; pour des raisons de longueur, nous ne le traiterons pas ici.
Supposez que vous utilisez souvent la commande
find . -name file -print
et que vous prefereriez taper une commande simple telle que
sfind file
Creez un script shell
% cd ~/bin % emacs sfind % page sfind find . -name $1 -print % chmod a+x sfind % rehash % cd /usr/local/bin % sfind tcsh ./shells/tcsh
Ce rapide exemple est loin d'etre tres adequate mais permet les observations suivantes:
%chmod a+x sfind
Tous les scripts Bourne Shell doivent commencer par la sequence
#!/bin/sh
Exemple tire de la page du manuel de la commande exec (2):
"Sur la premiere ligne de l'interpreteur de script suivant le "#!" devra figurer le nom du programme qui doit etre utilise pour interprete le contenu du fichier. Par exemple, si la premiere ligne contient "#! /bin/sh", le contenu du fichier sera alors execute comme un script shell."
Vous pouvez passer outre cet formalite, mais vous ne devriez pas. Tous les bon scripts etablissent explicitement quel interpreteur doit etre utilise. Il y a longtemps, il y avait uniquement un seul interpreteur (Le Bourne Shell) mais aujourd'hui, il y a beaucoup plus d'interpreteurs -- Csh, Ksh, Bash, et bien d'autres.
Les commentaires sont precedes du caractere diese (#). Un commentaire peut commencer n'importe ou sur une ligne et continue jusqu'a le fin de la ligne.
Tous les scripts shell devraient inclure un chemin de recherche specifique:
PATH=/usr/ucb:/usr/bin:/bin; export PATH
PATH=/usr/ucb:/usr/bin:/bin; export PATH Une specification du PATH est recommandee -- Dans bien des cas le script ne fonctionnera pas chez differents utilisateurs car le chemin de recherche est manquant ou incomplet.
Le Bourne Shell n'exporte pas les variables d'environnement a son heritage a moins que vous l'ayez explicitement declare en utilisant la commande export.
Un bon script shell doit verifier que les arguments passes (s'il y en a) sont corrects
if [ $# -ne 3 ]; then echo 1>&2 Usage: $0 19 Oct 91 exit 127 fi
Ce script necessite trois arguments et averti en cas de probleme.
Toutes les applications Unix doivent retourner un status de sortie (ndt: Pas forcement, si vous proggez a la porc, y'en a pas besoin..Enjoy :)
# is the year out of range for me? if [ $year -lt 1901 -o $year -gt 2099 ]; then echo 1>&2 Year \"$year\" out of range exit 127 fi etc... # All done, exit ok exit 0
Un status de sortie different de zero indique une condition d'erreur alors qu'un status a zero indique le script s'est execute correctement.
Sur les systemes BSD, il y a un classement en categorie des codes de sorties les plus souvent usites. Voir /usr/include/sysexits.h
les codes de sorties sont important pour la plupart des gens qui utilisent votre code. Beaucoup construisent des tests sur le status de sortie d'une commande.
La condition de construction est:
if command; then command fi
Par exemple,
if tty -s; then echo Enter text end with \^D fi
Votre code devrait etre ecrit dans la vision que d'autres pourront l'utiliser. Assurez vous que vous retournez un code de sortie significatif. Cela les aidera beaucoup.
Entree standard, sortie standard et la sortie d'erreur sont les descripteurs de fichier 0, 1 et 2. Chacun a un role particulier et doit etre utilise a propos.
# is the year out of range for me? if [ $year -lt 1901 -o $year -gt 2099 ]; then echo 1>&2 Year \"$year\" out of my range exit 127 fi etc... # ok, you have the number of days since Jan 1, ... case `expr $days % 7` in 0) echo Mon;; 1) echo Tue;; etc...
Le message d'erreur doit apparaitre sur stderr et pas sur stdout! Les sorties doivent apparaitre sur stdout. Comme pour le dialogue input/outpout:
# give the fellow a chance to quit if tty -s ; then echo This will remove all files in $* since ... echo $n Ok to procede? $c; read ans case "$ans" in n*|N*) echo File purge abandoned; exit 0 ;; esac RM="rm -rfi" else RM="rm -rf" fi
Note: Ce code se comporte differement s'il doit communiquer avec un utilisateur. (Si l'entree standard est un tty plutot qu'un pipe, ou un fichier, ou etc. Voir tty(1)).
La boucle POUR
Elle substitue les variables et effectues les taches comprises dans la boucle:
for variable in word ... do command done
Par exemple:
for i in `cat $LOGS` do mv $i $i.$TODAY cp /dev/null $i chmod 664 $i done
Vous pourrez aussi voir le code ecrit comme cela:
for variable in word ...; do command; done
Selectionne les actions a effectuer en fonction des correspondances de cas case word in [ pattern [ | pattern ... ] ) command ;; ] ... esac
Par exemple: case "$year" in [0-9][0-9]) year=19${year} years=`expr $year - 1901` ;; [0-9][0-9][0-9][0-9]) years=`expr $year - 1901` ;; *) echo 1>&2 Year \"$year\" out of range ... exit 127 ;; esac
if command then command [ else command ] fiPar exemple:
if [ $# -ne 3 ]; then echo 1>&2 Usage: $0 19 Oct 91 exit 127 fiVous pourrez aussi voir:
if command; then command; [ else command; ] fi
Repete la tache tant que la condition retourne un code de retour valide
{while | until} command do command donePar exemple:
# Pour chaque argument mentionne, on purge le repertoire while [ $# -ge 1 ]; do _purge $1 shift doneVous pouvez aussi voir
while command; do command; done
Les variables sont des sequences de lettres, de chiffres ou de traits commencant avec une lettre ou un underscore. Pour obtenir le contenu d'une variable, vous devez preceder le nom de la variable par le symbole $.
Les variables numeriques (comme $1, etc.) sont des variables de position pour le passage des arguments.
Assigner directement une valeur a une variable (variable=valeur).
Par exemple:
PATH=/usr/ucb:/usr/bin:/bin; export PATHOu
TODAY=`(set \`date\`; echo $1)`
Les variables ne sont pas exportees vers les variables filles a moins que cela soit explicitement marque.
# Nous devons avoir une variable d'environnement DISPLAY if [ "$DISPLAY" = "" ]; then if tty -s ; then echo "DISPLAY (`hostname`:0.0)? \c"; read DISPLAY fi if [ "$DISPLAY" = "" ]; then DISPLAY=`hostname`:0.0 fi export DISPLAY fiPour les variables comme PRINTER, vous pouvez les mettre dans le .profile de l'utilisateur, ce qui evitera de le specifier dans chaque programme
PRINTER=PostScript; export PRINTERNote: Le Cshell (CSH) exportes toutes les variables d'environnement.
Utiliser $variable (ou si necessaire ${variable}) pour referencer une valeur.
#La plupart des utilisatejurs ont un /bin propre if [ "$USER" != "root" ]; then PATH=$HOME/bin:$PATH else PATH=/etc:/usr/etc:$PATH fiLes accolades sont necessaires pour la concatenation.
$p_01La valeur de la variable "p_01".
${p}_01La valeur de la variable "p" avec "_01" accole a la fin
${variable-word}Si la variable a ete declaree, elle utilise sa valeur, sinon, elle utilise le mot.
POSTSCRIPT=${POSTSCRIPT-PostScript}; export POSTSCRIPT ${variable:-word}Si la variable a ete declaree et qu'elle est non nulle, alors elle utilise sa valeur, sinon, elle utilise le mot.
Ce sont des constructions tres utiles pour le partage de l'environement utilisateur. L'utilisateur du script peut outrepasser les assignements de variable. Conferez vous aux programmes comme lpr(1) definissant la variable d'environnement PRINTER, vous pouvez utiliser la meme astuce avec vos scripts.
${variable:?word}Si la variable est declaree, alors on utilise sa valeur, sinon, on ecrit le mot et on sort.
Les arguments des lignes de commande sont des arguments passe aux scripts shell comme des variables positionnees:
$0, $1, ...La commande et les arguments. Avec $0, on obtient la commande passee; le reste correspond aux arguments
$#Permet d'obtenir le nombre d'arguments
$*, $@Tous les arguments passes dans une chaine separes par des espaces. Voir le man pour connaitre la difference entre "$*" et "$@".
shiftDecale la position des variables d'une place et decremente le nombre d'arguments.
set arg arg ...Ajoute les variables positionnees a la liste d'argument.
Parcours de la ligne d'arguments en utilisant shift:
#parcourir la liste d'arguments while [ $# -ge 1 ]; do case $1 in process arguments... esac shift doneUne utilisation de la commande SET:
#Ressors le jour TODAY=`(set \`date\`; echo $1)` cd $SPOOL for i in `cat $LOGS` do mv $i $i.$TODAY cp /dev/null $i chmod 664 $i done
$$L'ID du process en cours. Ceci est tres utile pour contruire des fichiers temporaires.
tmp=/tmp/cal0$$ trap "rm -f $tmp /tmp/cal1$$ /tmp/cal2$$" trap exit 1 2 13 15 /usr/lib/calprog >$tmp $?Le status de sortie de la derniere commande.
$command # Run target file if no errors and ... if [ $? -eq 0 ] then etc... fi
Caracteres speciaux pour terminer les mots:
; & ( ) | ^ < > new-line space tabCes caracteres sont utilises pour les sequences de commande, les travaux en tache de fond, etc. Pour apostropher (ndt: pour ceux qui ne comprendraient pas "mettre out") un de ces caracteres, il faut utiliser un anti-slash ou une parenthese avec les guillemets ("" ou '')
Les apostrophes simples
A l'interieur des apostrophes, tous les caracteres sont apostrophes (NDT:sans fonction) - ce qui comprends aussi l'anti-slash. Le resultat est appele mot.
grep :${gid}: /etc/group | awk -F: '{print $1}'Les guillemets
A l'interieur des guillemets, la substitution des variables s'effectue (par exemple, le signe dollar est interprete), mais pas la generation de nom de fichiers. Le resultat est appele mot.
if [ ! "${parent}" ]; then parent=${people}/${group}/${user} fiL'accent grave
L'accent grave permet l'emulation de la commande passee et substitue la sortie.
if [ "`echo -n`" = "-n" ]; then n="" c="\c" else n="-n" c="" fi
et
TODAY=`(set \`date\`; echo $1)`
Les fonctions sont des composantes tres importants dans l'univers du script shell. Cependant, elle sont loin d'etre suffisament employees. La syntaxe est:
name () { commands }Par exemple:
#Purger un repertoire _purge() { #On verifie que c'est bien un repertoire. if [ ! -d $1 ]; then echo $1: No such directory 1>&2 return fi etc... }A l'interieur d'une fonction, les parametres positionnes $0, $1, etc. sont les arguments passes a la fonction et non pas ceux passes a la ligne de commande.
A l'interieur d'une fonction, utilisez return au lieu d'exit.
Les fonctions sont pratique pour l'encalpsulation. vous pouvez piper, rediriger a l'interieur, etc. vers d'autres fonctions. Par exemple:
#Traite un fichier, ajoute les personnes une par une. do_file() { while parse_one etc... } etc... #Prends l'entree standard (ou un fichier specifie) et effectue le traitement. if [ "$1" != "" ]; then cat $1 | do_file else do_file fi
Vous pouvez executer des scripts shell a l'interieur de script shell. Il y a deux possibilites:
sh command
Lance le script shell dans un shell separe. Par exemple, sur des machines Sun dans /etc/rc:
sh /etc/rc.local. command
Lance le script shell a l'interieur du script en cours
#Lis les infos de config . /etc/hostconfigQuel est l'interet de chacune de ces commandes? quelles sont les differences? La seconde forme est tres pratique pour les fichiers de configuration où les variables d'environnement sont declarees pour le script. Par exemple:
for HOST in $HOSTS; do #Y a t'il un fichier de config pour cet hote? if [ -r ${BACKUPHOME}/${HOST} ]; then . ${BACKUPHOME}/${HOST} fi etc...L'utilisation des fichiers de configuration dans cette optique rend possible l'ecriture de scripts qui sont automatiquement configures selon l'environnement.
La commande la plus puissante est la commande test(1).
if test expression; then etc...et (l'argument doit etre entre les crochets)
if [ expression ]; then etc...Sur les machines dotees du systemes V, cela est implicite (regardez le man de /bin/test)
Sur les machines dotees de BSD (comme les machines Suns) comparez la commande /usr/bin/test avec /usr/bin/[.
Les expressions les plus employees sont:
test { -w, -r, -x, -s, ... } filenameLe fichier est il ouvert en ecriture, lisible, executable, vide, etc.?
test n1 { -eq, -ne, -gt, ... } n2sont egaux, differents, superieurs, etc?
test s1 { =, != } s2Les chaines sont elles identiques ou differentes?
test cond1 { -o, -a } cond2OU binaire; ET binaire; l'utilisation de ! signifie la negation.
Par exemple
if [ $year -lt 1901 -o $year -gt 2099 ]; then echo 1>&2 Year \"$year\" out of range exit 127 fiApprenez cette commande par coeur, elle vous sera tres utile.
La commande test fournit une comparaison sur une correspondance limite en caractere. L'astuce reside dans l'utilisation d'un cas.
#Parcours la liste d'arguments while [ $# -ge 1 ]; do case $1 in -c*) rate=`echo $1 | cut -c3-`;; -c) shift; rate=$1 ;; -p*) prefix=`echo $1 | cut -c3-`;; -p) shift; prefix=$1 ;; -*) echo $Usage; exit 1 ;; *) disks=$*; break ;; esac shift doneBien sur, l'emploi de la commande getopt sera recommandee.
Sur un systeme BSD, pour obtenir le prompt, nous utiliserions:
echo -n Ok to procede?; read ansSur un systeme SysV, il faut dire:
echo Ok to procede? \c; read ansDans un effort de portabilite du code, nous ecrirons:
#Precise quel echo il faut utiliser if [ "`echo -n`" = "-n" ]; then n=""; c="\c" else n="-n"; c="" fi etc... echo $n Ok to procede? $c; read ans
La traditoin Unix veut qu'un programme soit execute le plus rapidement possible. Specialement pour les pipelines, les cron jobs, etc.
L'utilisation des prompts n'est pas necessaire s'il n'y a pas d'utilisateur.
#Si jamais il y a une personne, laissez lui une chance... if tty -s; then echo Enter text end with \^D fiLa tradition (ndt: encore elle) veut aussi que l'on determine vers ou est redirigee la sortie
# Si la sortie est redirigee vers un terminal, soyez prolixe a souhait :) if tty -s <&1; then verbose=true else verbose=false fiATTENTION: Ce n'est pas parce que stdin est un tty que stdout le sera forcement. Le prompt peut etre redirige vers un terminal.
# S'il y a qqn dans les parages, donnez lui une chance de surpasser l'envie de RAZ ;p if tty -s; then echo Enter text end with \^D >&0 fiAvez vous deja vu un programme arreter d'attendre l'appui d'une touche parce que la sortie est redirigee ailleurs?
Nous avons l'habitude de rediriger les entrees. Par exemple
#Prennez l'entree standard (ou un fichier specifique) et faites cela. if [ "$1" != "" ]; then cat $1 | do_file else do_file fiAlternativement, redirection d'un fichier
#Prennez l'entree standard (ou un fichier specifique) et faites cela. if [ "$1" != "" ]; then do_file < $1 else do_file fiVous pouvez ainsi construire des fichiers a la volee.
rmail bsmtp <<EOF helo news mail from:><$1@newshost.uwo.ca> rcpt to:<LISTSERV@$3> data from: <$1@newshost.uwo.ca> to:<LISTSERV@$3> Subject: Signon $2 subscribe $2 Usenet Feeder at UWO . quit EOFNote : Les variables sont definies dans l'entrees.
Une des choses les plus courantes que vous aurez a faire est de parcourir une chaine. Quelques conseils:
TIME=`date | cut -c12-19` TIME=`date | sed 's/.* .* .* \(.*\) .* .*/\1/'` TIME=`date | awk '{print $4}'` TIME=`set \`date\`; echo $4` TIME=`date | (read u v w x y z; echo $x)`Avec beaucoup d'attention, redefinir les separateurs des champs d'entrees peut etre tres utile.
#!/bin/sh # convert IP number to in-addr.arpa name name() { set `IFS=".";echo $1` echo $4.$3.$2.$1.in-addr.arpa } if [ $# -ne 1 ]; then echo 1>&2 Usage: bynum IP-address exit 127 fi add=`name $1` nslookup < < EOF | grep "$add" | sed 's/.*= //' set type=any $add EOF
Le shell a beaucoup d'indicateurs qui permet un debugage tres aise:
sh -n command
Lit le script mais ne l'execute pas.
sh -x command
Affiche les commandes et les arguments comme ils seront executes. Dans beaucoup de mes scripts, vous verrez afficher:
# Uncomment the next line for testing # set -x
Based on An Introduction to Shell Programing by:
Reg Quinton <REGGERS@JULIAN.UWO.CA> Computing and Communications Services The University of Western Ontario London, Ontario N6A 5B7 Canada
Traduit par Lansciac pour Madchat.org