Возвращение логического значения из функции Bash

211

Я хочу написать функцию bash, которая проверяет, имеет ли файл определенные свойства, и возвращает true или false. Затем я могу использовать его в своих сценариях в «если». Но что я должен вернуть?

function myfun(){ ... return 0; else return 1; fi;}

тогда я использую это так:

if myfun filename.txt; then ...

конечно это не работает. Как это можно сделать?

Luca
источник
3
бросьте functionключевое слово, myfun() {...}достаточно
Гленн Джекман
2
Что имеет значение, так ifэто статус нулевого выхода myfun: если выполняется myfunс 0, then ...выполняется; если это что-то еще else ... выполняется.
Eelvex
7
@nhed: functionключевое слово является bashism и приведет к ошибкам синтаксиса в некоторых других оболочках. По сути, это либо ненужно, либо запрещено, так зачем его использовать? Он даже не полезен в качестве цели grep, поскольку его там может и не быть ( ()вместо этого grep ).
Гордон Дэвиссон
3
@GordonDavisson: что? есть другие снаряды? ;-)
nhed
Пожалуйста , не используйте 0 и 1. См stackoverflow.com/a/43840545/117471
Бруно Bronosky

Ответы:

333

Используйте 0 для истины и 1 для ложных.

Образец:

#!/bin/bash

isdirectory() {
  if [ -d "$1" ]
  then
    # 0 = true
    return 0 
  else
    # 1 = false
    return 1
  fi
}


if isdirectory $1; then echo "is directory"; else echo "nopes"; fi

редактировать

Из комментария @ amichair это также возможно

isdirectory() {
  if [ -d "$1" ]
  then
    true
  else
    false
  fi
}


isdirectory() {
  [ -d "$1" ]
}
Erik
источник
4
Нет, вам не нужно этого делать - посмотрите образец.
Эрик
46
Для лучшей читаемости вы можете использовать команду 'true' (которая ничего не делает и успешно завершает, т.е. возвращает 0) и команду 'false' (которая ничего не делает и завершается неудачно, то есть возвращает ненулевое значение). Кроме того, функция, которая заканчивается без явного оператора возврата, возвращает код завершения последней выполненной команды, поэтому в приведенном выше примере тело функции может быть сокращено только до [ -d "$1" ].
amichair
24
Бенгт: имеет смысл, когда вы думаете об этом как «код ошибки»: код ошибки 0 = все прошло нормально = 0 ошибок; код ошибки 1 = главное, что должен был сделать этот вызов, не удалось; еще: провал! посмотрите на странице руководства.
Летающая овца
7
Это имеет смысл, если учесть, что в программировании вещи обычно могут быть успешными только одним способом, но могут терпеть неудачу бесконечными способами. Ну, может быть, не бесконечно, но многое, шансы сложились против нас. Успех / Ошибка (и) не является логическим. Я думаю, что это «Используйте 0 для истины и 1 для ложных». следует читать «Используйте 0 для успеха и ненулевое значение для ошибки».
Давос,
6
Пожалуйста , не используйте 0 и 1. См stackoverflow.com/a/43840545/117471
Бруно Bronosky
167

Почему вы должны заботиться о том, что я говорю, несмотря на то, что есть ответ 250+ upvote

Дело не в этом 0 = trueа 1 = false. Это: ноль означает отсутствие отказа (успех) и ненулевое значение означает отказ (типа N) .

Хотя выбранный ответ является технически «верным», пожалуйста , не ставьте return 1** в своем коде для ложного . У него будет несколько неприятных побочных эффектов.

  1. Опытные разработчики заметят вас как любителя (по причине ниже).
  2. Опытные разработчики этого не делают (по всем причинам, указанным ниже).
  3. Это подвержено ошибкам.
    • Даже опытные разработчики могут ошибочно принять 0 и 1 как false и true соответственно (по причине выше).
  4. Это требует (или будет поощрять) посторонние и смешные комментарии.
  5. На самом деле это менее полезно, чем неявные статусы возврата.

Узнайте немного

Руководство Bash говорит (выделено мое)

возврат [n]

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

Поэтому нам не нужно НИКОГДА использовать 0 и 1 для обозначения True и False. Тот факт, что они делают это, является по существу тривиальным знанием, полезным только для отладки кода, вопросов об интервью и для того, чтобы поразить умы новичков.

Руководство Bash также говорит

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

Руководство Bash также говорит

( $? ) Расширяется до состояния выхода последнего выполненного переднего плана конвейера .

Ой, подожди. Трубопровод? Давайте вернемся к руководству по bash еще раз.

Конвейер - это последовательность из одной или нескольких команд, разделенных одним из управляющих операторов '|' или '| &'.

Да. Они сказали, что 1 команда - это конвейер. Поэтому все 3 из этих цитат говорят одно и то же.

  • $? рассказывает, что случилось в последний раз.
  • Это пузыри.

Мой ответ

Таким образом, хотя @Kambus продемонстрировал, что с такой простой функцией нет returnнеобходимости вообще. Я думаю, что это было нереально просто по сравнению с потребностями большинства людей, которые будут читать это.

