Determina se esiste una funzione in bash


187

Attualmente sto facendo alcuni test unitari che vengono eseguiti da bash. I test unitari vengono inizializzati, eseguiti e ripuliti in uno script bash. Questo script di solito contiene una init (), execute () e cleanup () funzioni. Ma non sono obbligatori. Mi piacerebbe testare se sono o non sono definiti.

L'ho fatto in precedenza bestemmiando e sedando la fonte, ma sembrava sbagliato. C'è un modo più elegante per farlo?

Modifica: il seguente sniplet funziona come un incantesimo:

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

Grazie. L'ho usato per definire in modo condizionale versioni di funzioni eliminate durante il caricamento di una libreria shell. fn_exists foo || foo() { :; }
Harvey,

2
Puoi salvare il grep usando type -te ==.
Roland Weber,

Non funziona quando le impostazioni internazionali non sono in inglese. type test_functiondice test_function on funktio.quando si usa la localizzazione finlandese e ist eine Funktionquando si usa il tedesco.
Kimmo Lehto,

3
Per i locali non inglesi LC_ALL=Cal
resque

Risposte:


192

Penso che tu stia cercando il comando 'type'. Ti dirà se qualcosa è una funzione, una funzione integrata, un comando esterno o semplicemente non definito. Esempio:

$ 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 $functionè il biglietto del pasto.
Allan Wind,

4
Perché non l'hai pubblicato come risposta? :-)
capolinea

Perché avevo inviato la mia risposta usando prima dichiarare :-)
Allan Wind,

5
type [-t]è bello dirti che cos'è una cosa, ma quando si verifica se qualcosa è una funzione, è lento poiché è necessario eseguire il pipe per grep o utilizzare i backtick, entrambi i quali generano un sottoprocesso.
Lloeki,

1
A meno che non abbia letto male, l'uso del tipo dovrà eseguire un accesso dichiaratamente minimo, per verificare se esiste un file corrispondente. @Lloeki, hai ragione, ma è l'opzione che produce un output minimo e puoi ancora usare il livello di errore. È possibile ottenere il risultato senza un sottoprocesso, ad es. type -t realpath > /dev/shm/tmpfile ; read < /dev/shm/tmpfile(Esempio errato). Tuttavia, dichiarare è la risposta migliore poiché ha 0 disco io.
Orwellophile,

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

1
ha funzionato alla grande per me. Soprattutto perché la mia shell non ha il flag -t per il tipo (avevo un sacco di problemi con il tipo "$ command")
Dennis

2
In effetti, funziona anche in zsh (utile per gli script rc) e non richiede grep per il tipo.
Lloeki,

2
@DennisHodapp non è necessario type -t, puoi invece fare affidamento sullo stato di uscita. Ho a lungo type program_name > /dev/null 2>&1 && program_name arguments || echo "error"visto se sarei stato in grado di chiamare qualcosa. Ovviamente il type -tmetodo e sopra consente anche di rilevare il tipo, non solo se è "richiamabile".
0xC0000022L

@ 0xC0000022L cosa succede se nome_programma non è una funzione?
David Winiecki,

2
@ 0xC0000022L Stavo puntualizzando come l'utilizzo dello stato di uscita non ti facesse sapere se nome_programma è una funzione, ma ora penso che tu abbia risolto il problema quando hai detto "Ovviamente il tipo -t e il metodo sopra consentono anche di rilevare il tipo , non solo se è "richiamabile". " Scusate.
David Winiecki,

40

Se dichiarare è 10 volte più veloce del test, questa sembrerebbe la risposta ovvia.

Modifica: di seguito, l' -fopzione è superflua con BASH, sentiti libero di lasciarla fuori. Personalmente, ho difficoltà a ricordare quale opzione fa quale, quindi uso semplicemente entrambi. -f mostra le funzioni e -F mostra i nomi delle funzioni.

#!/bin/sh

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

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

