Возврат значения из вызываемой функции в сценарии оболочки


126

Я хочу вернуть значение из функции, вызываемой в сценарии оболочки. Возможно, мне не хватает синтаксиса. Я пробовал использовать глобальные переменные. Но это тоже не работает. Код такой:

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

Не связано с вашим вопросом, но в любом случае ... если вы пытаетесь получить блокировку, вы можете использовать команду "lockfile".
Víctor Herraiz

Ответы:


277

Функция Bash не может возвращать строку так, как вы этого хотите. Вы можете сделать три вещи:

  1. Эхо строки
  2. Вернуть статус выхода, который является числом, а не строкой
  3. Поделиться переменной

Это верно и для некоторых других оболочек.

Вот как выполнить каждый из этих вариантов:

1. Эхо-струны

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. Статус обратного выхода

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. Поделиться переменной

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

2
Не используйте functionключевое слово для определения функции bash. Это сделало бы его менее портативным. Удаление его.
dimir 05

2
В третьем примере retval не является переменной среды. Это просто переменная оболочки. Он станет переменной среды только в том случае, если вы его экспортируете. Возможно, название третьего примера должно быть «глобальная переменная», а не «переменная среды».
Уильям Перселл 05

4
Во втором примере, вместо того, чтобы назначать из $?, Более идиоматично написать «if testlock; then ...»
Уильям Перселл

@WilliamPursell Я удалил неправильное слово "среда". Оставим "$?" в педагогических целях. Я включил Wiki-сообщество, так что вы все можете улучшить ответ ;-)
olibre

1
@ManuelJordan, функции могут возвращать только коды выхода и> & 2 журналов в stderror, поэтому последнее эхо записывается в stdout, поэтому вызывающая функция ТОЛЬКО захватывает stdout, а не stderr. Предполагая, что выполнение является однопоточным, лучшим вариантом является сохранение специальной пользовательской переменной, такой как TEST_LOCK_STATUS = "", внешнего метода, который любой может использовать после вызова testlock, и сбрасывать его каждый раз при запуске метода
кисна

16

Вы слишком много работаете. Весь ваш сценарий должен быть:

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

но даже это, вероятно, слишком многословно. Я бы закодировал это:

mkdir "$lockdir" || exit 1

но получившееся сообщение об ошибке немного неясно.


1
Отсутствующее сообщение об ошибке достаточно легко исправить, хотя оно немного более подробное: mkdir "$lockdir" || { echo "could not create lock dir" >&2 ; exit 1 ; }(обратите внимание ;на закрывающую фигурную скобку). Кроме того, я часто определяю функцию сбоя, которая принимает необязательный параметр сообщения, который он выводит на stderr, а затем выходит с кодом возврата 1, что позволяет мне использовать более читаемый mkdir "$lockdir" || fail "could not create lock dir".
blubberdiblub

@blubberdiblub: но функция отказа не может выйти из "текущей" функции или скрипта, не так ли? так что вам придется использовать, cmd || fail "error msg" || return 1если вы хотите это сделать, не так ли?
Макс

@Max не текущая функция, это правильно. Но он выйдет из текущего скрипта, если вы вызвали его как команду и не использовали его как источник . Обычно я думаю, что такая failфункция используется только для фатальных ситуаций.
blubberdiblub

12

Если это просто тест «правда / ложь», пусть ваша функция будет return 0успешной или return 1неудачной. Тогда тест будет:

if function_name; then
  do something
else
  error condition
fi

Именно то, что я искал.
Самуэль

Есть ли способ использовать эту нотацию для параметризованных функций?
Alex

@alex, можете ли вы привести пример того, что вы подразумеваете под «параметризованной функцией»?
Гленн Джекман

'myCopyFunc $ {SOURCE} $ {DEST}', вернуть 0 в случае успеха. Например, как в этом выпуске: stackoverflow.com/questions/6212219/…
Alex

Да, это прекрасно
Гленн Джекман

2

Я думаю, что возвращение 0 для succ / 1 для отказа (Гленн Джекман) и ясный и пояснительный ответ Олибра говорит само за себя; просто чтобы упомянуть своего рода "комбинированный" подход для случаев, когда результаты не являются двоичными, и вы предпочитаете устанавливать переменную, а не "выводить эхом" результат (например, если ваша функция ТАКЖЕ предполагает эхо чего-то, этот подход будет не работает). Что тогда? (ниже Bourne Shell)

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

как в (да, пример несколько глупый, это просто .. пример)

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

1

Если у вас есть параметры для передачи функции и вы хотите получить взамен значение. Здесь я передаю «12345» в качестве аргумента функции и после обработки возвращаю переменную XYZ, которой будет присвоено значение VALUE.

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


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

Вывод:

something12345
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.