Renvoyer la valeur de la fonction appelée dans un script shell

126

Je souhaite renvoyer la valeur d'une fonction appelée dans un script shell. Peut-être que je manque la syntaxe. J'ai essayé d'utiliser les variables globales. Mais cela ne fonctionne pas non plus. Le code est:

lockdir="somedir"
test() {
    retval=""

    if mkdir "$lockdir"
        then    # Directory did not exist, but it was created successfully
            echo >&2 "successfully acquired lock: $lockdir"
            retval="true"
        else
            echo >&2 "cannot acquire lock, giving up on $lockdir"
            retval="false"
    fi
    return retval
}


retval=test()
if [ "$retval" == "true" ]
    then
        echo "directory not created"
    else
        echo "directory already created"
fi
Mridul Vishal
la source
Pas lié à votre question, mais de toute façon ... si vous essayez d'obtenir un verrou, vous pouvez utiliser la commande "lockfile".
Víctor Herraiz

Réponses:

277

Une fonction Bash ne peut pas renvoyer une chaîne directement comme vous le souhaitez. Vous pouvez faire trois choses:

  1. Faire écho à une chaîne
  2. Renvoie un statut de sortie, qui est un nombre, pas une chaîne
  3. Partager une variable

Ceci est également vrai pour certains autres obus.

Voici comment faire chacune de ces options:

1. Chaînes d'écho

lockdir="somedir"
testlock(){
    retval=""
    if mkdir "$lockdir"
    then # Directory did not exist, but it was created successfully
         echo >&2 "successfully acquired lock: $lockdir"
         retval="true"
    else
         echo >&2 "cannot acquire lock, giving up on $lockdir"
         retval="false"
    fi
    echo "$retval"
}

retval=$( testlock )
if [ "$retval" == "true" ]
then
     echo "directory not created"
else
     echo "directory already created"
fi

2. Retourner l'état de sortie

lockdir="somedir"
testlock(){
    if mkdir "$lockdir"
    then # Directory did not exist, but was created successfully
         echo >&2 "successfully acquired lock: $lockdir"
         retval=0
    else
         echo >&2 "cannot acquire lock, giving up on $lockdir"
         retval=1
    fi
    return "$retval"
}

testlock
retval=$?
if [ "$retval" == 0 ]
then
     echo "directory not created"
else
     echo "directory already created"
fi

3. Variable de partage

lockdir="somedir"
retval=-1
testlock(){
    if mkdir "$lockdir"
    then # Directory did not exist, but it was created successfully
         echo >&2 "successfully acquired lock: $lockdir"
         retval=0
    else
         echo >&2 "cannot acquire lock, giving up on $lockdir"
         retval=1
    fi
}

testlock
if [ "$retval" == 0 ]
then
     echo "directory not created"
else
     echo "directory already created"
fi
olibre
la source
2
N'utilisez pas de functionmot clé pour définir une fonction bash. Cela le rendrait moins portable. Enlevez-le.
dimir
2
Dans votre troisième exemple, retval n'est pas une variable d'environnement. C'est simplement une variable shell. Elle ne deviendra une variable d'environnement que si vous l'exportez. Le titre du troisième exemple devrait peut-être être "variable globale" au lieu de "variable d'environnement".
William Pursell
4
Dans le deuxième exemple, plutôt que d'attribuer à partir de $?, Il est plus idiomatique d'écrire "if testlock; then ..."
William Pursell
@WilliamPursell J'ai supprimé le mauvais mot «environnement». Gardons "$?" à des fins pédagogiques. J'ai activé la communauté Wiki, vous êtes donc tous libres d'améliorer la réponse ;-)
olibre
1
@ManuelJordan, les fonctions ne peuvent renvoyer que les codes de sortie et les journaux> & 2 à stderror, donc le dernier écho est écrit dans stdout, donc la fonction appelante capture UNIQUEMENT stdout et non stderr. En supposant que l'exécution est à thread unique, une meilleure option est de maintenir une variable personnalisée spécifique comme TEST_LOCK_STATUS = "" en dehors de la méthode que tout le monde peut utiliser après avoir appelé testlock et le réinitialiser à chaque fois au début de la méthode
kisna
16

