23.1. Fonctions complexes et complexité des fonctions

Les fonctions peuvent récupérer des arguments qui leur sont passés et renvoyer un code de sortie au script pour utilisation ultérieure.

nom_fonction $arg1 $arg2

La fonction se réfère aux arguments passés par leur position (comme si ils étaient des paramètres positionnels), c'est-à-dire $1, $2, et ainsi de suite.

Exemple 23-2. Fonction prenant des paramètres

#!/bin/bash
# Functions and parameters

DEFAUT=defaut                               # Valeur par défaut.

fonc2 () {
   if [ -z "$1" ]                           # Est-ce que la taille du paramètre
	                                    # #1 a une taille zéro?
   then
     echo "-Le paramètre #1 a une taille nulle.-"  # Ou aucun paramètre n'est passé.
   else
     echo "-Le paramètre #1 est \"$1\".-"
   fi

   variable=${1-$DEFAUT}                    #  Que montre la substitution de
   echo "variable = $variable"              #+ paramètre?
                                            #  ---------------------------
                                            #  Elle distingue entre pas de
                                            #+ paramètre et un paramètre nul.

   if [ "$2" ]
   then
     echo "-Le paramètre #2 est \"$2\".-"
   fi

   return 0
}

echo
   
echo "Aucun argument."   
fonc2                          # Appelé sans argument
echo


echo "Argument de taille nulle."
fonc2 ""                       # Appelé avec un paramètre de taille zéro
echo

echo "Paramètre nul."
fonc2 "$parametre_non_initialise" # Appelé avec un paramètre non initialisé
echo

echo "Un paramètre."   
fonc2 first           # Appelé avec un paramètre
echo

echo "Deux paramètres."   
fonc2 first second    # Appelé avec deux paramètres
echo

echo "\"\" \"second\" comme argument."
fonc2 "" second       # Appelé avec un premier paramètre de taille nulle,
echo                  # et une chaîne ASCII pour deuxième paramètre.

exit 0

Important

La commande shift fonctionne sur les arguments passés aux fonctions (voir Exemple 34-10).

Note

En contraste avec certains autres langages de programmation, les scripts shell passent normalement seulement des paramètres par valeur aux fonctions. [1] Les noms de variables (qui sont réellement des pointeurs), s'ils sont passés en tant que paramètres de fonctions, seront traités comme des chaînes de caractères et ne pourront être déréférencés. Les fonctions interprètent leur arguments littéralement.

Sortie et retour

code de sortie

Les fonctions renvoient une valeur, appellée un code (ou état) de sortie. Le code de sortie peut être explicitement spécifié par une instruction return, sinon il s'agit du code de sortie de la dernière commande de la fonction (0 en cas de succès et une valeur non nulle sinon). Ce status de sortie peut être utilisé dans le script en le référanceant à l'aide de la variable $?. Ce mécanisme permet effectivement aux fonctions des scripts d'avoir une << valeur de retour >> similaire à celle des fonctions C.

return

Termine une fonction. Une commande return [2] prend optionnellement un argument de type entier, qui est renvoyé au script appelant comme << code de sortie >> de la fonction, et ce code de sortie est affecté à la variable $?.

Exemple 23-3. Maximum de deux nombres

#!/bin/bash
# max.sh: Maximum de deux entiers.

E_PARAM_ERR=-198   # Si moins de deux paramètres passés à la fonction.
EGAL=-199          # Code de retour si les deux paramètres sont égaux.

max2 ()            # Envoie le plus important des deux entiers.
{                  # Note: les nombres comparés doivent être plus petit que 257.
if [ -z "$2" ]
then
  return $E_PARAM_ERR
fi

if [ "$1" -eq "$2" ]
then
  return $EGAL
else
  if [ "$1" -gt "$2" ]
  then
    return $1
  else
    return $2
  fi
fi
}

max2 33 34
return_val=$?

if [ "$return_val" -eq $E_PARAM_ERR ]
then
	echo "Vous devez donner deux arguments à la fonction."
elif [ "$return_val" -eq $EGAL ]
  then
    echo "Les deux nombres sont identiques."
else
    echo "Le plus grand des deux nombres est $return_val."
fi  

  
exit 0

#  Exercice (facile):
#  ---------------
#  Convertir ce script en une version interactive,
#+ c'est-à-dire que le script vous demande les entrées (les deux nombres).

Astuce

Pour qu'une fonction renvoie une chaîne de caractères ou un tableau, utilisez une variable dédiée.
compte_lignes_dans_etc_passwd()
{
  [[ -r /etc/passwd ]] && REPONSE=$(echo $(wc -l < /etc/passwd))
  # Si /etc/passwd est lisible, met dans REPONSE le nombre de lignes.
  # Renvoie une valeur et un statut.
}

