Les conditionnelles Attention aux espaces de part et d'autre de [ et ] lorsque vous construisez une expression booleenne. Attention, la commande test retourne une valeur, mais vous ne pouvez pas directement la stocker. Le manuel de la commande Unix test (pas la commande interne a bash) vous dit: NOM test - Vérifier le type d’un fichier, et comparer des valeurs. SYNOPSIS test [expr] test {--help,--version} DESCRIPTION Cette page de manuel documente la version GNU de test. Remarquez que la plupart des shells ont une commande interne avec le même nom et des fonctionnalités similaires. test renvoie une valeur 0 (vrai) ou 1 (faux) suivant l’évaluation de l’expression conditionnelle expr. Si vous essayez : cerin@taipei:~$ echo $i 10 cerin@taipei:~$ c=`test $i -lt 10` cerin@taipei:~$ echo $c cerin@taipei:~$ c=`test $i -lt 15` cerin@taipei:~$ echo $c cerin@taipei:~$ Ici, de toute evidence, $c ne vaut ni 0 ni 1 Le problème vient de ce que ` ` ou $( ) expanse la commande en la remplacant par sa sortie standard, pas par sa valeur de retour. Cette dernière est utilisée directement par les instructions de branchement (if par exemple). Le seul moyen de la récupérer est d'utiliser la variable speciale $? cerin@taipei:~$ test 10 -lt 15 cerin@taipei:~$ echo $? 0 cerin@taipei:~$ test 15 -lt 10 cerin@taipei:~$ echo $? 1 cerin@taipei:~$ +++++++++++++++++++++++++++++++++++++++++++++ Les itérations: La notation i++ fonctionne aussi en bash a condition d'être entre (( et )): cerin@taipei:~$ for (( i=0;i<10;i++ )); do echo $i; done 0 1 2 3 4 5 6 7 8 9 On peut aussi utiliser le + de bash cerin@taipei:~$ for (( i=0;$i<10;i=$(($i+1)) )); do echo $i; done 0 1 2 3 4 5 6 7 8 9 La meme iteration avec un while et une expression entre [ et ]. Notez les "blancs". cerin@taipei:~$ i=0;while [ $i -lt 10 ]; do echo $i; i=$(($i+1)); done 0 1 2 3 4 5 6 7 8 9 La meme chose avec un while et un appel a test. cerin@taipei:~$ i=0;while test $i -lt 10; do echo $i; i=$(($i+1)); done 0 1 2 3 4 5 6 7 8 9 +++++++++++++++++++++++++++++++++++++++++++++ Expressions regulieres : grep -E "a{2}" titi.txt permet de trouver si exactement 2 a consecutifs apparaissent dans le fichier titi.txt grep -E "a{2,3}" titi.txt permet de trouver si aa ou aaa apparaissent dans le fichier titi.txt grep -E "[[:cntrl:]]" titi.txt permet de rechercher si un caractere de la classe des caracteres de controle est present dans le fichier titi.txt grep -E "[a-d]" titi.txt grep -E "[abcd]" titi.txt permet de rechercher si a ou b ou c ou d est present dans le fichier titi.txt grep -E "[^[:digit:]]" titi.txt permet de rechercher sur chacune des lignes de titi.txt un caractere qui n'est PAS un digit. Retourne la ligne si on trouve sur la ligne un caracter qui n'est pas un digit grep -E ".*{[[:digit:]]p" titi.txt On recherche dans titi.txt n'importe quel caractere autant de fois que je veux, suivi par {, suivi par un digit, suivi d'un p. Si ce motif apparait sur une ligne de titi.txt, on l'affiche. +++++++++++++++++++++++++++++++++++++++++++++ Un etudiant voulait tester si les caracteres de l'argument $1 sont tous en minuscules. On peut commencer par transformer $1 en minuscule en rangeant le resultat dans la variable bidon, puis comparer $bidon et $1. Si les deux chaines sont egales, c'est que $1 ne comportait que des minuscules. Implémentez cette méthode. Dans la solution ci dessous, on utilise le fait que l'instruction case de bash comprend les classes de caractères [[:ccc:]]. On utilise aussi des notations "un peu particulieres" : ${paramètre:début:longueur} Extraction de sous-chaîne Se développe pour fournir la sous- chaîne de la longueur indiquée (en caractères) commençant au début.D offset. Si la longueur est omise, fournit la sous- chaîne commençant au caractère de début et s’étendant jusqu’à la fin du paramètre. La longueur et le début sont des expressions arithmétiques (voir ÉVALUATION ARITHMÉTIQUE plus bas). La longueur doit être positive ou nulle. Si le début est négatif, sa valeur est considérée à partir de la fin du contenu du paramètre. Si le paramètre est @, le résultat correspond aux longueur paramètres positionnels commençant au début. Si le paramètre est un nom de tableau indexé par @ ou *, le résultat est les longueur membres du tableau commençant à ${paramètre[début]}. L’indexation des sous-chaînes débute à zéro, sauf pour les paramètres positionnels qui débute en 1. ${#paramètre} Est remplacé par la longueur, en caractères, de la valeur du paramètre. Si le paramètre est * ou @, la valeur est le nombre de paramètres positionnels. Si le paramètre est un nom de tableau indexé par * ou @, la valeur est le nombre d’éléments dans le tableau. Le texte suivant permet d'isoler les caracteres : #!/bin/bash a=$1 for (( i=0;i<${#a};i++ )) do b=${a:$i:1} # on prend la sous chaine de 1 caractere echo $b done cerin@taipei:~$ /bin/sh e.sh abcd a b c d Maintenant on rajoute un case avec lequel on peut faire une mise en correspondance (matching) : #!/bin/bash a=$1 for (( i=0;i<${#a};i++ )) do b=${a:$i:1} # on prend la sous chaine de 1 caractere case $b in [[:lower:]] ) echo "Minuscule: " $b;; *) echo "Autre caractere" esac done cerin@taipei:~$ /bin/sh e.sh abcdABCDefgh Minuscule: a Minuscule: b Minuscule: c Minuscule: d Autre caractere Autre caractere Autre caractere Autre caractere Minuscule: e Minuscule: f Minuscule: g Minuscule: h +++++++++++++++++++++++++++++++++++++++++++++ Evaluation, Expansion Ne pas confondre la semantique de { et } dans: ls /usr/{local,include,lib} et grep -E "a{2,3}" foo.txt Dans le premier cas, les {} servent de caracteres d'expansion pour generer /usr/local, /usr/include et /usr/lib pour faire ls de chacun de ces repertoires ; dans le deuxieme cas, les {} servent a specifier que le recherche le caractere a 2 fois consecutivement et au plus 3 fois consecutivement dans le fichier foo.txt Rappel : de nombreux caracteres ont plusieurs semantiques. Chaque semantique depend du contexte. Autre exemple : ^ peut etre interprete comme "debut de la ligne" ou encore exprimer la "complement d'une classe" comme dans grep -E "[^[:cntrl:]]" titi qui permet de rechercher les lignes de titi qui NE COMPORTENT PAS de caracteres de controle. ----- Ce n'est pas la peine de mettre echo dans : cerin@taipei:~$ echo `pwd` /users/cerin Dans cette commande, echo récupère la sortie standard de pwd pour l'afficher ensuite. Il est beaucoup plus logique de faire directement un appel a pwd qui est une commande qui renvoie son resultat sur l'entree standard. cerin@taipei:~$ pwd /users/cerin cerin@taipei:~$ C'est la meme chose avec grep. Vous ne faites pas : echo `grep ....` mais uniquement grep....` ------ Dans l'exercice Q12, nous avons un probleme pour contrecarrer l'expansion de *. Certains etudiants ont ecrit le script q12.sh suivant: #!/bin/bash echo "$1" > /tmp/foo grep "\*" /tmp/foo # Le symbole $? permet de recupperer le code de retour de la # derniere commande executee (ici grep). if [ $? -eq 1 ] then echo "etoile pas presente dans $1" else echo "etoile presente dans $1" fi cerin@taipei:~$ /bin/bash q12.sh tata etoile pas presente dans tata cerin@taipei:~$ /bin/bash q12.sh tata* tata* etoile presente dans tata* cerin@taipei:~$ /bin/bash q12.sh /bin etoile pas presente dans /bin cerin@taipei:~$ /bin/bash q12.sh /bin/* etoile pas presente dans /bin/arch cerin@taipei:~$ more /tmp/foo /bin/arch On remarque plusieurs choses : a) quand une * est passee avec le parametre, le grep retourne la ligne du fichier /tm/foo, d'ou l'affichage tata* produit par le grep b) quand on a fait l'appel q12.sh /bin/* il y a eu expansion de l'etoile ce qui a provoque l'instanciation de $1 avec le premier fichier trouve dans /bin (i.e /bin/arch). Comme ce fichier ne contient pas de *, on a affiche "etoile pas presente dans /bin/arch". Ceci n'est pas le resulat attendu. Le code propose n'est pas aussi general qu'on le voudrait. Le seul moyen d'éviter l'évaluation de * dans /bin est de faire, sur la ligne de commande: cerin@taipei:~$ /bin/bash q12.sh /bin/\* => le \ devant * Nous proposons aussi le code suivant : #!/bin/bash : ${1?"Usage: $0 ARGUMENT"} # Script exits here if command-line parameter absent, #+ with following error message. # usage-message.sh: 1: Usage: usage-message.sh ARGUMENT echo "These two lines echo only if command-line parameter given." echo "command line parameter = \"$1\"" a=\"$1\" `echo $a | grep "*"` if [ $? -eq 1 ] then echo "etoile pas presente dans $a" else echo "etoile presente dans $a" fi exit 0 # Will exit here only if command-line parameter present. Utilisation : Ordinateur-de-Christophe-Cerin:~ cerin$ /bin/bash q12.sh q12.sh: line 3: 1: Usage: q12.sh ARGUMENT Ordinateur-de-Christophe-Cerin:~ cerin$ /bin/bash q12.sh /bin/\* These two lines echo only if command-line parameter given. command line parameter = "/bin/*" Ordinateur-de-Christophe-Cerin:~ cerin$ /bin/bash q12.sh /bin\* These two lines echo only if command-line parameter given. command line parameter = "/bin*" +++++++++++++++++++++++++++++++++++++++++++++ Tail et head A priori, ces commandes ont pour syntaxe : tail [-c [+]N[bkm]] [-n [+]N] [-fqv] [--bytes=[+]N[bkm]] [--lines=[+]N] [--follow] [--quiet] [--silent] [--verbose] [--help] [--version] [fichier...] head [OPTION]... [FICHIER]... -n, --lines=[-]N afficher les N premières lignes au lieu des 10 premières ; avec le préfixe « - », afficher toutes les lignes sauf les N dernières lignes de chaque fichier On ne peut donc pas faire tail +2 e.sh | head -n 2 car +2 n'est pas reconnu. Or, partons de l'exemple suivant : cerin@taipei:~$ cat e.sh #!/bin/bash a=$1 for (( i=0;i<${#a};i++ )) do b=${a:$i:1} # on prend la sous chaine de 1 caractere case $b in [[:lower:]] ) n'est pas echo "Minuscule: " $b;; *) echo "Autre caractere" esac done cerin@taipei:~$ tail +2 e.sh | head -n 2 a=$1 for (( i=0;i<${#a};i++ )) cerin@taipei:~$ Cependant, un peu plus loin dans le manual on lit : tail accepte deux formats d’options différents. Le nouveau format dans lequel les nombres sont des arguments précédés par les lettres représentant des options, et l’ancien format dans lequel un ‘+’ ou un ‘-’ est suivi d’un nombre puis d’une lettre d’option. Si un nombre (‘N’) est précédé de ‘+’, tail commence l’affichage à par‐ tir du Nième élément en partant du début du fichier (au lieu de la fin). Donc ici tail +2 e.sh renvoie le fichier e.sh privé de la première ligne (on commence l’affichage à partir de la deuxième ligne). Ultime remarque : cerin@taipei:~$ tail -n 2 e.sh esac done cerin@taipei:~$ tail -2 e.sh esac done cerin@taipei:~$ +++++++++++++++++++++++++++++++++++ Question B.1 Dans cette question un script prend en parametre le nom d'un executable et détruit (commande kill) toutes les instances de cet executable. On peut simplement utiliser 'killall ' mais c'est vraiment trop simple. Un algorithme possible est le suivant : pour toutes les lignes renvoyees par la commande 'ps aux' faire reperer le nom de l'executable; // ceci ce fait avec un 'cut' et des options appropriées si ce nom = $1 alors reperer le numero de processus (PID) associe a la commande; // ceci ce fait avec un 'cut' et des options appropriées kill $PID finsi finpour Une autre possibilité est d'utiliser des options appropriées pour la commande ps. Le manuel nous dit : Print only the process IDs of syslogd: ps -C syslogd -o pid= Print only the name of PID 42: ps -p 42 -o comm= Ainsi on a: cerin@taipei:~/public_html/SE$ ps -C emacs -o pid= 8145 cerin@taipei:~/public_html/SE$ ps -p 8145 -o comm= emacs cerin@taipei:~/public_html/SE$ ps -p `ps -C emacs -o pid=` -o comm= emacs On peut alors écrire : #!/bin/bash if test `ps -C $1 -o pid=` then MyPid=`ps -C $1 -o pid=` echo $MyPid #kill -9 $MyPiD fi mais ceci ne fonctionne que pour une seule instance du programme: cerin@taipei:~/public_html/SE$ ps aux | grep emacs cerin 8145 0.2 1.3 19576 14372 pts/0 S 12:58 0:02 emacs Aide.txt cerin 9942 16.1 1.4 19848 14588 pts/0 S 13:11 0:01 emacs cerin 9960 0.0 0.0 4144 852 pts/0 S+ 13:12 0:00 grep emacs cerin@taipei:~/public_html/SE$ /bin/sh test1.bash emacs test1.bash: line 3: test: 8145: unary operator expected cerin@taipei:~/public_html/SE$ ps -C emacs -o pid= 8145 9942 cerin@taipei:~/public_html/SE$ On voit que ps retourne des lignes ! On utilise alors l'une ou l'autre des possibilités suivantes : #!/bin/bash MyPid=\"`ps -C $1 -o pid=`\" #echo $MyPid if test "$MyPid"!="" then for MyPid in `ps -C $1 -o pid=` do echo $MyPid #kill -9 $MyPiD done fi # # Ou plus simplement # for MyPid in `ps -C $1 -o pid=` do echo $MyPid #kill -9 $MyPiD done ----------------------------------- Modification du 3/3/2006 Le script ci-dessous prend en parametre un repertoire et affiche d'abord les fichiers du repertoire puis applique ce meme script sur tous les repertoires du repertoire $1. A chaque fois qu'on explore un nouveau repertoire, on indente un peu plus le texte a afficher. Ceci est un script RECURSIF (dans le script on appelle le script). ATTENTION : c'est du haut vol ! #!/bin/bash # # pour tous les fichers et repertoire de $1 faire # isoler les fichiers 'simples' et les repertoires # finpour # NbRep=0 NbFic=0 for i in `ls $1` do if [ -d $1/$i ] then MyRep="$1/$i:$MyRep" # on isole le repertoire NbRep=$(( $NbRep+1 )) # un repertoire de plus else MyFic="$MyFic:$i" # on isole le fichier NbFic=$(( $NbFic+1 )) # un fichier de plus fi done # # pour tous les fichiers faire # afficher le nom du fichier # finpour # i=1 while [ $NbFic -gt 1 ] do echo `echo $MyFic | cut -d ":" -f $i` i=$(( $i+1 )); NbFic=$(( $NbFic-1 )) done #echo $MyRep # # pour tous les repertoires de $1 faire # afficher le nom du repertoire # augmenter la valeur de l'indentation # rappeler recursivement le script sur le repertoire isole # finpour # i=1 while [ $NbRep -gt 0 ] do echo $2`echo $MyRep | cut -d ":" -f $i` MyIndent="$2___>" /home/usager/info/dut1/10500780/system/exo31bis.sh `echo $MyRep | cut -d ":" -f $i` "$MyIndent" i=$(( $i+1 )); NbRep=$(( $NbRep-1 )) done # # Limite du code : il n'y a pas de gestion des liens si bien # quue l'on peut relancer une exploration (par l'appel # recursif) sur un repertoire que l'on a deja visite. # Veuillez corriger ce probleme. #