L'opzione "-F" per dichiarare fa sì che restituisca solo il nome della funzione trovata, anziché l'intero contenuto.

Non ci dovrebbe essere alcuna penalità di prestazione misurabile per l'uso di / dev / null, e se ti preoccupa così tanto:

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

O combina i due, per il tuo divertimento inutile. Entrambi funzionano.

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'opzione '-f' è ridondante.
Rajish,

3
L' -Fopzione des non esiste in zsh (utile per la portabilità)
Lloeki,

-Finoltre non è realmente necessario: sembra sopprimere la definizione di funzione / solo corpo.
blueyed

1
@blueyed Potrebbe non essere necessario, ma è altamente desiderabile, stiamo provando a confermare l'esistenza di una funzione, non elencando i suoi interi contenuti (che è in qualche modo inefficiente). Verificheresti se un file è presente usando cat "$fn" | wc -c? Per quanto riguarda zsh, se il tag bash non ti ha indotto, forse la domanda stessa dovrebbe avere. "Determina se esiste una funzione in bash". Vorrei inoltre sottolineare che, sebbene l' -Fopzione non esista in zsh, non provoca anche un errore, quindi l'uso di -f e -F consente al controllo di avere successo sia in zsh che in bash che altrimenti non avrebbe .
Orwellophile,

@Orwellophile -Fè usato in zsh per i numeri in virgola mobile. Non riesco a capire perché l'utilizzo lo -Frenda migliore in bash ?! Ho avuto l'impressione che declare -ffunzioni allo stesso modo in bash (per quanto riguarda il codice di ritorno).
blueyed

18

Prendendo in prestito da altre soluzioni e commenti, ho pensato a questo:

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

Usato come ...

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

Verifica se l'argomento dato è una funzione ed evita reindirizzamenti e altri grepping.


Bello, il mio preferito dal gruppo! Non vuoi doppie virgolette anche attorno all'argomento? Come in[ $(type -t "$1")"" == 'function' ]
cambio rapido il

Grazie @quickshiftin; Non so se voglio quelle doppie virgolette, ma probabilmente hai ragione, anche se .. una funzione può anche essere dichiarata con un nome che dovrebbe essere citato?
Grégory Joseph,

4
Stai usando bash, usa [[...]]invece di [...]ed elimina l'hack della citazione. Anche il backtick della forcella, che è lento. Usa declare -f $1 > /dev/nullinvece.
Lloeki,

3
Evitando errori con argomenti vuoti, riducendo le virgolette e usando l'uguaglianza conforme '=' posix, può essere tranquillamente ridotto a :: fn_exists() { [ x$(type -t $1) = xfunction ]; }
qneill

10

Eliminare un vecchio post ... ma recentemente ne ho fatto uso e ho testato entrambe le alternative descritte 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

questo ha generato:

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

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

dichiarare è un helluvalot più veloce!