Почему return?

Если функция собирается возвращать статус завершения своей последней команды, зачем returnвообще ее использовать ? Потому что это приводит к прекращению выполнения функции.

Остановить выполнение в нескольких условиях

01  function i_should(){
02      uname="$(uname -a)"
03
04      [[ "$uname" =~ Darwin ]] && return
05
06      if [[ "$uname" =~ Ubuntu ]]; then
07          release="$(lsb_release -a)"
08          [[ "$release" =~ LTS ]]
09          return
10      fi
11
12      false
13  }
14
15  function do_it(){
16      echo "Hello, old friend."
17  }
18
19  if i_should; then
20    do_it
21  fi

Что у нас здесь есть ...

Строка 04- это явное [-ish] возвращаемое значение true, потому что RHS &&выполняется только в том случае, если LHS был истинным

Строка 09возвращает true или false, соответствующие статусу строки08

Строка 13возвращает ложь из-за строки12

(Да, это может быть проигнорировано, но весь пример придуман).

Еще одна распространенная модель

# Instead of doing this...
some_command
if [[ $? -eq 1 ]]; then
    echo "some_command failed"
fi

# Do this...
some_command
status=$?
if ! $(exit $status); then
    echo "some_command failed"
fi

Обратите внимание, как установка statusпеременной демистифицирует значение $?. (Конечно, вы знаете, что это $?значит, но кто-то менее знающий, чем вы, когда-нибудь придет в Google. Если ваш код не занимается высокочастотной торговлей, проявите некоторую любовь , установите переменную.) Но реальная выгода заключается в том, что «если не существует статуса "или наоборот", если статус выхода "можно прочитать вслух и объяснить их значение. Однако последний может быть слишком амбициозным, потому что, увидев слово, exitвы можете подумать, что он выходит из сценария, а в действительности он выходит из $(...)подоболочки.


** Если вы абсолютно настаиваете на использовании return 1для ложных, я предлагаю вам хотя бы использовать return 255вместо этого. Это заставит вашего будущего себя или любого другого разработчика, который должен поддерживать ваш код, задать вопрос "почему это 255?" Тогда они, по крайней мере, будут обращать внимание и будут иметь больше шансов избежать ошибки.

Бруно Броноски
источник
1
@ZeroPhase 1 & 0 для false & true было бы смешно. Если у вас есть двоичный тип данных, для этого нет причин. В bash вы имеете дело с кодом состояния, который отражает успехи (единственное число) и неудачи (множественное число). Это « ifуспех, сделай это, elseсделай это». Успех в чем? Может быть проверка на истину / ложь, может быть проверка на строку, целое число, файл, каталог, права на запись, glob, regex, grep или любую другую команду, которая подвержена сбоям .
Бруно Броноски
3
Избегать стандартного использования 0/1 в качестве возвращаемого значения только потому, что оно тупое и склонно к путанице, глупо. Весь язык оболочки тупой и подвержен путанице. Сам Bash использует 0/1 = истина / ложь конвенции в своих собственных trueи falseкоманд. Таким образом, ключевое слово trueбуквально оценивается как код состояния 0. Кроме того, операторы if-then по своей природе оперируют логическими значениями, а не кодами успеха. Если во время логической операции возникает ошибка, она не должна возвращать true или false, а просто прерывать выполнение. В противном случае вы получите ложные срабатывания (каламбур).
Beejor
1
«if! $ (exit $ status); затем» - это заслуживает лучшего объяснения. Это не интуитивно понятно. Я должен подумать о том, выйдет ли программа перед печатью сообщения, или нет. Остальная часть вашего ответа была хорошей, но это все испортило.
Крейг Хикс,
1
@BrunoBronosky Я прочитал ваш ответ и мне кажется, что я понимаю различие между конвейером контента (stdin-> stdout) и кодами ошибок в возвращаемых значениях. Тем не менее, я не понимаю, почему return 1следует избегать использования. Предполагая, что у меня есть validateфункция, я считаю разумным, return 1если проверка не пройдена. В конце концов, это является причиной, чтобы остановить выполнение скрипта, если он не обрабатывается должным образом (например, при использовании set -e).
JepZ
1
@Джепз, это отличный вопрос. Вы правы, return 1это действительно. Меня беспокоят все комментарии, в которых говорится: «0 = истина 1 = ложь - это [вставить отрицательное слово]». Эти люди, вероятно, когда-нибудь прочитают ваш код. Таким образом, для них видение return 255(или 42 или даже 2) должно помочь им больше думать об этом и не принимать это за «истину». set -eвсе равно поймаю это.
Бруно Броноски
31
myfun(){
    [ -d "$1" ]
}
if myfun "path"; then
    echo yes
fi
# or
myfun "path" && echo yes
Kambus
источник
1
Как насчет отрицания?
einpoklum
Как насчет этого, @einpoklum?
Марк Рид
@MarkReed: я имел в виду, добавить к вашему примеру случай «еще».
einpoklum
myfun "путь" || эхо нет
Хробки
13

