Sprawdź, czy funkcja istnieje w bash

187

Obecnie przeprowadzam kilka testów jednostkowych wykonywanych z poziomu bash. Testy jednostkowe są inicjowane, wykonywane i czyszczone w skrypcie bash. Ten skrypt zwykle zawiera funkcje init (), execute () i cleanup (). Ale nie są obowiązkowe. Chciałbym sprawdzić, czy są, czy nie są zdefiniowane.

Zrobiłem to wcześniej, szukając źródła i szukając źródła, ale wydawało się to złe. Czy jest na to bardziej elegancki sposób?

Edycja: Poniższy fragment kodu działa jak urok:

fn_exists()
{
    LC_ALL=C type $1 | grep -q 'shell function'
}
stacja końcowa
źródło
Dzięki. Użyłem tego do warunkowego zdefiniowania zgaszonych wersji funkcji podczas ładowania biblioteki powłoki. fn_exists foo || foo() { :; }
Harvey
2
Możesz zapisać grep za pomocą type -ti ==.
Roland Weber,
Nie działa, gdy ustawienia regionalne są w języku innym niż angielski. type test_functionmówi test_function on funktio.podczas korzystania z fińskich ustawień regionalnych i ist eine Funktionpodczas używania języka niemieckiego.
Kimmo Lehto,
3
Dla nieanglojęzycznych lokalizacji LC_ALL=Cdo resque
gaRex

Odpowiedzi:

191

Myślę, że szukasz polecenia „typ”. Powie ci, czy coś jest funkcją, funkcją wbudowaną, poleceniem zewnętrznym, czy po prostu nie jest zdefiniowane. Przykład:

$ LC_ALL=C type foo
bash: type: foo: not found

