Stellen Sie fest, ob in bash eine Funktion vorhanden ist

187

Derzeit mache ich einige Unit-Tests, die von Bash ausgeführt werden. Unit-Tests werden in einem Bash-Skript initialisiert, ausgeführt und bereinigt. Dieses Skript enthält normalerweise die Funktionen init (), execute () und cleanup (). Sie sind jedoch nicht obligatorisch. Ich möchte testen, ob sie definiert sind oder nicht.

Ich habe dies zuvor getan, indem ich die Quelle ergriffen und ausgesät habe, aber es schien falsch zu sein. Gibt es eine elegantere Möglichkeit, dies zu tun?

Bearbeiten: Das folgende Sniplet wirkt wie ein Zauber:

fn_exists()
{
    LC_ALL=C type $1 | grep -q 'shell function'
}
Terminus
quelle
Vielen Dank. Ich habe dies verwendet, um beim Laden einer Shell-Bibliothek gestoppelte Versionen von Funktionen bedingt zu definieren. fn_exists foo || foo() { :; }
Harvey
2
Sie können das grep mit type -tund speichern ==.
Roland Weber
Funktioniert nicht, wenn das Gebietsschema nicht Englisch ist. type test_functionsagt test_function on funktio.bei Verwendung des finnischen Gebietsschemas und ist eine Funktionbei Verwendung des deutschen.
Kimmo Lehto
3
Für nicht-englische Locales LC_ALL=Czum Resque
gaRex

Antworten:

191

Ich denke, Sie suchen nach dem Befehl 'type'. Hier erfahren Sie, ob etwas eine Funktion, eine integrierte Funktion, ein externer Befehl ist oder nicht. Beispiel:

$ 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
quelle
119
type -t $functionist die Essenskarte.
Allan Wind
4
Warum hast du es nicht als Antwort gepostet? :-)
Endstation
Weil ich meine Antwort mit deklarieren zuerst gepostet hatte :-)
Allan Wind
5
type [-t]Es ist schön, Ihnen zu sagen , was eine Sache ist, aber wenn Sie testen, ob etwas eine Funktion ist, ist es langsam, da Sie Pipe zu Grep oder Backticks verwenden müssen, die beide einen Unterprozess erzeugen.
Lloeki
1
Sofern ich nicht falsch verstanden habe, muss die Verwendung von type einen zugegebenermaßen minimalen Zugriff ausführen, um zu überprüfen, ob eine übereinstimmende Datei vorhanden ist. @Lloeki, Sie sind ganz richtig, aber es ist die Option, die eine minimale Ausgabe erzeugt, und Sie können immer noch die Fehlerstufe verwenden. Sie könnten das Ergebnis ohne einen Unterprozess erhalten, z. B. type -t realpath > /dev/shm/tmpfile ; read < /dev/shm/tmpfile(schlechtes Beispiel). Allerdings declare ist die beste Antwort , da 0 Disk io ist hat.
Orwellophile
79
$ g() { return; }
$ declare -f g > /dev/null; echo $?
0
$ declare -f j > /dev/null; echo $?
1
Allan Wind
quelle
1
hat super für mich funktioniert. Zumal meine Shell nicht das Flag -t für Typ hat (ich hatte große Probleme mit Typ "$ command")
Dennis
2
In der Tat funktioniert es auch in zsh (nützlich für RC-Skripte) und erfordert nicht, nach dem Typ zu suchen.
Lloeki
2
@DennisHodapp keine Notwendigkeit type -t, Sie können sich stattdessen auf den Exit-Status verlassen. Ich habe lange nachgesehen type program_name > /dev/null 2>&1 && program_name arguments || echo "error", ob ich etwas anrufen kann. Offensichtlich type -terlaubt die und die obige Methode auch, den Typ zu erkennen, nicht nur, ob er "aufrufbar" ist.
0xC0000022L
@ 0xC0000022L Was ist, wenn Programmname keine Funktion ist?
David Winiecki
2
@ 0xC0000022L Ich habe nicht darüber nachgedacht, wie die Verwendung des Exit-Status Sie nicht wissen lässt, ob Programmname eine Funktion ist, aber jetzt haben Sie das angesprochen, als Sie sagten: "Offensichtlich erlaubt der Typ -t und die obige Methode auch, den Typ zu erkennen." , nicht nur, ob es "aufrufbar" ist. " Es tut uns leid.
David Winiecki
40

Wenn deklarieren 10x schneller als Test ist, scheint dies die offensichtliche Antwort zu sein.

Bearbeiten: Unten ist die -fOption mit BASH überflüssig. Sie können sie auch weglassen. Persönlich fällt es mir schwer, mich daran zu erinnern, welche Option welche macht, also benutze ich einfach beide. -f zeigt Funktionen und -F zeigt Funktionsnamen.

#!/bin/sh

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

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

