Determinar se existe uma função no bash

187

Atualmente estou fazendo alguns testes de unidade que são executados a partir do bash. Os testes de unidade são inicializados, executados e limpos em um script bash. Esse script geralmente contém as funções init (), execute () e cleanup (). Mas eles não são obrigatórios. Eu gostaria de testar se eles estão ou não definidos.

Eu fiz isso anteriormente, contornando e seduzindo a fonte, mas parecia errado. Existe uma maneira mais elegante de fazer isso?

Editar: O sniplet a seguir funciona como um encanto:

fn_exists()
{
    LC_ALL=C type $1 | grep -q 'shell function'
}
terminus
fonte
Obrigado. Eu usei isso para definir condicionalmente versões stubbed out de funções ao carregar uma biblioteca shell. fn_exists foo || foo() { :; }
Harvey
2
Você pode salvar o grep usando type -te ==.
Roland Weber
Não funciona quando a localidade é diferente do inglês. type test_functiondiz test_function on funktio.ao usar a localidade finlandesa e ist eine Funktionao alemão.
Kimmo Lehto
3
Para localidades não inglesas LC_ALL=Cno
resque

Respostas:

191

Eu acho que você está procurando o comando 'type'. Indica se algo é uma função, função interna, comando externo ou simplesmente não definido. Exemplo:

$ 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
fonte
119
type -t $functioné o bilhete da refeição.
Allan Wind
4
Por que você não publicou como resposta? :-)
terminus
Porque eu tinha postado minha resposta usando declare primeira :-)
Allan Vento
5
type [-t]É bom dizer o que é uma coisa, mas ao testar se algo é uma função, é lento, pois você precisa canalizar para grep ou usar backticks, os quais geram um subprocesso.
Lloeki
1
A menos que eu tenha interpretado mal, o uso do tipo precisará executar um acesso mínimo admissível, para verificar se há um arquivo correspondente. @Lloeki, você está certo, mas é a opção que produz saída mínima, e você ainda pode usar o nível de erro. Você pode obter o resultado sem um subprocesso, por exemplo type -t realpath > /dev/shm/tmpfile ; read < /dev/shm/tmpfile(exemplo ruim). No entanto, declare é a melhor resposta, pois possui 0 disco io.
Orwellophile
79
$ g() { return; }
$ declare -f g > /dev/null; echo $?
0
$ declare -f j > /dev/null; echo $?
1
Allan Wind
fonte
1
funcionou demais para mim. Especialmente porque o meu shell não tem o sinalizador -t para o tipo (eu estava tendo muitos problemas com o tipo "$ command") #
Dennis
2
De fato, ele também funciona no zsh (útil para scripts rc) e não requer grep para o tipo.
Lloeki
2
@DennisHodapp não há necessidade type -t, você pode confiar no status de saída. Há muito tempo type program_name > /dev/null 2>&1 && program_name arguments || echo "error"que vejo se consigo ligar para alguma coisa. Obviamente, o type -tmétodo e o método acima também permitem detectar o tipo, não apenas se é "exigível".
0xC0000022L
@ 0xC0000022L e se program_name não for uma função?
21715 David Winiecki
2
@ 0xC0000022L Eu estava falando sobre como usar o status de saída não informa se program_name é uma função, mas agora acho que você abordou isso quando disse: "Obviamente, o tipo -t e o método acima também permitem detectar o tipo , não apenas se é "exigível". " Desculpe.
21815 David Winiecki
40

Se declarar é 10x mais rápido que o teste, esta parece a resposta óbvia.

Edit: Abaixo, a -fopção é supérflua com o BASH, fique à vontade para deixar de fora. Pessoalmente, tenho dificuldade em lembrar qual opção faz qual, então apenas uso as duas. -f mostra funções e -F mostra nomes de funções.

#!/bin/sh

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

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

A opção "-F" a declarar faz com que retorne apenas o nome da função encontrada, em vez de todo o conteúdo.

Não deve haver nenhuma penalidade de desempenho mensurável pelo uso de / dev / null e se isso lhe preocupa tanto:

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

Ou combine os dois, para seu próprio prazer inútil. Ambos trabalham.

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
fonte
2
A opção '-f' é redundante.
Rajish
3
A -Fopção des não existir em zsh (útil para portabilidade)
Lloeki
-Ftambém não é realmente necessário: parece suprimir apenas a definição / corpo da função.
blueyed 21/09/16
1
@ blueyed Pode não ser necessário, mas é altamente desejável, estamos tentando confirmar que uma função existe, não listar todo o conteúdo (o que é um pouco ineficiente). Você verificaria se um arquivo está presente usando cat "$fn" | wc -c? Quanto ao zsh, se a tag bash não o identificou, talvez a pergunta em si deva ter. "Determinar se existe uma função no bash". Gostaria de salientar ainda que, embora a -Fopção não exista no zsh, ela também não causa um erro, portanto, o uso de -f e -F permite que a verificação seja bem-sucedida no zsh e no bash, caso contrário, não existiria. .
Orwellophile
@Orwellophile -Fé usado no zsh para números de ponto flutuante. Não vejo por que usar -Fmelhora no bash ?! Tive a impressão de que declare -ffunciona da mesma forma no bash (em relação ao código de retorno).
blueyed 22/09/16
18

Tomando emprestado de outras soluções e comentários, eu vim com isso:

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

Ele verifica se o argumento fornecido é uma função e evita redirecionamentos e outros grepping.

