Zwracanie wartości logicznej z funkcji Bash

211

Chcę napisać funkcję bash, która sprawdza, czy plik ma określone właściwości i zwraca wartość true lub false. Następnie mogę użyć go w moich skryptach w „if”. Ale co mam zwrócić?

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

następnie używam tego w ten sposób:

if myfun filename.txt; then ...

oczywiście to nie działa. Jak można tego dokonać?

luca
źródło
3
upuść functionsłowo kluczowe, myfun() {...}wystarczy
glenn jackman
2
Liczy się ifstatus zerowego wyjścia myfun: jeśli myfunwychodzi z 0, then ...jest wykonywany; jeśli to cokolwiek innego else ... jest wykonywane.
Eelvex
7
@nhed: functionsłowo kluczowe to bashism i spowoduje błędy składniowe w niektórych innych powłokach. Zasadniczo jest to zbędne lub zabronione, więc po co z niego korzystać? Nie jest nawet przydatny jako cel grep, ponieważ może go nie być ( ()zamiast tego grep ).
Gordon Davisson
3
@GordonDavisson: co? są inne muszle? ;-)
nhed
Nie używaj 0 i 1. Zobacz stackoverflow.com/a/43840545/117471
Bruno Bronosky

Odpowiedzi:

333

Użyj 0 dla true i 1 dla false.

Próba:

#!/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

Edytować

Z komentarza @ amichair są one również możliwe

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


isdirectory() {
  [ -d "$1" ]
}
Erik
źródło
4
Nie, nie musisz tego robić - zobacz próbkę.
Erik
46
Aby uzyskać lepszą czytelność, możesz użyć polecenia „prawda” (która nic nie robi i kończy się powodzeniem, tj. Zwraca 0) oraz polecenia „fałsz” (które nie robi niczego i kończy się niepowodzeniem, tj. Zwraca wartość niezerową). Ponadto funkcja, która kończy się bez wyraźnej instrukcji return, zwraca kod wyjścia ostatnio wykonanej komendy, więc w powyższym przykładzie treść funkcji można zredukować do tylko [ -d "$1" ].
amichair
24
Bengt: ma sens, gdy myślisz o tym jako o „kodzie błędu”: kod błędu 0 = wszystko poszło dobrze = 0 błędów; kod błędu 1 = najważniejsze, że to wywołanie miało się nie powieść; inaczej: nie! sprawdź to na stronie podręcznika.
latające owce
7
Ma to sens, gdy weźmie się pod uwagę, że w programowaniu rzeczy mogą zwykle odnieść sukces tylko w jeden sposób, ale mogą zawieść w nieskończony sposób. Cóż, może nie jest nieskończony, ale losy są duże. Sukces / błędy nie są logiczne. Myślę, że „Użyj 0 dla prawdy i 1 dla fałszu”. powinno brzmieć „Użyj 0 dla sukcesu i niezerowe dla niepowodzenia”.
Davos,
6
Nie używaj 0 i 1. Zobacz stackoverflow.com/a/43840545/117471
Bruno Bronosky
167

Dlaczego powinieneś przejmować się tym, co mówię, pomimo ponad 250 pozytywnych odpowiedzi

To nie tak 0 = truei 1 = false. Jest to: zero oznacza brak awarii (sukces), a niezerowe oznacza uszkodzenie (typu N) .

Podczas gdy wybrana odpowiedź jest technicznie „true” proszę nie umieszczać return 1** w kodzie na false . Będzie miał kilka niefortunnych skutków ubocznych.

  1. Doświadczeni programiści zauważą Cię jako amatora (z poniższego powodu).
  2. Doświadczeni programiści tego nie robią (z wszystkich powodów poniżej).
  3. Jest podatny na błędy.
    • Nawet doświadczeni programiści mogą pomylić 0 i 1 odpowiednio jako fałszywe i prawdziwe (z powyższego powodu).
  4. Wymaga (lub będzie zachęcać) obcych i śmiesznych komentarzy.
  5. Jest to w rzeczywistości mniej pomocne niż niejawne statusy zwrotu.