if compte_ligne_dans_etc_passwd
then
  echo "Il y a $REPONSE lignes dans /etc/passwd."
else
  echo "Ne peut pas compter les lignes dans /etc/passwd."
fi  

# Merci, S.C.

Exemple 23-4. Convertire des nombres en chiffres romains

#!/bin/bash

# Conversion d'un nombre arabe en nombre romain
# Echelle: 0 - 200
# C'est brut, mais cela fonctionne.

# Etendre l'échelle et améliorer autrement le script est laissé en exercice.

# Usage: roman nombre-a-convertir

LIMITE=200
E_ERR_ARG=65
E_HORS_ECHELLE=66

if [ -z "$1" ]
then
  echo "Usage: `basename $0` nombre-a-convertir"
  exit $E_ERR_ARG
fi  

num=$1
if [ "$num" -gt $LIMITE ]
then
  echo "En dehors de l'échelle!"
  exit $E_HORS_ECHELLE
fi  

vers_romain ()   # Doit déclarer la fonction avant son premier appel.
{
nombre=$1
facteur=$2
rchar=$3
let "reste = nombre - facteur"
while [ "$reste" -ge 0 ]
do
  echo -n $rchar
  let "nombre -= facteur"
  let "reste = nombre - facteur"
done  

return $nombre
       # Exercixe:
       # --------
       # Expliquer comment fonctionne cette fonction.
       # Astuce: division par une soustraction successive.
}
   

vers_romain $nombre 100 C
nombre=$?
vers_romain $nombre 90 LXXXX
nombre=$?
vers_romain $nombre 50 L
nombre=$?
vers_romain $nombre 40 XL
nombre=$?
vers_romain $nombre 10 X
nombre=$?
vers_romain $nombre 9 IX
nombre=$?
vers_romain $nombre 5 V
nombre=$?
vers_romain $nombre 4 IV
nombre=$?
vers_romain $nombre 1 I

echo

exit 0

Voir aussi Exemple 10-28.

Important

L'entier positif le plus grand qu'une fonction peut renvoyer est 256. La commande return est très liée au code de sortie, qui tient compte de cette limite particulière. Heureusement, il existe quelques astuces pour ces situations réclamant une valeur de retour sur un grand entier.

Exemple 23-5. Tester les valeurs de retour importantes dans une fonction

#!/bin/bash
# return-test.sh

# La plus grande valeur positive qu'une fonction peut renvoyer est 256.

test_retour ()         # Renvoie ce qui lui est passé.
{
  return $1
}

test_retour 27         # OK.
echo $?                # Renvoie 27.
  
test_retour 256        # Toujours OK.
echo $?                # Renvoie 256.

test_retour 257        # Erreur!
echo $?                # Renvoie 1 (code d'erreur divers).

test_retour -151896    #  Néanmoins, les valeurs négatives peuvent être plus
                       #+ importantes.
echo $?                # Renvoie -151896.

exit 0

Comme nous l'avons vu, une fonction peut retourner une valeur négative importante. Ceci permet aussi de retourner un grand entier positif, en utilisant un peu d'astuce.

Un autre moyen d'accomplir ceci est d'affecter simplement le << code de retour >> à une variable globale.
Val_Retour=   # Variable globale pour recevoir une valeur de retour d'une taille trop importante.

alt_return_test ()
{
  fvar=$1
  Val_Retour=$fvar
  return   # Renvoie 0 (succès).
}

alt_return_test 1
echo $?                              # 0
echo "valeur de retour = $Val_Retour"    # 1

alt_return_test 256
echo "valeur de retour = $Val_Retour"    # 256

alt_return_test 257
echo "valeur de retour = $Val_Retour"    # 257

alt_return_test 25701
echo "valeur de retour = $Val_Retour"    #25701

Exemple 23-6. Comparer deux grands entiers

#!/bin/bash
# max2.sh: Maximum de deux entiers LARGES.

# Ceci correspond au précédent exemple "max.sh", modifié pour permettre la
# comparaison d'entiers larges.

EGAL=0              # Code de retour si les deux paramètres sont égaux.
VALRETOURMAX=256    # Code de retour positif maximum à partir d'une fonction.
E_ERR_PARAM=-99999  # Erreur de paramètre.
E_ERR_NPARAM=99999  # Erreur de paramètre "normalisé".

max2 ()             # Renvoie le plus gros des deux nombres.
{
if [ -z "$2" ]
then
  return $E_ERR_PARAM
fi

if [ "$1" -eq "$2" ]
then
  return $EGAL
else
  if [ "$1" -gt "$2" ]
  then
    retval=$1
  else
    retval=$2
  fi
fi

# -------------------------------------------------------------- #
# Ceci est une astuce pour renvoyer un entier large à partir de
# cette fonction.
if [ "$retval" -gt "$VALRETOURMAX" ] # Si en dehors de l'échelle,
then                                 # alors
  let "retval = (( 0 - $retval ))"   # ajuster à une valeur négative.
  # (( 0 - $VALUE )) change le signe de VALUE.
fi
# Heureusement les codes de retour négatifs larges sont permis.
# -------------------------------------------------------------- #

return $retval
}

