Determinar si existe una función en bash

187

Actualmente estoy haciendo algunas pruebas unitarias que se ejecutan desde bash. Las pruebas unitarias se inicializan, ejecutan y limpian en un script bash. Este script generalmente contiene funciones init (), execute () y cleanup (). Pero no son obligatorios. Me gustaría probar si están o no definidos.

Hice esto previamente codificando y enviando la fuente, pero parecía estar mal. ¿Hay alguna forma más elegante de hacer esto?

Editar: el siguiente fragmento funciona como un encanto:

fn_exists()
{
    LC_ALL=C type $1 | grep -q 'shell function'
}
término
fuente
Gracias. Utilicé esto para definir condicionalmente versiones apagadas de funciones al cargar una biblioteca de shell. fn_exists foo || foo() { :; }
Harvey
2
Puede guardar el grep usando type -ty ==.
Roland Weber
No funciona cuando la configuración regional no está en inglés. type test_functiondice test_function on funktio.cuando se usa la configuración regional finlandesa y ist eine Funktioncuando se usa el alemán.
Kimmo Lehto
3
Para locales no ingleses LC_ALL=Cen el resque
gaRex

Respuestas:

192

Creo que estás buscando el comando 'tipo'. Le dirá si algo es una función, una función incorporada, un comando externo o simplemente no está definido. Ejemplo:

$ 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
fuente
120
type -t $functionEs el boleto de comida.
Allan Wind
44
¿Por qué no lo publicaste como respuesta? :-)
terminus
Porque había publicado mi respuesta usando declare primero :-)
Allan Wind
55
type [-t]Es bueno decirle qué es una cosa, pero cuando se prueba si algo es una función, es lento ya que tiene que canalizar para grep o usar backticks, los cuales generan un subproceso.
Lloeki
1
A menos que haya leído mal, el uso de tipo tendrá que realizar un acceso mínimo admitido, para verificar si hay un archivo coincidente. @Lloeki, tienes toda la razón, pero es la opción que produce un resultado mínimo, y aún puedes usar el nivel de error. Podría obtener el resultado sin un subproceso, por ejemplo type -t realpath > /dev/shm/tmpfile ; read < /dev/shm/tmpfile(mal ejemplo). Sin embargo, declarar es la mejor respuesta ya que tiene 0 discos io.
Orwellophile
79
$ g() { return; }
$ declare -f g > /dev/null; echo $?
0
$ declare -f j > /dev/null; echo $?
1
Allan Wind
fuente
1
Me funcionó de maravilla. Especialmente porque mi shell no tiene el indicador -t para type (estaba teniendo muchos problemas con el tipo "$ command")
Dennis
2
De hecho, también funciona en zsh (útil para scripts rc), y no requiere grep para el tipo.
Lloeki
2
@DennisHodapp no ​​es necesario type -t, puede confiar en el estado de salida. Hace tiempo type program_name > /dev/null 2>&1 && program_name arguments || echo "error"que veía si podría llamar a algo. Obviamente, el type -tmétodo anterior también permite detectar el tipo, no solo si es "invocable".
0xC0000022L
@ 0xC0000022L ¿y si program_name no es una función?
David Winiecki
2
@ 0xC0000022L Estaba discutiendo acerca de cómo usar el estado de salida no te permite saber si program_name es una función, pero ahora creo que lo hiciste cuando dijiste "Obviamente, el tipo -t y el método anterior también permiten detectar el tipo , no solo si es "invocable". Lo siento.
David Winiecki
40

Si declarar es 10 veces más rápido que la prueba, esta parece ser la respuesta obvia.

Editar: a continuación, la -fopción es superflua con BASH, no dude en dejarla fuera. Personalmente, tengo problemas para recordar qué opción hace cuál, así que solo uso ambas. -f muestra funciones y -F muestra nombres de funciones.

#!/bin/sh

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

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

La opción "-F" para declarar hace que solo devuelva el nombre de la función encontrada, en lugar de todo el contenido.

No debería haber ninguna penalización de rendimiento medible por usar / dev / null, y si te preocupa tanto:

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

O combine los dos, para su propio disfrute sin sentido. Ambos trabajan.

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
fuente
2
La opción '-f' es redundante.
Rajish
3
La -Fopción no existe en zsh (útil para la portabilidad)
Lloeki
-Ftampoco es realmente necesario: parece suprimir solo la definición de función / cuerpo.
azulado
1
@blueyed Puede que no sea necesario, pero es altamente deseable, estamos tratando de confirmar que existe una función, no enumerar todo su contenido (lo cual es algo ineficiente). ¿Verificaría si un archivo está presente usando cat "$fn" | wc -c? En cuanto a zsh, si la etiqueta bash no te dio una pista, tal vez la pregunta en sí debería haberlo hecho. "Determinar si existe una función en bash". Además, señalaría que, si bien la -Fopción no existe en zsh, tampoco causa un error, por lo tanto, el uso de -f y -F permite que la verificación tenga éxito tanto en zsh como en bash, que de lo contrario no lo haría. .
Orwellophile
@Orwellophile -Fse usa en zsh para números de coma flotante. No puedo ver por qué el uso lo -Fhace mejor en bash ?! Tengo la impresión de que declare -ffunciona igual en bash (con respecto al código de retorno).
azulado
18

Tomando prestado de otras soluciones y comentarios, se me ocurrió esto:

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

Usado como ...

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

Comprueba si el argumento dado es una función y evita redireccionamientos y otros grepping.