Die zu deklarierende Option "-F" gibt nur den Namen der gefundenen Funktion und nicht den gesamten Inhalt zurück.

Es sollte keine messbare Leistungseinbuße für die Verwendung von / dev / null geben, und wenn es Sie so sehr beunruhigt:

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

Oder kombinieren Sie beides für Ihren eigenen sinnlosen Genuss. Sie arbeiten beide.

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
Orwellophil
quelle
2
Die Option '-f' ist redundant.
Rajish
3
Die -FOption des existiert nicht in zsh (nützlich für die Portabilität)
Lloeki
-Fauch ist eigentlich nicht nötig: es scheint nur die funktionsdefinition / body zu unterdrücken.
Blueyed
1
@blueyed Es ist möglicherweise nicht notwendig, aber es ist sehr wünschenswert, wir versuchen zu bestätigen, dass eine Funktion vorhanden ist, und nicht den gesamten Inhalt aufzulisten (was etwas ineffizient ist). Würden Sie mit überprüfen, ob eine Datei vorhanden ist cat "$fn" | wc -c? Was zsh betrifft, wenn das Bash- Tag Sie nicht darauf hingewiesen hat, sollte es vielleicht die Frage selbst geben. "Bestimmen Sie, ob eine Funktion in bash vorhanden ist". Ich möchte weiter darauf hinweisen, dass die -FOption zwar nicht in zsh vorhanden ist, aber auch keinen Fehler verursacht. Daher ermöglicht die Verwendung von -f und -F, dass die Prüfung sowohl in zsh als auch in bash erfolgreich ist, was sonst nicht der Fall wäre .
Orwellophile
@Orwellophile -Fwird in zsh für Gleitkommazahlen verwendet. Ich kann nicht verstehen, warum die Verwendung -Fin Bash besser ist?! Ich habe den Eindruck, dass es declare -fin Bash genauso funktioniert (in Bezug auf den Rückkehrcode).
Blueyed
18

In Anlehnung an andere Lösungen und Kommentare habe ich Folgendes gefunden:

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' ]
}

Benutzt als ...

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

Es prüft, ob das angegebene Argument eine Funktion ist, und vermeidet Umleitungen und andere Grepping.

Grégory Joseph
quelle
Schön, mein Favorit aus der Gruppe! Wollen Sie nicht auch doppelte Anführungszeichen um das Argument? Wie in[ $(type -t "$1")"" == 'function' ]
Quickshiftin
Danke @quickshiftin; Ich weiß nicht, ob ich diese doppelten Anführungszeichen möchte, aber Sie haben wahrscheinlich Recht, obwohl ... kann eine Funktion sogar mit einem Namen deklariert werden, der in Anführungszeichen gesetzt werden muss?
Grégory Joseph
4
Sie verwenden Bash, verwenden [[...]]statt [...]und zitieren den Zitat-Hack. Auch Backticks Gabel, die langsam ist. Verwenden Sie declare -f $1 > /dev/nullstattdessen.
Lloeki
3
Vermeiden Sie Fehler mit leeren Argumenten, reduzieren Sie Anführungszeichen und verwenden Sie die Posix-konforme Gleichheit '='. Sie kann sicher auf ::fn_exists() { [ x$(type -t $1) = xfunction ]; }
qneill
10

Einen alten Beitrag ausgraben ... aber ich habe ihn kürzlich genutzt und beide Alternativen getestet, die beschrieben wurden mit:

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

dies erzeugte:

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

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

deklarieren ist ein helluvalot schneller!