Naucz się bashu

Podręcznik bash mówi (moje podkreślenie)

powrót [n]

Powoduje, że funkcja powłoki przestaje działać i zwraca wartość n swojemu wywołującemu. Jeśli nie podano n , zwracana wartość to status wyjścia ostatniego polecenia wykonanego w funkcji.

Dlatego nie musimy NIGDY używać 0 i 1, aby wskazać Prawda i Fałsz. Fakt, że robią to, jest w zasadzie trywialną wiedzą przydatną tylko do debugowania kodu, pytań do wywiadu i zaskakiwania umysłów początkujących.

Podręcznik bash również mówi

w przeciwnym razie stanem zwrotnym funkcji jest status wyjścia ostatniego wykonanego polecenia

Podręcznik bash również mówi

( $? ) Rozwija się do statusu wyjścia ostatnio wykonywanego potoku pierwszego planu .

Zaraz, poczekaj. Rurociąg? Jeszcze raz przejdźmy do instrukcji bash .

Potok jest sekwencją jednego lub więcej poleceń oddzielonych jednym z operatorów sterujących „|” lub „| &”.

Tak. Powiedzieli, że 1 polecenie to potok. Dlatego wszystkie 3 cytaty mówią to samo.

  • $? mówi ci, co się stało ostatnio.
  • Pęka bąbelki.

Moja odpowiedź

Tak więc, chociaż @Kambus wykazał, że przy tak prostej funkcji nie returnjest wcale potrzebny. Myślę, że było nierealistycznie proste w porównaniu z potrzebami większości ludzi, którzy to przeczytają.

Dlaczego return?

Jeśli funkcja zwraca status wyjścia ostatniego polecenia, po returnco w ogóle używać ? Ponieważ powoduje to, że funkcja przestaje działać.

Zatrzymaj wykonywanie w wielu warunkach

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

Mamy tutaj ...

Wiersz 04jest jawnym [-ish] zwróconym prawdą, ponieważ RHS &&jest wykonywany tylko wtedy, gdy LHS był prawdziwy

Linia 09zwraca wartość true lub false pasującą do stanu linii08

Linia 13zwraca false z powodu linii12

(Tak, można to zagrać w golfa, ale cały przykład jest wymyślony).

Kolejny wspólny wzór

# 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

Zwróć uwagę, jak ustawienie statuszmiennej demistyfikuje znaczenie $?. (Oczywiście, że wiesz, co to $?znaczy, ale ktoś mniej znający się na tym od Ciebie będzie musiał go kiedyś Google. O ile twój kod nie zajmuje się handlem wysokimi częstotliwościami, okaż trochę miłości , ustaw zmienną.) Ale prawdziwą rzeczą na wynos jest „jeśli status „nie istnieje” lub odwrotnie „jeśli status wyjścia” można odczytać na głos i wyjaśnić ich znaczenie. Jednak ten ostatni może być nieco zbyt ambitny, ponieważ zobaczenie tego słowa exitmoże sprawić, że pomyślisz, że wychodzi on ze skryptu, podczas gdy w rzeczywistości wychodzi z $(...)podpowłoki.


** Jeśli absolutnie nalegasz na użycie return 1fałszywego, sugeruję przynajmniej użyć return 255zamiast tego. Spowoduje to, że twoje przyszłe ja lub inny programista, który będzie musiał utrzymywać Twój kod, będzie pytał „dlaczego to jest 255?” Wtedy będą przynajmniej uważać i będą mieli większą szansę na uniknięcie błędu.

