Retornando valor da função chamada em um script de shell

126

Eu quero retornar o valor de uma função chamada em um script de shell. Talvez eu esteja sentindo falta da sintaxe. Eu tentei usar as variáveis ​​globais. Mas isso também não está funcionando. O código é:

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
fonte
Não está relacionado à sua pergunta, mas mesmo assim ... se você está tentando obter um bloqueio, pode usar o comando "lockfile".
Víctor Herraiz

Respostas:

277

Uma função Bash não pode retornar uma string diretamente como você deseja. Você pode fazer três coisas:

  1. Ecoar uma string
  2. Retornar um status de saída, que é um número, não uma sequência
  3. Compartilhar uma variável

Isto também é verdade para algumas outras conchas.

Veja como fazer cada uma dessas opções:

1. Cordas 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. Retornar status de saída

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. Variável de compartilhamento

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
fonte
2
Não use uma functionpalavra-chave para definir uma função bash. Isso tornaria menos portátil. Removendo-o.
Dimir
2
No seu terceiro exemplo, retval não é uma variável de ambiente. É apenas uma variável de shell. Ele se tornará uma variável de ambiente apenas se você a exportar. Talvez o título do terceiro exemplo deva ser "variável global" em vez de "variável de ambiente".
William Pursell
4
No segundo exemplo, em vez de atribuir a partir de $?, É mais idiomático escrever "se testlock; então ..."
William Pursell
@WilliamPursell Eu removi a palavra errada 'ambiente'. Vamos manter "$?" para fins pedagógicos. Tenho habilitado comunidade Wiki, para que são todos gratuitos para melhorar a resposta ;-)
olibre
1
@ManuelJordan, o Functions pode retornar apenas códigos de saída e> & 2 logs para stderror; portanto, o último eco é gravado em stdout; portanto, a função de chamada APENAS captura stdout e não stderr. Supondo que a execução seja de thread único, uma opção melhor é manter uma variável personalizada específica, como TEST_LOCK_STATUS = "" método externo que qualquer pessoa possa usar após chamar o testlock e redefini-la sempre no início do método
kisna
16

Você está trabalhando demais. Seu script inteiro deve ser:

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

mas mesmo isso é provavelmente muito detalhado. Eu codificaria:

mkdir "$lockdir" || exit 1

mas a mensagem de erro resultante é um pouco obscura.

William Pursell
fonte
1
A mensagem de erro ausente é fácil de corrigir, mesmo que seja um pouco mais detalhada: mkdir "$lockdir" || { echo "could not create lock dir" >&2 ; exit 1 ; }(observe o ;antes da chave de fechamento). Além disso, costumo definir uma função de falha que recebe um parâmetro de mensagem opcional que é impresso no stderr e sai com o código de retorno 1, permitindo que eu use o mais legível mkdir "$lockdir" || fail "could not create lock dir".
blubberdiblub
@blubberdiblub: mas a função de falha não pode sair da função ou script "atual", pode? então você teria que usar cmd || fail "error msg" || return 1se você quiser fazer isso, é?
Max
@Max não é a função atual, está correta. Mas ele sairá do script atual, contanto que você o chame como comando e não o tenha como fonte . Normalmente, penso em uma failfunção usada apenas para situações fatais.
blubberdiblub 15/07
12

Se for apenas um teste verdadeiro / falso, tenha sua função return 0para o sucesso e o return 1fracasso. O teste seria então:

if function_name; then
  do something
else
  error condition
fi
Glenn Jackman
fonte
Exatamente o que eu estava procurando.
Samuel
Existe uma maneira de usar essa notação também para funções parametrizadas?
284 Alex
@alex você pode dar um exemplo do que você quer dizer com "função parametrizada"?
Glenn Jackman
'myCopyFunc $ {SOURCE} $ {DEST}', retorne 0 com êxito. Por exemplo, como nesta edição: stackoverflow.com/questions/6212219/…
Alex
Sim, está perfeitamente bem
Glenn Jackman
2

Eu acho que retornar 0 para succ / 1 para fail (glenn jackman) e a resposta clara e explicativa de olibre diz tudo; apenas para mencionar um tipo de abordagem "combinada" para os casos em que os resultados não são binários e você prefere definir uma variável em vez de "ecoar" um resultado (por exemplo, se sua função TAMBÉM supor ecoar algo, essa abordagem será não funciona). O que então? (abaixo é Bourne Shell)

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

como em (sim, o exemplo é um pouco bobo, é apenas um .. exemplo)

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

Caso você tenha alguns parâmetros para passar para uma função e deseje um valor em troca. Aqui estou passando "12345" como argumento para uma função e após o processamento retornando a variável XYZ que será atribuída a VALUE

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


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

Resultado:

something12345
Rishi Bansal
fonte