Devolver el valor de la función llamada en un script de shell

126

Quiero devolver el valor de una función llamada en un script de shell. Quizás me falta la sintaxis. Intenté usar las variables globales. Pero eso tampoco está funcionando. El codigo es:

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
fuente
No está relacionado con su pregunta, pero de todos modos ... si está tratando de obtener un bloqueo, puede usar el comando "lockfile".
Víctor Herraiz

Respuestas:

277

Una función Bash no puede devolver una cadena directamente como lo desea. Puedes hacer tres cosas:

  1. Echo una cuerda
  2. Devuelve un estado de salida, que es un número, no una cadena
  3. Comparte una variable

Esto también es cierto para algunos otros proyectiles.

Aquí le mostramos cómo hacer cada una de esas opciones:

1. cuerdas de eco

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. Estado de salida de retorno

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. Compartir variable

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
fuente
2
No use una functionpalabra clave para definir una función bash. Eso lo haría menos portátil. Quitándolo
dimir
2
En su tercer ejemplo, retval no es una variable de entorno. Es simplemente una variable de shell. Solo se convertirá en una variable de entorno si lo exporta. Quizás el título del tercer ejemplo debería ser "variable global" en lugar de "variable de entorno".
William Pursell
44
En el segundo ejemplo, en lugar de asignar desde $ ?, es más idiomático escribir "if testlock; then ..."
William Pursell el
@WilliamPursell He eliminado la palabra incorrecta de "entorno". Mantengamos "$?" con fines pedagógicos. He habilitado la comunidad Wiki, por lo que son libres de mejorar la respuesta ;-)
olibre
1
@ManuelJordan, las funciones solo pueden devolver códigos de salida y> & 2 registros a stderror, por lo tanto, el último eco se escribe en stdout, por lo que la función de llamada SOLO captura stdout y no stderr. Suponiendo que la ejecución es de un solo subproceso, una mejor opción es mantener una variable personalizada específica como TEST_LOCK_STATUS = "" fuera del método que cualquiera puede usar después de llamar a testlock y restablecerlo cada vez al inicio del método
kisna
16

Estás trabajando demasiado duro. Su guión completo debe ser:

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

pero incluso eso es probablemente demasiado detallado. Lo codificaría:

mkdir "$lockdir" || exit 1

pero el mensaje de error resultante es un poco oscuro.

William Pursell
fuente
1
El mensaje de error que falta es bastante fácil de corregir, aunque es un poco más detallado: mkdir "$lockdir" || { echo "could not create lock dir" >&2 ; exit 1 ; }(tenga en cuenta lo ;anterior antes del cierre de la llave). Además, a menudo defino una función de falla que toma un parámetro de mensaje opcional que imprime en stderr y luego sale con el código de retorno 1, lo que me permite usar el más legible mkdir "$lockdir" || fail "could not create lock dir".
blubberdiblub
@blubberdiblub: pero la función de falla no puede salir de la función o script "actual", ¿verdad? así que tendrías que usarlo cmd || fail "error msg" || return 1si deseas hacer eso, ¿verdad?
Max
@Max no es la función actual, eso es correcto. Pero saldrá del script actual, siempre que lo haya llamado como un comando y no lo haya originado . Por lo general, pienso en una failfunción que se usa solo para situaciones fatales.
blubberdiblub
12

Si es solo una prueba de verdadero / falso, tenga su función return 0para el éxito y return 1para el fracaso. La prueba sería entonces:

if function_name; then
  do something
else
  error condition
fi
Glenn Jackman
fuente
Exactamente lo que estaba buscando.
Samuel
¿Hay alguna manera de usar esta notación también para funciones parametrizadas?
Alex
@alex, ¿puede dar un ejemplo de lo que quiere decir con "función parametrizada"?
Glenn Jackman
'myCopyFunc $ {SOURCE} $ {DEST}', devuelve 0 en caso de éxito. Por ejemplo, como en este número: stackoverflow.com/questions/6212219/…
Alex
Sí, eso está perfectamente bien
Glenn Jackman
2

Creo que devolver 0 para succ / 1 para fail (glenn jackman) y la respuesta clara y explicativa de olibre lo dice todo; solo para mencionar un tipo de enfoque "combinado" para los casos en que los resultados no son binarios y preferiría establecer una variable en lugar de "hacer eco" de un resultado (por ejemplo, si su función TAMBIÉN se supone que repite algo, este enfoque será no trabajo). ¿Entonces que? (a continuación se muestra Bourne Shell)

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

como en (sí, el ejemplo es algo tonto, es solo un ... ejemplo)

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
fuente
1

En caso de que tenga algunos parámetros para pasar a una función y desee un valor a cambio. Aquí paso "12345" como argumento a una función y después de procesar la variable de retorno XYZ que se asignará a VALUE

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


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

Salida:

something12345
Rishi Bansal
fuente