1
Può essere fatto senza grep: test_type_nogrep () { a () { echo 'a' ;}; local b=$(type a); c=${b//is a function/}; [ $? = 0 ] && return 1 || return 0; }
qneill

@qneill Ho fatto un po 'più ampio test nella mia risposta ,
jarno

PIPE è l'elemento più lento. Questo test non confronta typee declare. Si confronta type | grepcon declare. Questa è una grande differenza.
Kyb,

7

Si riduce a usare 'dichiarare' per controllare il codice di uscita o di uscita.

Stile di uscita:

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

Uso:

isFunction some_name && echo yes || echo no

Tuttavia, se la memoria serve, il reindirizzamento a null è più veloce della sostituzione dell'output (a proposito, il metodo `cmd` terribile e obsoleto dovrebbe essere bandito e dovrebbe essere usato $ (cmd).) E poiché dichiarare restituisce vero / falso se trovato / non trovato e le funzioni restituiscono il codice di uscita dell'ultimo comando nella funzione, pertanto un ritorno esplicito di solito non è necessario e poiché la verifica del codice di errore è più rapida della verifica di un valore di stringa (anche una stringa nulla):

Stile stato di uscita:

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

Questo è probabilmente il più succinto e benigno possibile.


3
Per la massima sintonia utilizzareisFunction() { declare -F "$1"; } >&-
Neil,

3
isFunction() { declare -F -- "$@" >/dev/null; }è la mia raccomandazione. Funziona anche su un elenco di nomi (riesce solo se tutte sono funzioni), non dà problemi con i nomi che iniziano con -e, al mio fianco ( bash4.2.25), declarefallisce sempre quando l'output viene chiuso con >&-, perché non può scrivere il nome a stdout in quel caso
Tino

E tieni presente che a echovolte può non riuscire con la "chiamata di sistema interrotta" su alcune piattaforme. In tal caso "check && echo yes || echo no" può comunque essere emesso nose checkè vero.
Tino,

7

Test di diverse soluzioni:

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

uscite ad es .:

test_declare (f è la funzione)

reale 0m0,055s utente 0m0,041s sys 0m0,004s codice di uscita 0

test_declare2 (f è la funzione)

reale 0m0,042s utente 0m0,022s sys 0m0,017s codice di uscita 0

test_type (f è la funzione)

reale 0m2.200s utente 0m1.619s sys 0m1.008s codice di uscita 0

test_type2 (f è la funzione)

reale 0m0.746s utente 0m0.534s sys 0m0.237s codice di uscita 0

test_declare (f unset)

real 0m0,040s user 0m0,029s sys 0m0,010s codice di uscita 1

test_declare2 (f unset)

real 0m0,038s user 0m0,038s sys 0m0,000s codice di uscita 1

test_type (f unset)

reale 0m2.438s utente 0m1.678s sys 0m1.045s codice di uscita 1

test_type2 (f unset)

real 0m0.805s user 0m0.541s sys 0m0.274s codice di uscita 1

test_declare (f è stringa)

real 0m0,043s user 0m0,034s sys 0m0,007s codice di uscita 1

test_declare2 (f è stringa)

real 0m0,039s user 0m0,035s sys 0m0,003s codice di uscita 1

test_type (f è stringa)

reale 0m2.394s utente 0m1.679s sys 0m1.035s codice di uscita 1

test_type2 (f è stringa)

real 0m0,851s user 0m0,554s sys 0m0,294s codice di uscita 1

Quindi declare -F fsembra essere la soluzione migliore.


Attenzione qui: declare -F fnon restituisce un valore diverso da zero se f non esiste su zsh, ma bash sì. Fai attenzione ad usarlo. declare -f f, d'altra parte, funziona come previsto allegando la definizione della funzione sullo stdout (che può essere fastidioso ...)
Manoel Vilela,

1
Hai provato che test_type3 () { [[ $(type -t f) = function ]] ; }esiste un costo marginale per definire una var locale (anche se <10%)
Oliver,

4

Dal mio commento su un'altra risposta (che mi manca quando torno a questa pagina)

$ 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
}

aggiornare

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

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

2

Lo migliorerei per:

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

E usalo in questo modo:

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

2

Questo ti dice se esiste, ma non che sia una funzione

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

2

Mi è piaciuta particolarmente la soluzione di Grégory Joseph

Ma l'ho modificato un po 'per superare il "brutto trucco della doppia virgoletta":

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

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

0

È possibile utilizzare 'type' senza comandi esterni, ma è necessario chiamarlo due volte, quindi finisce circa due volte più lento della versione 'declare':

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

Inoltre questo non funziona in POSIX sh, quindi è totalmente inutile tranne che per curiosità!


test_type_nogrep () {a () {echo 'a';}; locale b = $ (tipo a); c = $ {b // è una funzione /}; [$? = 0] && return 1 || ritorna 0; } - qneill
Alexx Roche il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.