Bruno Bronosky
źródło
1
@ZeroPhase 1 i 0 dla false i true byłoby śmieszne. Jeśli masz binarny typ danych, nie ma tego powodu. W bash masz do czynienia z kodem statusu, który odzwierciedla sukces (liczba pojedyncza) i niepowodzenia (liczba mnoga). To „ ifsukces zrób to, elsezrób to”. Sukces w czym? Może sprawdzać wartość true / false, może sprawdzać ciąg, liczbę całkowitą, plik, katalog, uprawnienia do zapisu, glob, regex, grep lub dowolne inne polecenie podlegające awariom .
Bruno Bronosky,
3
Unikanie standardowego użycia 0/1 jako wartości zwracanej tylko dlatego, że jest tępy i podatny na zamieszanie, jest głupie. Cały język powłoki jest tępy i podatny na zamieszanie. Sam Bash używa własnej konwencji 0/1 = prawda / fałsz truei falsepoleceń. Oznacza to, że słowo kluczowe truedosłownie zwraca kod stanu 0. Ponadto instrukcje typu „jeśli-to” z natury działają na boolach, a nie na kodach sukcesu. Jeśli błąd wystąpi podczas operacji logicznej, nie powinien zwracać wartości true lub false, ale po prostu przerywać wykonywanie. W przeciwnym razie otrzymujesz fałszywe alarmy (pun).
Beejor,
1
„if! $ (exit $ status); then” - To zasługuje na lepsze wyjaśnienie. To nie jest intuicyjne. Muszę pomyśleć o tym, czy program zakończy działanie przed wydrukowaniem wiadomości, czy nie. Reszta twojej odpowiedzi była dobra, ale to ją zepsuło.
Craig Hicks
1
@BrunoBronosky Przeczytałem twoją odpowiedź i myślę, że rozumiem różnicę między potokiem treści (stdin-> stdout) a kodami błędów w zwracanych wartościach. Nie rozumiem jednak, dlaczego return 1należy unikać używania . Zakładając, że mam validatefunkcję, uważam za uzasadnione, return 1jeśli sprawdzanie poprawności się nie powiedzie. W końcu jest to powód, aby zatrzymać wykonywanie skryptu, jeśli nie jest on odpowiednio obsługiwany (np. Podczas używania set -e).
JepZ
1
@Jepz to świetne pytanie. Masz rację, która return 1jest ważna. Moje obawy dotyczą wszystkich komentarzy tutaj: „0 = prawda 1 = fałsz to [wstaw słowo wykluczające]”. Ci ludzie prawdopodobnie kiedyś przeczytają Twój kod. Dlatego dla nich widzenie return 255(lub 42, a nawet 2) powinno pomóc im w większym zastanowieniu się i nie pomylić tego z „prawdą”. set -enadal go złapię.
Bruno Bronosky,
31
myfun(){
    [ -d "$1" ]
}
if myfun "path"; then
    echo yes
fi
# or
myfun "path" && echo yes
Kambus
źródło
1
Co z negacją?
einpoklum
A co z tym, @einpoklum?
Mark Reed
@ MarkReed: Chciałem dodać przypadek „else” do swojego przykładu.
einpoklum
myfun „ścieżka” || echo nie
Hrobky,
13

Zachowaj ostrożność, sprawdzając katalog tylko z opcją -d!
jeśli zmienna $ 1 jest pusta, sprawdzanie nadal się powiedzie. Dla pewności sprawdź także, czy zmienna nie jest pusta.

#! /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
źródło
1
Nie jestem pewien, jak to odpowiada na zadane pytanie. Chociaż miło jest wiedzieć, że pusty 1 $ może zwrócić wartość true, gdy jest pusty, nie zapewnia żadnego wglądu w to, jak zwrócić true lub false z funkcji bash. Sugerowałbym utworzenie nowego pytania „Co się stanie, gdy wykonasz test na pustej zmiennej powłoki?” A potem publikując to jako odpowiedź.
DRaehal
3
Zauważ, że jeśli dodasz prawidłowe cytowanie do $1( "$1"), nie musisz sprawdzać pustej zmiennej. Nie [[ -d "$1" ]]powiedzie się, ponieważ ""nie jest to katalog.
morgents
3