max2 33001 33997
return_val=$?

# -------------------------------------------------------------------------- #
if [ "$return_val" -lt 0 ]                  # Si nombre négatif "ajusté",
then                                        # alors
  let "return_val = (( 0 - $return_val ))"  # retour au positif.
fi                                          # "Valeur absolue" de $return_val.  
# -------------------------------------------------------------------------- #


if [ "$return_val" -eq "$E_ERR_NPARAM" ]
then                   # La variable d'erreur de paramètre a aussi un changement
	               # e signe.
  echo "Erreur: Pas assez de paramètres."
elif [ "$return_val" -eq "$EGAL" ]
  then
    echo "Les deux nombres sont égaux."
else
    echo "Le plus grand des deux nombres est $return_val."
fi  
  
exit 0

Voir aussi Exemple A-8.

Exercice: Utiliser ce que nous venons d'apprendre, étendre l'exemple précédent sur le nombres romains pour accepter une entrée arbitrairement grande.

Redirection

Rediriger le stdin d'une fonction

Une fonction est essentiellement un bloc de code, ce qui signifie que stdin peut être redirigé (comme dans Exemple 3-1).

Exemple 23-7. Vrai nom pour un utilisateur

#!/bin/bash

# A partir du nom utilisateur, obtenir le "vrai nom" dans /etc/passwd.

NBARGS=1  # Attend un arg.
E_MAUVAISARGS=65

fichier=/etc/passwd
modele=$1

if [ $# -ne "$NBARGS" ]
then
  echo "Usage: `basename $0` NOMUTILISATEUR"
  exit $E_MAUVAISARGS
fi  

partie_fichier ()  # Scanne le fichier pour trouver modele, l'affichage correct
                   # se fait sur la ligne en question.
{
while read ligne  # while n'a pas nécessairement besoin d'une "[ condition]"
do
  echo "$ligne" | grep $1 | awk -F":" '{ print $5 }'
      # awk utilise le délimiteur ":".
done
} <$fichier  # Redirige le stdin de la fonction.

partie_fichier $modele

# Oui, le script entier peut être réduit en
#       grep MODELE /etc/passwd | awk -F":" '{ print $5 }'
# ou
#       awk -F: '/MODELE/ {print $5}'
# ou
#       awk -F: '($1 == "nomutilisateur") { print $5 }' # vrai nom à partir du nom utilisateur
# Néanmoins, ce n'est pas aussi instructif.

exit 0

Il existe une autre méthode, certainement moins compliquée , de rediriger le stdin d'une fonction. Celle-ci fait intervenir la redirection de stdin vers un bloc de code entre accolades contenu à l'intérieur d'une fonction.
# Au lieu de:
Function ()
{
 ...
 } < fichier

# Essayez ceci:
Function ()
{
  {
    ...
   } < fichier
}

# Similairement,

Function ()  # Ceci fonctionne.
{
  {
   echo $*
  } | tr a b
}

Function ()  # Ceci ne fonctionne pas.
{
  echo $*
} | tr a b   # Un bloc de code intégré est obligatoire ici.


# Merci, S.C.

Notes

[1]

Les références de variables indirectes (voir Exemple 35-2) apportent une espèce de mécanisme peu pratique pour passer des pointeurs aux fonctions.
#!/bin/bash

ITERATIONS=3  # Combien de fois obtenir une entrée.
icompteur=1

ma_lecture () {
  # Appeler: ma_lecture nomvariable,
  # Affiche la précédente valeur entre crochets comme valeur par défaut,
  # et demande une nouvelle valeur.

  local var_locale

  echo -n "Entrez une valeur "
  eval 'echo -n "[$'$1'] "'  # Précédente valeur value.
  read var_locale
  [ -n "$var_locale" ] && eval $1=\$var_locale

  # "liste-ET": si "var_locale" alors l'initialisez à "$1".
}

echo

while [ "$icompteur" -le "$ITERATIONS" ]
do
  ma_lecture var
  echo "Entrée #$icompteur = $var"
  let "icompteur += 1"
  echo
done  


# Merci à Stephane Chazelas pour nous avoir apporté cet exemple instructif.

exit 0

[2]

La commande return est une commande intégrée Bash.

>Commandes pour les fichiers et l'archivageNiveau supérieurCommandes de contrôle du terminal gzip, ln, patch, tar, tr, bc, xargs. The texinfo documentation on bash, dd, m4, gawk, and sed.

ALIGN="left" VALIGN="top" >PrécédentSommaireSuivantConstructeurs de listesNiveau supérieurFichiers