Grégory Joseph
fonte
Bom, meu favorito do grupo! Você não quer aspas duplas também? Como em[ $(type -t "$1")"" == 'function' ]
quickshiftin
Obrigado @quickshiftin; Não sei se quero essas aspas duplas, mas você provavelmente está certo, embora .. uma função possa ser declarada com um nome que precisa ser citado?
Gregory Joseph
4
Você está usando o bash, use em [[...]]vez de [...]e livre-se da citação hack. Também reticula o garfo, que é lento. Use em declare -f $1 > /dev/nullvez disso.
Lloeki
3
Evitando erros com argumentos vazios, reduzindo aspas e usando a igualdade compatível com '=' posix, ela pode ser reduzida com segurança para :: ' fn_exists() { [ x$(type -t $1) = xfunction ]; }
qneill
10

Dragagem de um post antigo ... mas recentemente eu usei isso e testei as duas alternativas descritas com:

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

isso gerou:

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

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

declarar é muito mais rápido!

jonathanserafini
fonte
1
Isso pode ser feito sem o grep: test_type_nogrep () { a () { echo 'a' ;}; local b=$(type a); c=${b//is a function/}; [ $? = 0 ] && return 1 || return 0; }
qneill
@qneill Fiz teste um pouco mais extenso em minha resposta ,
Jarno
PIPE é o elemento mais lento. Este teste não compara typee declare. Compara type | grepcom declare. Isso é uma grande diferença.
KYB
7

Tudo se resume a usar 'declare' para verificar o código de saída ou saída.

Estilo de saída:

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

Uso:

isFunction some_name && echo yes || echo no

No entanto, se a memória servir, o redirecionamento para null é mais rápido que a substituição da saída (falando sobre, o terrível e desatualizado método `cmd` deve ser banido e $ (cmd) usado em seu lugar.) E como declare retorna true / false se encontrado / não encontrado e as funções retornam o código de saída do último comando na função, portanto, um retorno explícito geralmente não é necessário e, uma vez que a verificação do código de erro é mais rápida do que a verificação de um valor de sequência (mesmo uma sequência nula):

Estilo de status de saída:

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

Provavelmente é o mais sucinto e benigno possível.

Scott
fonte
3
Para o máximo de concisão usoisFunction() { declare -F "$1"; } >&-
Neil
3
isFunction() { declare -F -- "$@" >/dev/null; }é a minha recomendação. Também funciona em uma lista de nomes (é bem-sucedida apenas se todas forem funções), não apresenta problemas com nomes iniciados por -e, ao meu lado ( bash4.2.25), declaresempre falha quando a saída é fechada >&-, porque não pode escrever o nome para stdout nesse caso
Tino
E lembre-se de que echoàs vezes isso pode falhar com a "chamada interrompida do sistema" em algumas plataformas. Nesse caso, "check && echo yes || echo no" ainda pode gerar saída nose checkfor verdadeiro.
Tino
7

Testando diferentes soluções:

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

saídas, por exemplo:

test_declare (f é função)

real 0m0,055s usuário 0m0,041s sys 0m0,004s código de saída 0

test_declare2 (f é função)

0m0,042s real usuário 0m0,022s sys 0m0,017s código de saída 0

test_type (f é função)

real 0m2.200s usuário 0m1,619s sys 0m1,008s código de saída 0

test_type2 (f é função)

real 0m0,746s usuário 0m0,534s sys 0m0,237s código de saída 0

test_declare (não definido)

0m0,040s real usuário 0m0,029s sys 0m0,010s código de saída 1

test_declare2 (não definido)

0m0,038s real usuário 0m0,038s sys 0m0,000s código de saída 1

test_type (não definido)

real 0m2,438s usuário 0m1,678s sys 0m1,045s código de saída 1

test_type2 (f desativado)

0m0,805s real usuário 0m0,541s sys 0m0,274s código de saída 1

test_declare (f é string)

0m0,043s real usuário 0m0,034s sys 0m0,007s código de saída 1

test_declare2 (f é string)

real 0m0,039s usuário 0m0,035s sys 0m0,003s código de saída 1

test_type (f é string)

real 0m2,394s usuário 0m1,679s sys 0m1,035s código de saída 1

test_type2 (f é string)

real 0m0,851s usuário 0m0,554s sys 0m0,294s código de saída 1

Então declare -F fparece ser a melhor solução.

jarno
fonte
Atenção aqui: declare -F fnão retorna um valor diferente de zero se f não existir no zsh, mas sim no bash. Cuidado ao usá-lo. declare -f f, Por outro lado, funciona como esperado anexando a definição da função no stdout (que pode ser irritante ...)
Manoel Vilela
1
Você já tentou test_type3 () { [[ $(type -t f) = function ]] ; }há um custo marginal de definir uma var local (embora <10%)
Oliver
4

Do meu comentário sobre outra resposta (que continuo faltando quando volto 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
fonte
3
fn_exists()
{
   [[ $(type -t $1) == function ]] && return 0
}

atualizar

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

$ isFunc isFunc
$ echo $?
0
$ isFunc dfgjhgljhk
$ echo $?
1
$ isFunc psgrep && echo yay
yay
$
Jonah
fonte
2

Eu o melhoraria para:

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

E use-o assim:

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

fonte
2

Isso informa se existe, mas não é uma função

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

Eu particularmente gostei da solução de Grégory Joseph

Mas eu o modifiquei um pouco para superar o "truque feio de aspas duplas":

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

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

É possível usar 'type' sem nenhum comando externo, mas você deve chamá-lo duas vezes, para que ainda termine duas vezes mais lento que a versão 'declare':

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

Além disso, isso não funciona no POSIX sh, por isso é totalmente inútil, exceto como trivialidades!

Noah Spurrier
fonte
test_type_nogrep () {a () {echo 'a';}; local b = $ (tipo a); c = $ {b // é uma função /}; [$? = 0] && retornar 1 || retornar 0; } - qneill
Alexx Roche