Natknąłem się na jakiś punkt (jeszcze nie wymieniony?), Nad którym się potknąłem. Oznacza to, że nie jak zwrócić wartość logiczną, ale jak poprawnie ją ocenić!

Próbowałem powiedzieć if [ myfunc ]; then ..., ale to po prostu źle. Nie wolno używać nawiasów! if myfunc; then ...jest na to sposób.

Podobnie jak w @Bruno i innych powtórzonych, truei falsesą to polecenia , a nie wartości! To bardzo ważne, aby zrozumieć booleany w skryptach powłoki.

W tym poście wyjaśniłem i pokazałem przy użyciu zmiennych boolowskich : https://stackoverflow.com/a/55174008/3220983 . Zdecydowanie sugeruję sprawdzenie tego, ponieważ jest tak ściśle powiązane.

Tutaj podam kilka przykładów zwracania i oceny wartości logicznych z funkcji:

To:

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

Nie generuje echa. (tzn. false zwraca false)

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

Produkuje:

it is                                                        

(tzn. true zwraca true)

I

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

Produkuje:

it is                                                                           

Ponieważ 0 (tj. Prawda) zostało zwrócone niejawnie .

Teraz to mnie spieprzyło ...

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

Produkuje:

it is                                                                           

I

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

TAKŻE produkuje:

it is                                                                           

Użycie nawiasów tutaj wytworzyło fałszywy wynik pozytywny ! (Wnioskuję, że wynikiem polecenia „zewnętrznego” jest 0.)

Najważniejsze odejście od mojego postu: nie używaj nawiasów, aby ocenić funkcję boolowską (lub zmienną), tak jak w przypadku typowej kontroli równości, np. if [ x -eq 1 ]; then...!

BuvinJ
źródło
2

Użyj poleceń truelub falsebezpośrednio przed swoim return, a następnie returnbez parametrów. returnAutomatycznie użyje wartości ostatniego polecenia.

Podanie argumentów returnjest niespójne, specyficzne dla typu i podatne na błędy, jeśli nie używasz 1 lub 0. I jak stwierdzono w poprzednich komentarzach, użycie 1 lub 0 tutaj nie jest właściwym sposobem podejścia do tej funkcji.

#!/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

Wynik:

$ bash bash_return.sh

cat:
- True
hat:
- False
mrteatime
źródło
1

Może to działać, jeśli przepiszesz to function myfun(){ ... return 0; else return 1; fi;}w ten sposób function myfun(){ ... return; else false; fi;}. To znaczy, jeśli falsejest ostatnią instrukcją w funkcji, otrzymujesz fałszywy wynik dla całej funkcji, ale i tak returnprzerywa działanie z prawdziwym wynikiem. Uważam, że tak jest przynajmniej w przypadku mojego tłumacza bash.

Bratanek
źródło
1

Znalazłem najkrótszą formę, aby przetestować wynik funkcji jest po prostu

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

do_something "myfile.txt" || { echo "File doesn't exist!"; exit 1; }
Roy Shilkrot
źródło
1

Uważam, że ze względu na czytelność zwracanie wartości true / false powinno:

  • być w jednej linii
  • być jednym poleceniem
  • być łatwym do zapamiętania
  • wspomnieć słowo kluczowe, returna następnie inne słowo kluczowe ( truelub false)

Moje rozwiązanie jest return $(true)lub return $(false)jak pokazano na rysunku:

is_directory()
{
    if [ -d "${1}" ]; then
        return $(true)
    else
        return $(false)
    fi
}
Charlie
źródło
0

Kontynuując @Bruno Bronosky i @mrteatime, proponuję, abyś po prostu napisał zwrot boolowski „wstecz”. To jest to co mam na mysli:

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

Eliminuje to brzydkie wymagania dwóch wierszy dla każdej instrukcji return.

BuvinJ
źródło