12. Substitution de commandes

Une substitution de commande réassigne la sortie d'une commande [54] ou même de multiples commandes ; elle branche littéralement la sortie d'une commande sur un autre contexte. [55]

La forme classique de la substitution de commande utilise les apostrophes inverses (`...`). 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.
[Note]

Note

La substitution de commande appelle un sous-shell.

[Attention]

Attention

Les substitutions de commandes peuvent provoquer des coupures de mot.

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 sans coupure de mots, une substitution de commande peut couper 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.

# Auteur : St&eacute;phane Chazelas.
[Attention]

Attention

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.

# Remarquez que les variables peuvent contenir des espaces,
#+ ou même (horreur !), des caractères de contrôle.
#+
# Il n'est pas nécessaire d'affecter une variable explicitement.
echo "` &lt;$0`"          # Affiche le script lui-même sur la sortie
                          #+ standard.
#  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
[Attention]

Attention

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 12.1. Astuces 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é.

La 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 12.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

[Note]

Note

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 \\)
\
              

Dans sa forme $(...), la substitution de commande autorise les imbrications. [56]

word_count=$( wc -w $(echo * | awk '{print $8}') )

ou quelque chose d'un peu plus élaboré...

Exemple 12.3. Découvrir des anagrammes

#!/bin/bash
# agram2.sh
# Example of nested command substitution.

#  Uses "anagram" utility
#+ that is part of the author's "yawl" word list package.
#  http://ibiblio.org/pub/Linux/libs/yawl-0.3.2.tar.gz
#  http://bash.webofcrafts.net/yawl-0.3.2.tar.gz

E_NOARGS=66
E_BADARG=67
MINLEN=7

if [ -z "$1" ]
then
  echo "Usage $0 LETTERSET"
  exit $E_NOARGS         # Script needs a command-line argument.
elif [ ${#1} -lt $MINLEN ]
then
  echo "Argument must have at least $MINLEN letters."
  exit $E_BADARG
fi



FILTER='.......'         # Must have at least 7 letters.
#       1234567
Anagrams=( $(echo $(anagram $1 | grep $FILTER) ) )
#          $(     $(  nested command sub.    ) )
#        (              array assignment         )

echo
echo "${#Anagrams[*]}  7+ letter anagrams found"
echo
echo ${Anagrams[0]}      # First anagram.
echo ${Anagrams[1]}      # Second anagram.
                         # Etc.

# echo "${Anagrams[*]}"  # To list all the anagrams in a single line . . .

#  Look ahead to the "Arrays" chapter for enlightenment on
#+ what's going on here.

# See also the agram.sh script for an example of anagram finding.

exit $?

Exemples de substitution de commandes dans des scripts shell :

  1. Exemple 11.7, « Un équivalent de grep pour les fichiers binaires »

  2. Exemple 11.26, « Utiliser la substitution de commandes pour générer la variable case »

  3. Exemple 9.16, « Réinitialiser RANDOM »

  4. Exemple 16.3, « Badname élimine dans le répertoire courant les fichiers dont le nom contient des caractères incorrects et des espaces blancs. »

  5. Exemple 16.22, « lowercase : Change tous les noms de fichier du répertoire courant en minuscule. »

  6. Exemple 16.17, « Émuler grep dans un script »

  7. Exemple 16.54, « Utiliser seq pour générer l'incrément d'une boucle »

  8. Exemple 11.13, « Utilisation de efax en mode batch »

  9. Exemple 11.10, « Afficher les liens symboliques dans un répertoire »

  10. Exemple 16.32, « Supprimer les commentaires des programmes C »

  11. Exemple 20.8, « Boucle for redirigée »

  12. Exemple A.16, « tree: Afficher l'arborescence d'un répertoire »

  13. Exemple 29.3, « Trouver le processus associé à un PID »

  14. Exemple 16.47, « Paiement mensuel sur une hypothèque »

  15. Exemple 16.48, « Conversion de base »

  16. Exemple 16.49, « Appeler bc en utilisant un document en ligne »



[54] 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.

[55] 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 =.

[56] 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 \`echo * | awk '{print $8}'\` `