jonathanserafini
quelle
1
Es kann ohne grep gemacht werden: test_type_nogrep () { a () { echo 'a' ;}; local b=$(type a); c=${b//is a function/}; [ $? = 0 ] && return 1 || return 0; }
qneill
@qneill Ich machte etwas ausführlicher Test in meiner Antwort ,
jarno
PIPE ist das langsamste Element. Dieser Test vergleicht nicht typeund declare. Es ist vergleichbar type | grepmit declare. Das ist ein großer Unterschied.
Kyb
7

Es läuft darauf hinaus, 'declare' zu verwenden, um entweder den Ausgabe- oder den Exit-Code zu überprüfen.

Ausgabestil:

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

Verwendung:

isFunction some_name && echo yes || echo no

Wenn jedoch Speicher bereitgestellt wird, ist die Umleitung auf Null schneller als die Ersetzung der Ausgabe (wenn man davon spricht, sollte die schreckliche und veraltete "cmd" -Methode verbannt und stattdessen $ (cmd) verwendet werden.) Und da declare true / false zurückgibt, wenn gefunden / nicht gefunden, und Funktionen geben den Exit-Code des letzten Befehls in der Funktion zurück, sodass eine explizite Rückgabe normalerweise nicht erforderlich ist. Da das Überprüfen des Fehlercodes schneller ist als das Überprüfen eines Zeichenfolgenwerts (sogar einer Nullzeichenfolge):

Statusstatus beenden:

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

Das ist wahrscheinlich so prägnant und harmlos wie möglich.

Scott
quelle
3
Für maximale Prägnanz verwendenisFunction() { declare -F "$1"; } >&-
Neil
3
isFunction() { declare -F -- "$@" >/dev/null; }ist meine Empfehlung. Es funktioniert auch mit einer Liste von Namen (erfolgreich nur, wenn alle Funktionen sind), gibt keine Probleme mit Namen, die mit beginnen, -und schlägt an meiner Seite ( bash4.2.25) declareimmer fehl, wenn die Ausgabe mit geschlossen wird >&-, weil es den Namen nicht schreiben kann in diesem Fall zu stdout
Tino
Und bitte beachten Sie, dass echodies auf einigen Plattformen manchmal mit einem "unterbrochenen Systemaufruf" fehlschlagen kann. In diesem Fall kann "check && echo yes || echo no" weiterhin ausgegeben werden, nowenn dies der checkFall ist.
Tino
7

Testen verschiedener Lösungen:

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

Ausgänge zB:

test_declare (f ist Funktion)

real 0m0,055s Benutzer 0m0,041s sys 0m0,004s Exit-Code 0

test_declare2 (f ist Funktion)

real 0m0,042s Benutzer 0m0,022s sys 0m0,017s Exit-Code 0

test_type (f ist Funktion)

real 0m2,200s Benutzer 0m1,619s sys 0m1,008s Exit-Code 0

test_type2 (f ist Funktion)

real 0m0,746s Benutzer 0m0,534s sys 0m0,237s Exit-Code 0

test_declare (f nicht gesetzt)

real 0m0,040s Benutzer 0m0,029s sys 0m0,010s Exit-Code 1

test_declare2 (f nicht gesetzt)

real 0m0,038s Benutzer 0m0,038s sys 0m0,000s Exit-Code 1

test_type (f nicht gesetzt)

real 0m2,438s Benutzer 0m1,678s sys 0m1,045s Exit-Code 1

test_type2 (f nicht gesetzt)

real 0m0,805s Benutzer 0m0,541s sys 0m0,274s Exit-Code 1

test_declare (f ist string)

real 0m0,043s Benutzer 0m0,034s sys 0m0,007s Exit-Code 1

test_declare2 (f ist string)

real 0m0,039s Benutzer 0m0,035s sys 0m0,003s Exit-Code 1

test_type (f ist string)

real 0m2,394s user 0m1,679s sys 0m1,035s exit code 1

test_type2 (f ist string)

real 0m0,851s user 0m0,554s sys 0m0,294s exit code 1

Also declare -F fscheint die beste Lösung zu sein.

jarno
quelle
Achtung hier: declare -F fGibt keinen Wert ungleich Null zurück, wenn f auf zsh nicht existiert, aber bash yes. Sei vorsichtig damit. declare -f ffunktioniert andererseits wie erwartet, indem die Definition der Funktion an den Standard angehängt wird (was ärgerlich sein kann ...)
Manoel Vilela
1
Haben Sie versucht, test_type3 () { [[ $(type -t f) = function ]] ; }es gibt Grenzkosten für die Definition einer lokalen Var (obwohl <10%)
Oliver
4

Aus meinem Kommentar zu einer anderen Antwort (die ich immer wieder vermisse, wenn ich auf diese Seite zurückkomme)

$ 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
quelle
3
fn_exists()
{
   [[ $(type -t $1) == function ]] && return 0
}

aktualisieren

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

$ isFunc isFunc
$ echo $?
0
$ isFunc dfgjhgljhk
$ echo $?
1
$ isFunc psgrep && echo yay
yay
$
Jona
quelle
2

Ich würde es verbessern, um:

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

Und benutze es so:

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

quelle
2

Dies sagt Ihnen, ob es existiert, aber nicht, dass es eine Funktion ist

fn_exists()
{
  type $1 >/dev/null 2>&1;
}
Jason Plank
quelle
2

Besonders gut hat mir die Lösung von Grégory Joseph gefallen

Aber ich habe es ein wenig modifiziert, um den "hässlichen Trick mit doppelten Anführungszeichen" zu überwinden:

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

    if [ "$TYPE_RESULT" == 'function' ]; then
        return 0
    else
        return 1
    fi
}
b1r3k
quelle
0

Es ist möglich, 'type' ohne externe Befehle zu verwenden, aber Sie müssen es zweimal aufrufen, damit es immer noch etwa doppelt so langsam ist wie die 'declare'-Version:

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

Außerdem funktioniert dies in POSIX sh nicht, so dass es völlig wertlos ist, außer als Trivia!

Noah Spurrier
quelle
test_type_nogrep () {a () {echo 'a';}; lokal b = $ (Typ a); c = $ {b // ist eine Funktion /}; [$? = 0] && return 1 || return 0; } - qneill
Alexx Roche