Grégory Joseph
fuente
Bien, mi favorito del grupo! ¿No quieres comillas dobles alrededor del argumento también? Como en[ $(type -t "$1")"" == 'function' ]
quickshiftin
Gracias @quickshiftin; No sé si quiero esas comillas dobles, pero probablemente tengas razón, aunque ... ¿se puede declarar una función con un nombre que deba ser citado?
Grégory Joseph
44
Estás usando bash, usa en [[...]]lugar de [...]y elimina el hack de citas. También retrocede tenedor, que es lento. Usar en su declare -f $1 > /dev/nulllugar.
Lloeki
3
Evitando errores con argumentos vacíos, reduciendo comillas y utilizando la igualdad que cumple con '=' posix, se puede reducir de forma segura a :: fn_exists() { [ x$(type -t $1) = xfunction ]; }
qneill
10

Desenterrando una publicación anterior ... pero recientemente tuve uso de esto y probé ambas alternativas descritas con:

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

esto generó:

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

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

declarar es un helluvalot más rápido!

jonathanserafini
fuente
1
Se puede hacer sin grep: test_type_nogrep () { a () { echo 'a' ;}; local b=$(type a); c=${b//is a function/}; [ $? = 0 ] && return 1 || return 0; }
qneill
@qneill Hice una prueba algo más extensa en mi respuesta ,
jarno
PIPE es el elemento más lento. Esta prueba no se compara typey declare. Se compara type | grepcon declare. Esta es una gran diferencia.
kyb
7

Se reduce a usar 'declarar' para verificar la salida o el código de salida.

Estilo de salida:

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

Uso:

isFunction some_name && echo yes || echo no

Sin embargo, si la memoria sirve, la redirección a nulo es más rápida que la sustitución de salida (hablando de, el método `cmd` horrible y anticuado debe ser desterrado y $ (cmd) utilizado en su lugar). Y dado que declare devuelve verdadero / falso si se encuentra / no encontrado, y las funciones devuelven el código de salida del último comando en la función, por lo que generalmente no es necesario un retorno explícito, y dado que verificar el código de error es más rápido que verificar un valor de cadena (incluso una cadena nula):

Estilo de estado de salida:

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

Probablemente sea lo más sucinto y benigno posible.

Scott
fuente
3
Para un uso sucinto máximoisFunction() { declare -F "$1"; } >&-
Neil
3
isFunction() { declare -F -- "$@" >/dev/null; }Es mi recomendación. También funciona en una lista de nombres (solo tiene éxito si todas son funciones), no da problemas con los nombres que comienzan -y, a mi lado ( bash4.2.25), declaresiempre falla cuando se cierra la salida >&-, porque no puede escribir el nombre a stdout en ese caso
Tino
Y tenga en cuenta que a echoveces puede fallar con la "llamada interrumpida del sistema" en algunas plataformas. En ese caso, "check && echo yes || echo no" aún puede emitirse nosi checkes verdadero.
Tino
7

Probar diferentes soluciones:

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

salidas, por ejemplo:

test_declare (f es función)

usuario real 0m0,055s 0m0,041s sys 0m0,004s código de salida 0

test_declare2 (f es función)

0m0,042s real usuario 0m0,022s sys 0m0,017s código de salida 0

test_type (f es función)

usuario real 0m2.200s 0m1,619s sys 0m1,008s código de salida 0

test_type2 (f es función)

real 0m0,746s usuario 0m0,534s sys 0m0,237s código de salida 0

test_declare (f sin establecer)

0m0,040s usuario real 0m0,029s sys 0m0,010s código de salida 1

test_declare2 (f sin establecer)

usuario real 0m0,038s 0m0,038s sys 0m0,000s código de salida 1

test_type (f sin establecer)

usuario real 0m2,438s 0m1,678s sys 0m1,045s código de salida 1

test_type2 (f sin establecer)

0m0,805s real usuario 0m0,541s sys 0m0,274s código de salida 1

test_declare (f es una cadena)

0m0,043s real usuario 0m0,034s sys 0m0,007s código de salida 1

test_declare2 (f es una cadena)

0m0,039s real usuario 0m0,035s sys 0m0,003s código de salida 1

test_type (f es una cadena)

usuario real 0m2,394s 0m1,679s sys 0m1,035s código de salida 1

test_type2 (f es una cadena)

real 0m0,851s usuario 0m0,554s sys 0m0,294s código de salida 1

Entonces declare -F fparece ser la mejor solución.

jarno
fuente
Atención aquí: declare -F fno devuelve un valor distinto de cero si f no existe en zsh, pero bash sí. Ten cuidado al usarlo. declare -f f, por otro lado, funciona como se espera adjuntando la definición de la función en el stdout (que puede ser molesto ...)
Manoel Vilela
1
¿Has probado que test_type3 () { [[ $(type -t f) = function ]] ; }hay un costo marginal de definir una var local (aunque <10%)
Oliver
4

De mi comentario sobre otra respuesta (que sigo perdiendo cuando vuelvo a esta página)

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

actualizar

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

$ isFunc isFunc
$ echo $?
0
$ isFunc dfgjhgljhk
$ echo $?
1
$ isFunc psgrep && echo yay
yay
$
Jonás
fuente
2

Lo mejoraría para:

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

Y úsalo así:

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

fuente
2

Esto le indica si existe, pero no es que sea una función.

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

Me gustó especialmente la solución de Grégory Joseph

Pero lo he modificado un poco para superar el "truco feo entre comillas dobles":

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

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

Es posible usar 'type' sin ningún comando externo, pero debe llamarlo dos veces, por lo que aún termina aproximadamente el doble de lento que la versión 'declare':

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

Además, esto no funciona en POSIX sh, por lo que es totalmente inútil, excepto como trivia.

Noah Spurrier
fuente
test_type_nogrep () {a () {echo 'a';}; local b = $ (tipo a); c = $ {b // es una función /}; PS = 0] && retorno 1 || devuelve 0; } - qneill
Alexx Roche