Будьте внимательны при проверке каталога только с опцией -d!
если переменная $ 1 пуста, проверка все равно будет успешной. Чтобы быть уверенным, проверьте также, что переменная не пуста.

#! /bin/bash

is_directory(){

    if [[ -d $1 ]] && [[ -n $1 ]] ; then
        return 0
    else
        return 1
    fi

}


#Test
if is_directory $1 ; then
    echo "Directory exist"
else
    echo "Directory does not exist!" 
fi
RenRen
источник
1
Я не уверен относительно того, как это отвечает на заданный вопрос. Хотя приятно знать, что пустой $ 1 может возвращать true, когда он пуст, он не дает никакого представления о том, как вернуть true или false из функции bash. Я бы предложил создать новый вопрос "Что происходит, когда вы делаете тест для пустой переменной оболочки?" А потом опубликовать это как ответ.
DRaehal
3
Обратите внимание: если вы добавите правильное цитирование в $1( "$1"), вам не нужно проверять пустую переменную. [[ -d "$1" ]]Потерпит неудачу , потому что это ""не является каталогом.
Моргенты
3

Я столкнулся с моментом (еще явно не упомянутым?), На который я наткнулся. То есть не как вернуть логическое значение, а как правильно его оценить!

Я пытался сказать if [ myfunc ]; then ..., но это просто неправильно. Вы не должны использовать скобки! if myfunc; then ...это способ сделать это.

Как и у @Bruno, и другие повторяются, trueи falseэто команды , а не значения! Это очень важно для понимания логических значений в сценариях оболочки.

В этом посте я объяснил и продемонстрировал с помощью логических переменных : https://stackoverflow.com/a/55174008/3220983 . Я настоятельно рекомендую проверить это, потому что это так тесно связано.

Здесь я приведу несколько примеров возврата и оценки логических значений из функций:

Это:

test(){ false; }                                               
if test; then echo "it is"; fi                                 

Не выдает эхосигнала. (т.е. false возвращает ложь)

test(){ true; }                                                
if test; then echo "it is"; fi                                 

Производит:

it is                                                        

(т.е. true возвращает истину)

И

test(){ x=1; }                                                
if test; then echo "it is"; fi                                 

Производит:

it is                                                                           

Потому что 0 (то есть истина) был возвращен неявно .

Теперь это то, что напортачило мне ...

test(){ true; }                                                
if [ test ]; then echo "it is"; fi                             

Производит:

it is                                                                           

И

test(){ false; }                                                
if [ test ]; then echo "it is"; fi                             

ТАКЖЕ производит:

it is                                                                           

Использование скобок здесь дает ложный положительный результат ! (Я предполагаю, что «внешний» результат команды равен 0.)

Основной вывод из моего поста заключается в следующем: не используйте скобки для оценки логической функции (или переменной), как для обычной проверки на равенство, например if [ x -eq 1 ]; then...!

BuvinJ
источник
2

Используйте команды trueили falseнепосредственно перед вашим return, затем returnбез параметров. returnБудет автоматически использовать значение последней команды.

Предоставление аргументов для returnявляется непоследовательным, специфичным для типа и подверженным ошибкам, если вы не используете 1 или 0. И, как уже говорилось в предыдущих комментариях, использование 1 или 0 здесь не является правильным способом приблизиться к этой функции.

#!/bin/bash

function test_for_cat {
    if [ $1 = "cat" ];
    then
        true
        return
    else
        false
        return
    fi
}

for i in cat hat;
do
    echo "${i}:"
    if test_for_cat "${i}";
    then
        echo "- True"
    else
        echo "- False"
    fi
done

Вывод:

$ bash bash_return.sh

cat:
- True
hat:
- False
mrteatime
источник
1

Это может сработать, если переписать это function myfun(){ ... return 0; else return 1; fi;}так function myfun(){ ... return; else false; fi;}. То есть, если falseэто последняя инструкция в функции, вы получаете ложный результат для всей функции, но в returnлюбом случае прерываете функцию с истинным результатом. Я верю, что это правда, по крайней мере, для моего переводчика bash.

Племянник
источник
1

Я нашел самую короткую форму для проверки выхода функции просто

do_something() {
    [[ -e $1 ]] # e.g. test file exists
}

do_something "myfile.txt" || { echo "File doesn't exist!"; exit 1; }
Рой Шилкрот
источник
1

По причинам читабельности кода, я считаю, что возвращение true / false должно:

  • быть на одной линии
  • быть одной командой
  • легко запомнить
  • упомяните ключевое слово, returnза которым следует другое ключевое слово ( trueили false)

Мое решение return $(true)или return $(false)как показано:

is_directory()
{
    if [ -d "${1}" ]; then
        return $(true)
    else
        return $(false)
    fi
}
Чарли
источник
0

Следуя @Bruno Bronosky и @mrteatime, я предлагаю вам просто написать булево возвращение «назад». Это то, что я имею в виду:

foo()
{
    if [ "$1" == "bar" ]; then
        true; return
    else
        false; return
    fi;
}

Это устраняет уродливое требование в две строки для каждого оператора return.

BuvinJ
источник