Une substitution de commande réassigne la sortie d'une commande [43] ou même de multiples commandes ; elle branche littéralement la sortie d'une commande sur un autre contexte. [44]
La forme classique de la substitution de commande utilise l'apostrophe inverse (`...`). Les commandes placées à l'intérieur de ces apostrophes inverses génèrent du texte en ligne de commande.
nom_du_script=`basename $0` echo "Le nom de ce script est $nom_du_script."
La sortie des commandes peut être utilisée comme argument d'une autre commande, pour affecter une variable, voire pour génerer la liste des arguments dans une boucle for.
rm `cat nomfichier` # <quote>nomfichier</quote> contient une liste de fichiers à effacer.
#
# S. C. fait remarquer qu'une erreur "arg list too long" (liste d'arguments
#+ trop longue) pourrait en résulter.
# Mieux encore xargs rm -- < nomfichier
# ( -- couvre les cas dans lesquels <quote>nomfichier</quote> commence par un
#+ <quote>-</quote> )
listing_fichierstexte=`ls *.txt`
# Cette variable contient les noms de tous les fichiers *.txt
#+ du répertoire de travail actuel.
echo $listing_fichierstexte
listing_fichierstexte2=$(ls *.txt) # La forme alternative d'une substitution
#+ de commande.
echo $listing_fichierstexte2
# Même résultat.
# Un problème qui peut survenir lorsqu'on place une liste de fichiers dans
#+ une chaîne simple est qu'une nouvelle ligne peut s'y glisser.
# Une méthode plus sûre pour assigner une liste de fichiers à un paramètre est
#+ d'utiliser un tableau.
# shopt -s nullglob # S'il n'y a pas de correspondance, les noms de
#+ #+ fichier sont transformés en chaîne vide.
# listing_fichierstextes=( *.txt )
#
# Merci, S.C.
La substitution de commandes appelle un sous-shell.
Les substitutions de commandes peuvent provoquer des coupures de mots.
COMMANDE `echo a b` # 2 arguments: a et b COMMANDE "`echo a b`" # 1 argument : "a b" COMMANDE `echo` # pas d'argument COMMANDE "`echo`" # un argument vide # Merci, S.C.
Même s'il n'y a pas coupure de mots, une substitution de commandes peut ôter les retours à la ligne finaux.
# cd "`pwd`" # Ceci devrait toujours fonctionner.
# Néanmoins...
mkdir 'répertoire avec un retour à la ligne final
'
cd 'répertoire avec un retour à la ligne final
'
cd "`pwd`" # Message d'erreur:
# bash: cd: /tmp/fichier avec un retour à la ligne final : Pas de fichier
#+ ou répertoire
cd "$PWD" # Fonctionne parfaitement.
ancien_parametrage_tty=$(stty -g) # Sauve les anciens paramètres du terminal.
echo "Appuyez sur une touche "
stty -icanon -echo # Désactive le mode "canonique" du terminal.
# Désactive également l'écho *local* .
touche=$(dd bs=1 count=1 2> /dev/null) # Utilisation de dd pour obtenir
#+ l'appui d'une touche.
stty "$ancien_parametrage_tty" # Restaure les anciens paramètres.
echo "Vous avez appuyé sur ${#touche} touche." # ${#variable} = $variable
#
# Appuyez sur toute autre touche que RETURN, et la sortie devient "Vous avez
#+ appuyé sur 1 touche"
# Appuyez sur RETURN, et c'est "Vous avez appuyé sur 0 touche."
# Le retour à la ligne a été avalé par la substitution de commande.
Merci, S.C.
L'utilisation d'echo pour afficher la valeur d'une variable non protégée affectée à l'aide d'une substitution de commande retire les caractères de nouvelle ligne finaux de la sortie des commandes ainsi redirigées, ce qui peut créer des surprises désagréables.
listing_rep=`ls -l` echo $listing_rep # non protégée # Dans l'attente de la liste bien ordonnée du contenu d'un répertoire. # En fait, voici ce que l'on obtient: # total 3 -rw-rw-r-- 1 bozo bozo 30 May 13 17:15 1.txt -rw-rw-r-- 1 bozo # bozo 51 May 15 20:57 t2.sh -rwxr-xr-x 1 bozo bozo 217 Mar 5 21:13 wi.sh # Les retours à la ligne ont disparu. echo "$listing_rep" # protégée # -rw-rw-r-- 1 bozo 30 May 13 17:15 1.txt # -rw-rw-r-- 1 bozo 51 May 15 20:57 t2.sh # -rwxr-xr-x 1 bozo 217 Mar 5 21:13 wi.sh
La substitution de commande permet même d'affecter à une variable le contenu d'un fichier, en utilisant soit une redirection soit la commande cat
variable1=`<fichier1` # Affecte à "variable1" le contenu de "fichier1".
variable2=`cat fichier2` # Affecte à "variable2" le contenu de "fichier2".
# Néanmoins, ceci lance un nouveau processus,
#+ donc la ligne de code s'exécute plus lentement que
#+ la version ci-dessus.
# Note :
# Les variables peuvent contenir des espaces,
#+ voire même (horreur), des caractères de contrôle.
# Extraits des fichiers système, /etc/rc.d/rc.sysinit
#+ (sur une installation Red Hat Linux)
if [ -f /fsckoptions ]; then
fsckoptions=`cat /fsckoptions`
...
fi
#
#
if [ -e "/proc/ide/${disk[$device]}/media" ] ; then
hdmedia=`cat /proc/ide/${disk[$device]}/media`
...
fi
#
#
if [ ! -n "`uname -r | grep -- "-"`" ]; then
ktag="`cat /proc/version`"
...
fi
#
#
if [ $usb = "1" ]; then
sleep 5
mouseoutput=`cat /proc/bus/usb/devices 2>/dev/null|grep -E "^I.*Cls=03.*Prot=02"`
kbdoutput=`cat /proc/bus/usb/devices 2>/dev/null|grep -E "^I.*Cls=03.*Prot=01"`
...
fi
Ne pas affecter le contenu d'un gros fichier texte à une variable à moins que vous n'ayez une bonne raison de le faire. Ne pas affecter le contenu d'un fichier binaire à une variable, même pour blaguer.
Exemple 11.1. Trucs de script stupides
#!/bin/bash
# stupid-script-tricks.sh : Ne tentez pas ça chez vous, les gars !
# D'après "Trucs de Scripts Stupides", Volume I.
variable_dangereuse=`cat /boot/vmlinuz` # Le noyau Linux compressé en personne.
echo "longueur de la chaîne \$variable_dangereuse = ${#variable_dangereuse}"
# longueur de la chaîne $variable_dangereuse = 794151
# (ne donne pas le même résultat que 'wc -c /boot/vmlinuz')
# echo "$variable_dangereuse"
# N'essayez pas de faire ça ! Cela figerait le script.
# L'auteur de ce document n'a pas connaissance d'une utilité quelconque pour
#+ l'affectation à une variable du contenu d'un fichier binaire.
exit 0
Notez qu'on ne provoque pas de surcharge de tampon. C'est un exemple où un langage interprété, tel que Bash, fournit plus de protection vis à vis des erreurs de programmation qu'un langage compilé.
Une substitution de commande permet d'affecter à une variable la sortie d'une boucle. L'idée pour y parvenir est de se servir de la sortie d'une commande echo placée à l'intérieur de la boucle.
Exemple 11.2. Générer le contenu d'une variable à partir d'une boucle
#!/bin/bash # csubloop.sh: Initialiser une variable à la sortie d'une boucle. variable1=`for i in 1 2 3 4 5 do echo -n "$i" # La commande 'echo' est essentielle done` #+ à la substitution de commande. echo "variable1 = $variable1" # variable1 = 12345 i=0 variable2=`while [ "$i" -lt 10 ] do echo -n "$i" # A nouveau le nécessaire 'echo'. let "i += 1" # Incrémentation. done` echo "variable2 = $variable2" # variable2 = 0123456789 # Démontre qu'il est possible d'intégrer une boucle à l'intérieur de la #+ déclaration d'une variable. exit 0
La syntaxe $(...) a remplacé les apostrophes inverses pour la substitution de commande.
sortie=$(sed -n /"$1"/p $fichier) # Tiré de l'exemple "grp.sh". # Initialiser une variable avec le contenu d'un fichier texte. Contenu_fichier1=$(cat $fichier1) Contenu_fichier2=$(<$fichier2) # Bash le permet aussi.
La forme $(...) de la substitution de commande traite les doubles antislash d'une façon différente que `...`.
bash$ echo `echo \\`
bash$ echo $(echo \\)
\
La forme $(...) de la substitution de commandes autorise l'imbrication. [45]
word_count=$( wc -w $(ls -l | awk '{print $9}') )
ou quelque chose d'un peu plus élaboré...
Exemple 11.3. Découvrir des anagrammes
#!/bin/bash
# agram2.sh
# Exemple de substitution de commandes imbriquées.
# Utilise l'outil "anagram"
#+ qui fait partie du paquetage de liste de mots "yawl" de l'auteur.
# http://ibiblio.org/pub/Linux/libs/yawl-0.3.2.tar.gz
# http://personal.riverusers.com/~thegrendel/yawl-0.3.2.tar.gz
E_SANSARGS=66
E_MAUVAISARG=67
LONGUEUR_MIN=7
if [ -z "$1" ]
then
echo "Usage $0 LETTRES"
exit $E_SANSARGS # Le script a besoin d'un argument en ligne de commande.
elif [ ${#1} -lt $LONGUEUR_MIN ]
then
echo "L'argument doit avoir au moins $LONGUEUR_MIN lettres."
exit $E_MAUVAISARG
fi
FILTRE='.......' # Doit avoir au moins sept lettres.
# 1234567
Anagrammes=( $(echo $(anagram $1 | grep $FILTRE) ) )
# $( $( sous-commande imbriquée ) )
# ( affectation de tableaux )
# (1) substitution de commandes imbriquées
echo
echo "${#Anagrammes[*]} anagrammes trouvés de sept lettres ou plus"
echo
echo ${Anagrammes[0]} # Premier anagramme.
echo ${Anagrammes[1]} # Deuxième anagramme.
# Etc.
# echo "${Anagrammes[*]}" # Pour lister tous les anagrammes sur une seule ligne...
# Regardez dans le chapitre "Tableaux"
#+ pour des informations sur ce qu'il se passe ici.
# Voir aussi le script agram.sh pour un exemple de recherche d'anagramme.
exit $?
Exemples de substitution de commandes dans des scripts shell :
Exemple 10.7, « Un remplaçant de grep pour les fichiers binaires »
Exemple 10.26, « Utiliser la substitution de commandes pour générer la variable case »
Exemple 15.22, « lowercase : Change tous les noms de fichier du répertoire courant en minuscule. »
Exemple 15.53, « Utiliser seq pour générer l'incrément d'une boucle »
Exemple 10.10, « Afficher les liens symboliques dans un répertoire »
Exemple 15.32, « Supprimer les commentaires des programmes C »
Exemple A.17, « tree: Afficher l'arborescence d'un répertoire »
Exemple 15.48, « Appeler bc en utilisant un document en ligne »
[43] Dans le cadre des substitutions de commande, une commande peut être une commande système externe, une commande intégrée du shell voire même une fonction d'un script.
[44] Sur le plan technique, la substitution de commandes extrait la sortie (stdout) d'une commande et l'affecte à une variable en utilisant l'opérateur =.
[45] En fait, l'imbrication est aussi possible avec des guillemets inversés mais seulement en 'échappant' les guillemets inversés interne comme l'indique John Default.
nb_mots=` wc -w \`ls -l | awk '{print $9}'\` `