Chapitre 9. Chaînes de caractères

Table des matières

9.1. Protection de caractères
9.1.1. Mécanismes
9.1.2. Exemples d’utilisation
9.2. Longueur d'une chaîne de caractères
9.3. Modificateurs de chaînes
9.4. Extraction de sous-chaînes
9.5. Remplacement de sous-chaînes
9.6. Transformation en majuscules/minuscules
9.7. Formatage de chaînes
9.8. Génération de chaînes de caractères

9.1. Protection de caractères

9.1.1. Mécanismes

Le shell utilise différents caractères particuliers pour effectuer ses propres traitements ($ pour la substitution, > pour la redirection de la sortie standard, * comme caractère générique, etc.). Pour utiliser ces caractères particuliers comme de simples caractères, il est nécessaire de les protéger de l'interprétation du shell. Trois mécanismes sont utilisables :

  • Protection d'un caractère à l'aide du caractère \

    Ce caractère protège le caractère qui suit immédiatement le caractère \.

    $ ls
    tata  toto
    $ echo *
    tata toto
    $ echo \*	=> le caractère * perd sa signification de caractère générique
    *
    $
    $ echo \\  => le deuxième caractère \ perd sa signification de caractère de protection
    \
    $ 
    $
    

    Le caractère \ permet également d’ôter la signification de la touche Entrée. Cela a pour effet d’aller à la ligne sans qu’il y ait exécution de la commande. En effet, après saisie d’une commande, l’utilisateur demande au shell l’exécution de celle-ci en appuyant sur cette touche. Annuler l’interprétation de la touche Entrée autorise l’utilisateur à écrire une longue commande sur plusieurs lignes.

    Dans l’exemple ci-dessous, la signification de la touche Entrée a été inhibée par le caractère \ : bash détecte que la commande interne echo n’est pas terminée et affiche la chaîne d’appel contenue dans la variable prédéfinie PS2 du shell.

    $ echo coucou \Entrée
    > salut Entrée   => terminaison de la commande : le shell l’exécute !
    coucou salut
    $
    

  • Protection partielle "chaîne"

    A l'intérieur d'une paire de guillemets, tous les caractères de chaîne sauf $ \ ` " sont protégés de l'interprétation du shell. Cela signifie, par exemple, que le caractère $ sera quand même interprété comme une substitution à effectuer.

    $ echo  "< * $PWD  ' >"
    < * /home/sanchis ' >
    $
    $ echo  "\"$PS2\""
    "> "			=> valeur de la variable prédéfinie PS2 entre guillemets
    $
    
  • Protection totale 'chaîne'

    Entre une paire d’apostrophes (), aucun caractère de chaîne (sauf le caractère ') n'est interprété.

    $ echo '< * $PWD * >'
    < * $PWD * >
    $
    

    L’utilisation du caractère apostrophe peut provoquer l’erreur suivante.

    $ echo c'est lundi
    >			=> le shell interprète l’apostrophe comme un début de chaîne à 
    > '			=>    protéger et attend par conséquent la deuxième apostrophe
    cest lundi
    
    
    $
    

    Pour éviter cela, on peut supprimer la signification particulière de cette apostrophe ou bien utiliser une paire de guillemets.

    $ echo N\'oublie pas !
    N'oublie pas !
    $
    $ echo "c'est lundi"
    c'est lundi
    $
    

9.1.2. Exemples d’utilisation

  • Certaines commandes unix telles que find utilisent pour leur propre fonctionnement les mêmes caractères génériques que ceux du shell. Utilisés sans précaution, ces caractères provoquent le plus souvent une erreur d’exécution. Par exemple, la commande

                 find . -name *.py  –print 

    recherche à partir du répertoire courant et affiche (ou plutôt devrait afficher) tous les noms d’entrées se terminant par la chaîne .py.

    $ ls -l
    total 20
    -rw-r--r-- 1 sanchis sanchis  954 27 mars  13:19 cliTCP.py 
    drwxr-xr-x 2 sanchis sanchis 4096 27 mars  13:19 Divers 
    -rwxr--r-- 1 sanchis sanchis   15 27 mars  13:20 ou 
    -rwxr--r-- 1 sanchis sanchis   39 27 mars  13:20 ou0 
    -rw-r--r-- 1 sanchis sanchis 2208 27 mars  13:17 servTCP.py 
    $
    $ find . -name *.py -print
    find: paths must precede expression: `servTCP.py'
    find: possible unquoted pattern after predicate `-name'?
    $
    

    L’origine du problème est que le caractère * aurait dû être interprété par la commande find alors qu’il a été interprété par bash, ce qui a conduit à l’exécution de la commande

                 find . -name cliTCP.py  servTCP.py –print

    La commande find signale une erreur de syntaxe car elle n’attend qu’une seule chaîne après son option –name.

    Pour corriger le problème, il suffit de protéger le caractère * de l’interprétation de bash.

    $ find . -name "*.py" -print
    ./cliTCP.py
    ./servTCP.py
    $
    

    Dans ce cas de figure, le type de protection utilisé n’a pas d’importance : on aurait également pu écrire '*.py' ou \*.py.

  • La présence de caractères espace (ou de caractères spéciaux de bash) dans la valeur d’un paramètre (variable ou paramètre de position) peut poser problème lorsque cette valeur participe à l’exécution d’une commande.

    Soit le programme shell nblig0 qui affiche le nombre de lignes (commande wc –l) d’un fichier dont le nom est saisi par l’utilisateur et contenu dans la variable rep.

    	#!/bin/bash
    	#	@(#)	nblig0
    
    	read -p "Saisissez un nom d'entree : " rep
    	wc -l $rep
    
    $ ls -l
    total 12
    -rw-r--r-- 1 sanchis sanchis 25 27 mars  13:35 'Meteo du jour.txt'
    -rwxr--r-- 1 sanchis sanchis 82 27 mars  13:37  nblig
    -rwxr--r-- 1 sanchis sanchis 81 27 mars  13:36  nblig0
    $
    $ nblig0
    Saisissez un nom d'entree : Meteo du jour.txt
    wc: Meteo: Aucun fichier ou dossier de ce type
    wc: du: Aucun fichier ou dossier de ce type
    wc: jour.txt: Aucun fichier ou dossier de ce type
    0 total
    $
    

    La variable rep contient bien la chaîne Meteo du jour.txt mais après substitution de $rep, la commande qui sera finalement exécutée est : wc –l Meteo du jour.txt

    La commande unix wc n’est pas exécutée avec un seul argument mais avec trois. Chacun d’eux est considéré par wc comme un nom de fichier qui, absent, provoque une erreur d’exécution.

    La solution à ce problème consiste à préserver l’intégrité de la valeur de la variable rep après que la substitution ait été effectuée par le shell. On utilise pour cela la syntaxe "$rep". Il est à noter que la syntaxe '$rep' aurait été inadéquate car elle interdit au shell d’effectuer la substitution $rep.

    $ cat nblig
    #!/bin/bash
    #	@(#)	nblig
    
    read -p "Saisissez un nom d'entree : " rep
    wc -l "$rep"
    
    $ 
    $ nblig
    Saisissez un nom d'entree : Meteo du jour.txt
    1 Meteo du jour.txt
    $
    

    De manière générale, il est fortement conseillé de préserver l’intégrité d’une chaîne de caractères obtenue après une substitution, surtout lorsque l’on ne connait pas à l’avance la composition de cette chaîne.

    Cette politique est systématiquement suivie dans l’écriture des programmes shell qui accompagnent l’interpréteur bash (ex : .bashrc, .profile).

    $ cat ~/.profile
    # ~/.profile: executed by the command interpreter for login shells.
    #	. . . 
    
    # if running bash
    if [ -n "$BASH_VERSION" ]; then
       	# include .bashrc if it exists
       	if [ -f "$HOME/.bashrc" ]; then
    		. "$HOME/.bashrc"
       	fi
    fi
    
    # set PATH so it includes user's private bin if it exists
    #if [ -d "$HOME/bin" ] ; then
    #	PATH="$HOME/bin:$PATH"
    #fi
    $
    

    Les fichiers shell .bashrc et .profile permettent à l’utilisateur de configurer son environnement de travail [cf. Chapitre 1, Introduction à Bash, § 1.1]. Ces deux fichiers sont présents dans le répertoire de connexion de l’utilisateur dont le chemin peut être rapidement désigné par le caractère ~.

  • Avant qu’une syntaxe spécifique ne soit introduite dans bash, le mécanisme d’indirection [cf. Chapitre 2, Substitution de paramètres, § 6] était obtenu à l’aide de la commande interne eval et des caractères de protection du shell.

    La syntaxe de cette commande interne est la suivante : eval [ arg ... ]

    La commande interne eval interprète au sens du shell (substitution de paramètres, etc.) chacun de ses arguments arg puis concatène la chaîne résultante en une seule chaîne. Celle-ci est ensuite à nouveau interprétée et exécutée comme une commande par le shell. En d’autres termes, eval permet d’effectuer une interprétation en deux passes.

    $ var=v1
    $ v1=un
    $ eval echo \$$var
    un
    $
    

    Lors de la première passe, le premier caractère $ n’est pas interprété car il est protégé de l’interprétation du shell (\$) et la chaîne $var est remplacée par la valeur de la variable var. A l’issue de la première interprétation, on obtient la chaîne suivante : echo $v1

    Cette chaîne est ensuite interprétée comme commande par bash.

    Une autre manière de procéder est la suivante :

    $ var=\$v1
    $ v1=un
    $ 
    $ eval echo $var
    un
    $
    

    Cette formulation permet de généraliser le mécanisme et d’obtenir une indirection multiniveau.

    $ var=\$v1
    $ v1=\$v2
    $ v2=\$v3
    $ v3=trois
    $ 
    $ eval eval eval echo $var
    trois
    $
    

    Les interprétations successives donnent les résultats suivants :

    $ echo $var
    $v1
    $ eval echo $var
    $v2
    $ eval eval echo $var
    $v3
    $
    $ eval eval eval echo $var
    trois
    $