Zwracanie wartości z wywołanej funkcji w skrypcie powłoki

126

Chcę zwrócić wartość z funkcji wywołanej w skrypcie powłoki. Być może brakuje mi składni. Próbowałem użyć zmiennych globalnych. Ale to też nie działa. Kod to:

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
źródło
Nie jest to związane z twoim pytaniem, ale mimo wszystko ... jeśli próbujesz uzyskać blokadę, możesz użyć polecenia "lockfile".
Víctor Herraiz

Odpowiedzi:

277

Funkcja Bash nie może zwrócić ciągu bezpośrednio, tak jak chcesz. Możesz zrobić trzy rzeczy:

  1. Powtórz strunę
  2. Zwraca kod zakończenia, który jest liczbą, a nie łańcuchem
  3. Udostępnij zmienną

Dotyczy to również niektórych innych muszli.

Oto jak zrobić każdą z tych opcji:

1. Struny echa

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. Status wyjścia powrotnego

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. Udostępnij zmienną

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
źródło
2
Nie używaj functionsłowa kluczowego do definiowania funkcji bash. To sprawi, że będzie mniej przenośny. Usuwam to.
dimir
2
W trzecim przykładzie retval nie jest zmienną środowiskową. To tylko zmienna powłoki. Stanie się zmienną środowiskową tylko wtedy, gdy ją wyeksportujesz. Być może tytuł trzeciego przykładu powinien brzmieć „zmienna globalna” zamiast „zmienna środowiskowa”.
William Pursell,
4
W drugim przykładzie, zamiast przypisywać z $?, Bardziej idiomatyczne jest napisanie „if testlock; then ...”
William Pursell
@WilliamPursell Usunąłem niewłaściwe słowo „środowisko”. Zachowajmy "$?" w celach pedagogicznych.
Włączyłem
1
@ManuelJordan, Funkcje mogą zwracać tylko kody zakończenia i logi> & 2 na stderror, więc ostatnie echo jest zapisywane na stdout, więc funkcja wywołująca przechwytuje TYLKO stdout, a nie stderr. Zakładając, że wykonanie jest jednowątkowe, lepszą opcją jest zachowanie niestandardowej zmiennej specyficznej dla metody TEST_LOCK_STATUS = "", której każdy może użyć po wywołaniu testlock i zresetowaniu go za każdym razem na początku metody
kisna
16

Pracujesz zbyt ciężko. Twój cały skrypt powinien być:

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

ale nawet to jest prawdopodobnie zbyt szczegółowe. Zakodowałbym to:

mkdir "$lockdir" || exit 1

ale wynikowy komunikat o błędzie jest nieco niejasny.

William Pursell
źródło
1
Brakujący komunikat o błędzie jest dość łatwy do naprawienia, mimo że jest nieco bardziej szczegółowy: mkdir "$lockdir" || { echo "could not create lock dir" >&2 ; exit 1 ; }(zwróć uwagę na ;przed zamykającym nawiasem klamrowym). Ponadto często definiuję funkcję fail, która przyjmuje opcjonalny parametr komunikatu, który wypisuje na stderr, a następnie kończy z kodem powrotu 1, umożliwiając mi użycie bardziej czytelnego mkdir "$lockdir" || fail "could not create lock dir".
blubberdiblub
@blubberdiblub: ale funkcja fail nie może wyjść z "bieżącej" funkcji lub skryptu, prawda? więc musisz użyć, cmd || fail "error msg" || return 1jeśli chcesz to zrobić, prawda?
Max
@Max nie jest bieżącą funkcją, to prawda. Ale zamknie bieżący skrypt, o ile wywołasz go jako polecenie i nie otrzymasz źródła . Zwykle myślę o takiej failfunkcji używanej tylko w sytuacjach krytycznych.
blubberdiblub
12

Jeśli to tylko test prawda / fałsz, miej swoją funkcję return 0na sukces i return 1porażkę. Test wyglądałby wtedy:

if function_name; then
  do something
else
  error condition
fi
glenn jackman
źródło
Dokładnie to, czego szukałem.
Samuel
Czy istnieje sposób użycia tej notacji również dla funkcji sparametryzowanych?
Alex
@alex, czy możesz podać przykład, co masz na myśli, mówiąc o „funkcji sparametryzowanej”?
glenn jackman
„myCopyFunc $ {ŹRÓDŁO} $ {DEST}”, w przypadku sukcesu zwraca 0. Np. Jak w tym wydaniu: stackoverflow.com/questions/6212219/ ...
Alex
Tak, w porządku
glenn jackman
2

Myślę, że zwrócenie 0 dla sukcesu / 1 dla porażki (Glenn Jackman) i jasna i wyjaśniająca odpowiedź Olibre mówi wszystko; żeby wspomnieć o pewnego rodzaju podejściu "combo" dla przypadków, w których wyniki nie są binarne i wolisz ustawić zmienną zamiast "wypowiadać" wynik (na przykład, jeśli twoja funkcja RÓWNIEŻ ma coś powtarzać, to podejście będzie nie działa). Co wtedy? (poniżej Bourne Shell)

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

jak w (tak, przykład jest trochę głupi, to tylko .. przykład)

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
źródło
1

W przypadku, gdy masz jakieś parametry do przekazania do funkcji i chcesz otrzymać wartość w zamian. Tutaj przekazuję "12345" jako argument do funkcji i po przetworzeniu zwracam zmienną XYZ która zostanie przypisana do WARTOŚCI

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


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

Wynik:

something12345
Rishi Bansal
źródło