Déterminer si une fonction existe dans bash


187

Actuellement, je fais des tests unitaires qui sont exécutés à partir de bash. Les tests unitaires sont initialisés, exécutés et nettoyés dans un script bash. Ce script contient généralement des fonctions init (), execute () et cleanup (). Mais ils ne sont pas obligatoires. Je voudrais tester s'ils sont définis ou non.

Je l'ai fait précédemment en greffe et en semant la source, mais cela semblait faux. Y a-t-il une manière plus élégante de faire cela?

Edit: Le sniplet suivant fonctionne comme un charme:

fn_exists()
{
    LC_ALL=C type $1 | grep -q 'shell function'
}

Merci. Je l'ai utilisé pour définir de manière conditionnelle les versions stubbed des fonctions lors du chargement d'une bibliothèque shell. fn_exists foo || foo() { :; }
Harvey

2
Vous pouvez enregistrer le grep en utilisant type -tet ==.
Roland Weber

Ne fonctionne pas lorsque les paramètres régionaux ne sont pas anglais. type test_functiondit test_function on funktio.lors de l'utilisation de la langue finlandaise et ist eine Funktionlors de l'utilisation de l'allemand.
Kimmo Lehto

3
Pour les locales non anglaises LC_ALL=Cà la resque
gaRex

Réponses:


192

Je pense que vous recherchez la commande «type». Il vous dira si quelque chose est une fonction, une fonction intégrée, une commande externe ou tout simplement non définie. Exemple:

$ 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

120
type -t $functionest le ticket repas.
Allan Wind le

4
Pourquoi ne l'avez-vous pas posté comme réponse? :-)
terminus

Parce que j'avais posté ma réponse en utilisant declare first :-)
Allan Wind

5
type [-t]C'est bien de vous dire ce qu'est une chose, mais lorsque vous testez si quelque chose est une fonction, c'est lent car vous devez rediriger vers grep ou utiliser des backticks, qui génèrent tous deux un sous-processus.
Lloeki

1
Sauf erreur de lecture, l'utilisation de type devra effectuer un accès certes minimal, pour vérifier s'il existe un fichier correspondant. @Lloeki, vous avez tout à fait raison, mais c'est l'option qui produit une sortie minimale, et vous pouvez toujours utiliser le niveau d'erreur. Vous pourriez obtenir le résultat sans sous-processus, par exemple type -t realpath > /dev/shm/tmpfile ; read < /dev/shm/tmpfile(mauvais exemple). Cependant, déclarer est la meilleure réponse car il a 0 disque io.
Orwellophile

79
$ g() { return; }
$ declare -f g > /dev/null; echo $?
0
$ declare -f j > /dev/null; echo $?
1