Vous travaillez beaucoup trop dur. Votre script entier doit être:

if mkdir "$lockdir" 2> /dev/null; then 
  echo lock acquired
else
  echo could not acquire lock >&2
fi

mais même cela est probablement trop verbeux. Je le coderais:

mkdir "$lockdir" || exit 1

mais le message d'erreur qui en résulte est un peu obscur.

William Pursell
la source
1
Le message d'erreur manquant est assez facile à corriger, même s'il est légèrement plus détaillé: mkdir "$lockdir" || { echo "could not create lock dir" >&2 ; exit 1 ; }(notez le ;devant l'accolade fermante). De plus, je définis souvent une fonction d'échec qui prend un paramètre de message facultatif qu'elle imprime sur stderr, puis se termine avec le code de retour 1, ce qui me permet d'utiliser le plus lisible mkdir "$lockdir" || fail "could not create lock dir".
blubberdiblub
@blubberdiblub: mais la fonction d'échec ne peut pas quitter la fonction ou le script "courant", n'est-ce pas? vous devrez donc l'utiliser cmd || fail "error msg" || return 1si vous souhaitez faire cela, n'est-ce pas?
Max
@Max pas la fonction actuelle, c'est correct. Mais il quittera le script actuel, tant que vous l'avez appelé en tant que commande et que vous ne l'avez pas généré . Je pense généralement à une telle failfonction utilisée uniquement pour des situations mortelles.
blubberdiblub
12

Si c'est juste un test vrai / faux, ayez votre fonction return 0pour le succès et return 1pour l'échec. Le test serait alors:

if function_name; then
  do something
else
  error condition
fi
Glenn Jackman
la source
Exactement ce que je cherchais.
Samuel
Existe-t-il un moyen d'utiliser également cette notation pour les fonctions paramétrées?
Alex
@alex pouvez-vous donner un exemple de ce que vous entendez par «fonction paramétrée»?
glenn jackman le
'myCopyFunc $ {SOURCE} $ {DEST}', renvoie 0 en cas de succès. Par exemple, comme dans ce numéro: stackoverflow.com/questions/6212219/…
Alex
Oui, c'est parfaitement bien
glenn jackman
2

Je pense que renvoyer 0 pour succ / 1 pour échec (glenn jackman) et la réponse claire et explicative d'olibre dit tout; juste pour mentionner une sorte d'approche "combo" pour les cas où les résultats ne sont pas binaires et que vous préférez définir une variable plutôt que de "renvoyer" un résultat (par exemple, si votre fonction est AUSSI supposée faire écho à quelque chose, cette approche ne fonctionne pas). Et alors? (ci-dessous est Bourne Shell)

# Syntax _w (wrapReturn)
# arg1 : method to wrap
# arg2 : variable to set
_w(){
eval $1
read $2 <<EOF
$?
EOF
eval $2=\$$2
}

comme dans (oui, l'exemple est un peu idiot, c'est juste un ... exemple)

getDay(){
  d=`date '+%d'`
  [ $d -gt 255 ] && echo "Oh no a return value is 0-255!" && BAIL=0 # this will of course never happen, it's just to clarify the nature of returns
  return $d
}

dayzToSalary(){
  daysLeft=0
  if [ $1 -lt 26 ]; then 
      daysLeft=`expr 25 - $1`
  else
     lastDayInMonth=`date -d "`date +%Y%m01` +1 month -1 day" +%d`
     rest=`expr $lastDayInMonth - 25`
     daysLeft=`expr 25 + $rest`
  fi
  echo "Mate, it's another $daysLeft days.."
}

# main
_w getDay DAY # call getDay, save the result in the DAY variable
dayzToSalary $DAY
Ola Aronsson
la source
1

Si vous avez des paramètres à passer à une fonction et que vous souhaitez une valeur en retour. Ici, je passe "12345" comme argument à une fonction et après le traitement, je renvoie la variable XYZ qui sera affectée à VALUE

#!/bin/bash
getValue()
{
    ABC=$1
    XYZ="something"$ABC
    echo $XYZ
}


VALUE=$( getValue "12345" )
echo $VALUE

Production:

something12345
Rishi Bansal
la source