$ LC_ALL=C type ls
ls is aliased to `ls --color=auto'

$ which type

$ LC_ALL=C type type
type is a shell builtin

$ LC_ALL=C type -t rvm
function

$ if [ -n "$(LC_ALL=C type -t rvm)" ] && [ "$(LC_ALL=C type -t rvm)" = function ]; then echo rvm is a function; else echo rvm is NOT a function; fi
rvm is a function
JBB
źródło
120
type -t $functionjest bilet na posiłek.
Allan Wind
4
Dlaczego nie opublikowałeś go jako odpowiedzi? :-)
zakończenie
Ponieważ zamieściłem swoją odpowiedź, używając najpierw deklaracji :-)
Allan Wind,
5
type [-t]miło jest powiedzieć ci, co to jest, ale podczas testowania, czy coś jest funkcją, jest powolne, ponieważ musisz potokować, aby grep lub użyć backicks, z których oba odradzają podproces.
Lloeki,
1
O ile źle się nie odczytam, użycie type będzie musiało wykonać dopuszczalny minimalny dostęp, aby sprawdzić, czy istnieje pasujący plik. @Lloeki, masz całkowitą rację, ale jest to opcja, która generuje minimalną moc wyjściową i nadal możesz używać poziomu błędu. Możesz uzyskać wynik bez podprocesu, np. type -t realpath > /dev/shm/tmpfile ; read < /dev/shm/tmpfile(Zły przykład). Jednak deklaracja jest najlepszą odpowiedzią, ponieważ ma 0 dysków io.
Orwellophile,
79
$ g() { return; }
$ declare -f g > /dev/null; echo $?
0
$ declare -f j > /dev/null; echo $?
1
Allan Wind
źródło
1
pracował dla mnie niesamowicie. Zwłaszcza, że ​​moja powłoka nie ma flagi -t dla typu (miałem dużo problemów z typem „$ command”)
Dennis
2
Rzeczywiście, działa również w zsh (przydatny w skryptach rc) i nie wymaga grep dla tego typu.
Lloeki,
2
@DennisHodapp nie jest potrzebny type -t, możesz zamiast tego polegać na statusie wyjścia. Dawno już type program_name > /dev/null 2>&1 && program_name arguments || echo "error"widziałem, czy będę w stanie zadzwonić. Oczywiście type -tpowyższa metoda pozwala również wykryć typ, nie tylko to, czy można go „wywołać”.
0xC0000022L
@ 0xC0000022L co jeśli nazwa_programu nie jest funkcją?
David Winiecki
2
@ 0xC0000022L Długo zastanawiałem się, w jaki sposób użycie statusu wyjścia nie informuje, czy nazwa_programu jest funkcją, ale teraz myślę, że to zrobiłeś, kiedy powiedziałeś „Oczywiście, że typ -t i powyższa metoda pozwala również wykryć typ , nie tylko to, czy można ją „wywołać”. Przepraszam.
David Winiecki
40

Jeśli deklaracja jest 10 razy szybsza niż test, wydaje się to oczywistą odpowiedzią.

Edycja: poniżej -fopcja jest zbędna w przypadku BASH, możesz ją pominąć. Osobiście mam problem z zapamiętaniem, która opcja to robi, więc używam obu. -f pokazuje funkcje, a -F pokazuje nazwy funkcji.

#!/bin/sh

function_exists() {
    declare -f -F $1 > /dev/null
    return $?
}

function_exists function_name && echo Exists || echo No such function

Opcja „-F” do zadeklarowania powoduje, że zwraca tylko nazwę znalezionej funkcji, a nie całą zawartość.

Nie powinno być żadnych wymiernych ograniczeń wydajności za używanie / dev / null, a jeśli tak bardzo cię to martwi:

fname=`declare -f -F $1`
[ -n "$fname" ]    && echo Declare -f says $fname exists || echo Declare -f says $1 does not exist

Lub połącz te dwa elementy, aby uzyskać własną bezcelową przyjemność. Oboje działają.

fname=`declare -f -F $1`
errorlevel=$?
(( ! errorlevel )) && echo Errorlevel says $1 exists     || echo Errorlevel says $1 does not exist
[ -n "$fname" ]    && echo Declare -f says $fname exists || echo Declare -f says $1 does not exist
Orwellophile
źródło
2
Opcja „-f” jest zbędna.
Rajish
3
-FOpcja des nie istnieje w zsh (przydatne do przenoszenia)
Lloeki
-Fteż nie jest tak naprawdę konieczne: wydaje się, że tłumi ono jedynie definicję / treść funkcji.
blueyed
1
@blueyed Może to nie być konieczne, ale jest wysoce pożądane, staramy się potwierdzić, że funkcja istnieje, a nie wyświetlać jej całej zawartości (co jest nieco nieefektywne). Czy sprawdziłbyś, czy plik jest obecny przy użyciu cat "$fn" | wc -c? Jeśli chodzi o zsh, jeśli tag bash cię nie podpowie, być może samo pytanie powinno mieć. „Sprawdź, czy funkcja istnieje w bash”. Chciałbym ponadto zauważyć, że chociaż -Fopcja nie istnieje w Zsh, nie powoduje również błędu, dlatego użycie zarówno -f, jak i -F pozwala na pomyślne sprawdzenie zarówno w Zsh, jak i bash, co inaczej nie byłoby .
Orwellophile,
@Orwellophile -Fjest używany w zsh dla liczb zmiennoprzecinkowych. Nie rozumiem, dlaczego używanie -Fpoprawia jakość bash ?! Mam wrażenie, że declare -fdziała tak samo w bash (w odniesieniu do kodu powrotu).
blueyed
18

Pożyczając od innych rozwiązań i komentarzy, wymyśliłem to:

fn_exists() {
  # appended double quote is an ugly trick to make sure we do get a string -- if $1 is not a known command, type does not output anything
  [ `type -t $1`"" == 'function' ]
}

Użyty jako ...

if ! fn_exists $FN; then
    echo "Hey, $FN does not exist ! Duh."
    exit 2
fi

Sprawdza, czy dany argument jest funkcją i unika przekierowań i innych greppingów.

Grégory Joseph
źródło
Fajnie, moja ulubiona z grupy! Nie chcesz też podwójnych cudzysłowów wokół argumentu? Jak w[ $(type -t "$1")"" == 'function' ]
szybka zmiana w
Dzięki @quickshiftin; Nie wiem, czy chcę tych podwójnych cudzysłowów, ale prawdopodobnie masz rację, chociaż ... czy funkcja może być nawet zadeklarowana z nazwą, która musiałaby być cytowana?
Grégory Joseph,
4
Używasz bash, użyj [[...]]zamiast [...]i pozbywaj się hacków z wyceną. Również widelec wsteczny, który jest wolny. Użyj declare -f $1 > /dev/nullzamiast tego.
Lloeki,
3
Unikając błędów z pustymi argumentami, zmniejszając cudzysłowy i stosując równość zgodną z fn_exists() { [ x$(type -t $1) = xfunction ]; }
posixem
10

Pogłębiam stary post ... ale ostatnio z niego skorzystałem i przetestowałem obie alternatywy opisane za pomocą:

test_declare () {
    a () { echo 'a' ;}

    declare -f a > /dev/null
}

test_type () {
    a () { echo 'a' ;}
    type a | grep -q 'is a function'
}

echo 'declare'
time for i in $(seq 1 1000); do test_declare; done
echo 'type'
time for i in $(seq 1 100); do test_type; done

to wygenerowało:

real    0m0.064s
user    0m0.040s
sys     0m0.020s
type

real    0m2.769s
user    0m1.620s
sys     0m1.130s

deklaruj, że jest helluvalot szybciej!

jonathanserafini
źródło
1
Można to zrobić bez grep: test_type_nogrep () { a () { echo 'a' ;}; local b=$(type a); c=${b//is a function/}; [ $? = 0 ] && return 1 || return 0; }
qneill,
@qneill zrobiłem nieco szersze badanie w mojej odpowiedzi ,
Jarno
RURA jest najwolniejszym elementem. Ten test nie porównuje typei declare. Porównuje się type | grepz declare. To duża różnica.
kyb
7

Sprowadza się do użycia polecenia „deklaruj” do sprawdzenia kodu wyjściowego lub kodu zakończenia.

Styl wyjściowy:

isFunction() { [[ "$(declare -Ff "$1")" ]]; }

Stosowanie:

isFunction some_name && echo yes || echo no

Jeśli jednak pamięć służy, przekierowanie na wartość NULL jest szybsze niż podstawianie danych wyjściowych (mówiąc o tym, że okropna i przestarzała metoda `cmd` powinna zostać usunięta, a zamiast niej należy użyć $ (cmd).) A ponieważ deklaracja zwraca true / false, jeśli znaleziono / nie znaleziono, a funkcje zwracają kod wyjścia ostatniego polecenia w funkcji, więc jawny zwrot zwykle nie jest konieczny, a ponieważ sprawdzenie kodu błędu jest szybsze niż sprawdzenie wartości ciągu (nawet ciągu zerowego):

Styl statusu wyjścia:

isFunction() { declare -Ff "$1" >/dev/null; }

To prawdopodobnie tak zwięzłe i łagodne, jak to tylko możliwe.

Scott
źródło
3
Aby zapewnić maksymalną zwięzłośćisFunction() { declare -F "$1"; } >&-
Neil
3
isFunction() { declare -F -- "$@" >/dev/null; }to moja rekomendacja. Działa również na liście nazw (udaje się tylko wtedy, gdy wszystkie są funkcjami), nie daje problemów z nazwami zaczynającymi się od, -a po mojej stronie ( bash4.2.25) declarezawsze kończy się niepowodzeniem, gdy wyjście jest zamknięte >&-, ponieważ nie może zapisać nazwy na standardowe wyjście w tej sprawie
Tino
I pamiętaj, że echoczasami może się nie powieść z „przerwanym wywołaniem systemowym” na niektórych platformach. W takim przypadku „check && echo yes || echo no” nadal może wyświetlać, nojeśli checkjest to prawda.
Tino
7

Testowanie różnych rozwiązań:

#!/bin/bash

test_declare () {
    declare -f f > /dev/null
}

test_declare2 () {
    declare -F f > /dev/null
}

test_type () {
    type -t f | grep -q 'function'
}

test_type2 () {
     [[ $(type -t f) = function ]]
}

funcs=(test_declare test_declare2 test_type test_type2)

test () {
    for i in $(seq 1 1000); do $1; done
}

f () {
echo 'This is a test function.'
echo 'This has more than one command.'
return 0
}
post='(f is function)'

for j in 1 2 3; do

    for func in ${funcs[@]}; do
        echo $func $post
        time test $func
        echo exit code $?; echo
    done

    case $j in
    1)  unset -f f
        post='(f unset)'
        ;;
    2)  f='string'
        post='(f is string)'
        ;;
    esac
done

dane wyjściowe np .:

test_declare (f jest funkcją)

rzeczywisty 0m0,055s użytkownik 0m0,041s sys 0m0,004s kod wyjścia 0

test_declare2 (f jest funkcją)

rzeczywisty 0m0,042s użytkownik 0m0,022s sys 0m0,017s kod wyjścia 0

test_type (f jest funkcją)

rzeczywisty 0m2200s użytkownik 0m1,619s sys 0m1,008s kod wyjścia 0

test_type2 (f jest funkcją)

rzeczywisty 0m0,746s użytkownik 0m0,534s sys 0m0,237s kod wyjścia 0

test_declare (f rozbrojony)

rzeczywisty 0m0,040s użytkownik 0m0,029s sys 0m0,010s kod wyjścia 1

test_declare2 (f rozbrojony)

rzeczywisty 0m0,038s użytkownik 0m0,038s sys 0m0,000s kod wyjścia 1

test_type (f rozbrojony)

rzeczywisty 0m2,438s użytkownik 0m1,678s sys 0m1,045s kod wyjścia 1

test_type2 (f nieustawione)

rzeczywisty 0m0,805s użytkownik 0m0,541s sys 0m0,274s kod wyjścia 1

test_declare (f to string)

rzeczywisty 0m0,043s użytkownik 0m0,034s sys 0m0,007s kod wyjścia 1

test_declare2 (f to string)

rzeczywisty 0m0,039s użytkownik 0m0,035s sys 0m0,003s kod wyjścia 1

test_type (f to string)

rzeczywisty 0m2 394s użytkownik 0m1,679s sys 0m1,035s kod wyjścia 1

test_type2 (f to string)

rzeczywisty 0m0,851s użytkownik 0m0,554s sys 0m0,294s kod wyjścia 1

Więc declare -F fwydaje się być najlepszym rozwiązaniem.

jarno
źródło
Uwaga tutaj: declare -F fnie zwraca niezerowej wartości, jeśli f nie istnieje na zsh, ale bash tak. Ostrożnie go używaj. declare -f f, z drugiej strony, działa zgodnie z oczekiwaniami, dołączając definicję funkcji na stdout (co może być denerwujące ...)
Manoel Vilela
1
Czy próbowałeś, test_type3 () { [[ $(type -t f) = function ]] ; }istnieje marginalny koszt zdefiniowania lokalnego var (chociaż <10%)
Oliver
4

Z mojego komentarza do innej odpowiedzi (której brakuje mi, gdy wracam na tę stronę)

$ fn_exists() { test x$(type -t $1) = xfunction; }
$ fn_exists func1 && echo yes || echo no
no
$ func1() { echo hi from func1; }
$ func1
hi from func1
$ fn_exists func1 && echo yes || echo no
yes
qneill
źródło
3
fn_exists()
{
   [[ $(type -t $1) == function ]] && return 0
}

aktualizacja

isFunc () 
{ 
    [[ $(type -t $1) == function ]]
}

$ isFunc isFunc
$ echo $?
0
$ isFunc dfgjhgljhk
$ echo $?
1
$ isFunc psgrep && echo yay
yay
$
Jonasz
źródło
2

Poprawiłbym to, aby:

fn_exists()
{
    type $1 2>/dev/null | grep -q 'is a function'
}

I użyj tego w ten sposób:

fn_exists test_function
if [ $? -eq 0 ]; then
    echo 'Function exists!'
else
    echo 'Function does not exist...'
fi

źródło
2

To mówi ci, jeśli istnieje, ale nie, że jest to funkcja

fn_exists()
{
  type $1 >/dev/null 2>&1;
}
Jason Plank
źródło
2

Szczególnie podobało mi się rozwiązanie Grégory Joseph

Ale zmodyfikowałem to trochę, aby pokonać „podwójny cytat brzydkiej sztuczki”:

function is_executable()
{
    typeset TYPE_RESULT="`type -t $1`"

    if [ "$TYPE_RESULT" == 'function' ]; then
        return 0
    else
        return 1
    fi
}
b1r3k
źródło
0

Możliwe jest użycie „type” bez żadnych zewnętrznych poleceń, ale musisz wywołać go dwa razy, więc nadal kończy się to około dwa razy wolniej niż wersja „deklaruj”:

test_function () {
        ! type -f $1 >/dev/null 2>&1 && type -t $1 >/dev/null 2>&1
}

Plus to nie działa w POSIX sh, więc jest całkowicie bezwartościowe, z wyjątkiem ciekawostek!

Noah Spurrier
źródło
test_type_nogrep () {a () {echo 'a';}; lokalny b = $ (wpisz a); c = $ {b // jest funkcją /}; [$? = 0] && return 1 || zwraca 0; } - qneill
Alexx Roche,