1
a très bien fonctionné pour moi. D'autant que mon shell n'a pas le drapeau -t pour le type (j'avais beaucoup de problèmes avec le type "$ command")
Dennis

2
En effet, il fonctionne également en zsh (utile pour les scripts rc), et ne nécessite pas de grep pour le type.
Lloeki

2
@DennisHodapp n'est pas nécessaire type -t, vous pouvez vous fier à l'état de sortie à la place. J'ai depuis longtemps l'habitude type program_name > /dev/null 2>&1 && program_name arguments || echo "error"de voir si je pourrais appeler quelque chose. Évidemment type -t, la méthode et la méthode ci-dessus permettent également de détecter le type, pas seulement s'il est "appelable".
0xC0000022L

@ 0xC0000022L et si nom_programme n'est pas une fonction?
David Winiecki

2
@ 0xC0000022L J'étais pinaillé sur le fait que l'utilisation de l'état de sortie ne vous permet pas de savoir si nom_programme est une fonction, mais maintenant je pense que vous l'avez résolu lorsque vous avez dit: "De toute évidence, le type -t et la méthode ci-dessus permettent également de détecter le type , pas seulement si c'est "appelable". " Désolé.
David Winiecki

40

Si déclarer est 10 fois plus rapide que le test, cela semblerait la réponse évidente.

Edit: Ci-dessous, l' -foption est superflue avec BASH, n'hésitez pas à la laisser de côté. Personnellement, j'ai du mal à me souvenir quelle option fait quoi, alors j'utilise simplement les deux. -f montre les fonctions et -F montre les noms des fonctions.

#!/bin/sh

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

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

L'option "-F" à déclarer l'amène à ne renvoyer que le nom de la fonction trouvée, plutôt que le contenu entier.

Il ne devrait y avoir aucune pénalité mesurable en termes de performances pour l'utilisation de / dev / null, et si cela vous inquiète autant:

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

Ou combinez les deux, pour votre propre plaisir inutile. Ils fonctionnent tous les deux.

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

2
L'option '-f' est redondante.
Rajish

3
L' -Foption des n'existe pas dans zsh (utile pour la portabilité)
Lloeki

-Faussi n'est pas vraiment nécessaire: il semble supprimer uniquement la définition / le corps de la fonction.
blueyed le

1
@blueyed Ce n'est peut-être pas nécessaire, mais c'est hautement souhaitable, nous essayons de confirmer qu'une fonction existe, pas de lister son contenu entier (ce qui est quelque peu inefficace). Souhaitez-vous vérifier si un fichier est présent en utilisant cat "$fn" | wc -c? Quant à zsh, si la balise bash ne vous a pas indiqué, peut-être que la question elle-même devrait avoir. "Détermine si une fonction existe dans bash". Je voudrais en outre souligner que, bien que l' -Foption n'existe pas dans zsh, elle ne provoque pas non plus d'erreur.Par conséquent, l'utilisation de -f et -F permet à la vérification de réussir à la fois dans zsh et bash, ce qui sinon .
Orwellophile

@Orwellophile -Fest utilisé dans zsh pour les nombres à virgule flottante. Je ne vois pas pourquoi utiliser le -Frend meilleur dans bash?! J'ai eu l'impression que ça declare -fmarche de la même façon en bash (concernant le code retour).
blueyed le

18

En empruntant à d'autres solutions et commentaires, j'ai trouvé ceci:

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

Utilisé comme ...

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

Il vérifie si l'argument donné est une fonction et évite les redirections et autres grepping.


Bien, mon préféré du groupe! Vous ne voulez pas non plus de guillemets doubles autour de l'argument? As in[ $(type -t "$1")"" == 'function' ]
quickshift du

Merci @quickshiftin; Je ne sais pas si je veux ces guillemets, mais vous avez probablement raison, bien que .. une fonction peut-elle même être déclarée avec un nom qui aurait besoin d'être entre guillemets?
Grégory Joseph

4
Vous utilisez bash, utilisez à la [[...]]place [...]et débarrassez-vous du hack de devis. Également à la fourche, ce qui est lent. Utilisez declare -f $1 > /dev/nullplutôt.
Lloeki

3
En évitant les erreurs avec des arguments vides, en réduisant les guillemets et en utilisant l'égalité conforme posix '=', elle peut être réduite en toute sécurité à :: fn_exists() { [ x$(type -t $1) = xfunction ]; }
qneill

10

Récupérer un ancien article ... mais j'ai récemment eu recours à cela et j'ai testé les deux alternatives décrites avec:

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

cela a généré:

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

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

déclarer est un helluvalot plus rapide!


1
Cela peut être fait sans grep: test_type_nogrep () { a () { echo 'a' ;}; local b=$(type a); c=${b//is a function/}; [ $? = 0 ] && return 1 || return 0; }
qneill

@qneill j'ai fait un test un peu plus approfondi dans ma réponse ,
jarno

TUYAU est l'élément le plus lent. Ce test ne compare pas typeet declare. Il compare type | grepavec declare. C'est une grande différence.
kyb

7

Cela revient à utiliser 'declare' pour vérifier la sortie ou le code de sortie.

Style de sortie:

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

Usage:

isFunction some_name && echo yes || echo no

Cependant, si la mémoire est bonne, la redirection vers null est plus rapide que la substitution de sortie (en parlant de, la méthode horrible et obsolète `cmd` devrait être bannie et $ (cmd) utilisée à la place.) Et puisque declare retourne vrai / faux si trouvé / introuvable, et les fonctions renvoient le code de sortie de la dernière commande de la fonction, donc un retour explicite n'est généralement pas nécessaire, et comme la vérification du code d'erreur est plus rapide que la vérification d'une valeur de chaîne (même une chaîne nulle):

Style de statut de sortie:

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

C'est probablement aussi succinct et bénin que possible.


3
Pour un maximum de concisionisFunction() { declare -F "$1"; } >&-
Neil

3
isFunction() { declare -F -- "$@" >/dev/null; }est ma recommandation. Il fonctionne également sur une liste de noms (ne réussit que si tous sont des fonctions), ne pose aucun problème avec les noms commençant par -et, à mes côtés ( bash4.2.25), declareéchoue toujours lorsque la sortie est fermée avec >&-, car il ne peut pas écrire le nom à stdout dans ce cas
Tino

Et soyez conscient que cela echopeut parfois échouer avec un "appel système interrompu" sur certaines plates-formes. Dans ce cas, "check && echo yes || echo no" peut toujours afficher nosi checkest vrai.
Tino

7

Tester différentes solutions:

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

sorties par exemple:

test_declare (f est une fonction)

réel 0m0,055s utilisateur 0m0,041s sys 0m0,004s code de sortie 0

test_declare2 (f est une fonction)

réel 0m0,042s utilisateur 0m0,022s sys 0m0,017s code de sortie 0

test_type (f est une fonction)

réel 0m2,200s utilisateur 0m1,619s sys 0m1,008s code de sortie 0

test_type2 (f est une fonction)

réel 0m0,746s utilisateur 0m0,534s sys 0m0,237s code de sortie 0

test_declare (f non défini)

réel 0m0,040s utilisateur 0m0,029s sys 0m0,010s code de sortie 1

test_declare2 (f non défini)

réel 0m0,038s utilisateur 0m0,038s sys 0m0,000s code de sortie 1

test_type (f non défini)

réel 0m2,438s utilisateur 0m1,678s sys 0m1,045s code de sortie 1

test_type2 (f non défini)

réel 0m0,805s utilisateur 0m0,541s sys 0m0,274s code de sortie 1

test_declare (f est une chaîne)

réel 0m0,043s utilisateur 0m0,034s sys 0m0,007s code de sortie 1

test_declare2 (f est une chaîne)

réel 0m0,039s utilisateur 0m0,035s sys 0m0,003s code de sortie 1

test_type (f est une chaîne)

réel 0m2,394s utilisateur 0m1,679s sys 0m1,035s code de sortie 1

test_type2 (f est une chaîne)

réel 0m0,851s utilisateur 0m0,554s sys 0m0294s code de sortie 1

Cela declare -F fsemble donc être la meilleure solution.


Attention ici: declare -F fne renvoie pas de valeur non nulle si f n'existe pas sur zsh, mais bash oui. Soyez prudent en l'utilisant. declare -f f, d'autre part, fonctionne comme prévu en attachant la définition de la fonction sur le stdout (ce qui peut être ennuyeux ...)
Manoel Vilela

1
Avez-vous essayé test_type3 () { [[ $(type -t f) = function ]] ; }qu'il y ait un coût marginal de définition d'une var locale (bien que <10%)
Oliver

4

De mon commentaire sur une autre réponse (qui me manque toujours en revenant sur cette page)

$ 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

3
fn_exists()
{
   [[ $(type -t $1) == function ]] && return 0
}

mettre à jour

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

$ isFunc isFunc
$ echo $?
0
$ isFunc dfgjhgljhk
$ echo $?
1
$ isFunc psgrep && echo yay
yay
$

2

Je l'améliorerais pour:

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

Et utilisez-le comme ceci:

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

2

Cela vous indique s'il existe, mais pas que c'est une fonction

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

2

J'ai particulièrement aimé la solution de Grégory Joseph

Mais je l'ai un peu modifié pour surmonter le "truc laid des guillemets doubles":

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

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

0

Il est possible d'utiliser 'type' sans aucune commande externe, mais vous devez l'appeler deux fois, donc cela finit toujours par être environ deux fois plus lent que la version 'declare':

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

De plus, cela ne fonctionne pas dans POSIX sh, donc cela ne vaut absolument rien sauf pour des anecdotes!


test_type_nogrep () {a () {echo 'a';}; local b = $ (type a); c = $ {b // est une fonction /}; [$? = 0] && retourne 1 || return 0; } - qneill
Alexx Roche
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.