Bash'da bir işlevin var olup olmadığını belirleme


187

Şu anda bash yürütülen bazı birim testleri yapıyorum. Birim testleri bir bash betiğinde başlatılır, yürütülür ve temizlenir. Bu komut dosyası genellikle bir init (), execute () ve cleanup () işlevleri içerir. Ancak zorunlu değildir. Tanımlanmış olup olmadıklarını test etmek istiyorum.

Bunu daha önce kaynağı açıp kapatarak yaptım, ama yanlış görünüyordu. Bunu yapmanın daha zarif bir yolu var mı?

Düzenleme: Aşağıdaki sniplet bir cazibe gibi çalışır:

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

Teşekkürler. Bunu bir kabuk kitaplığı yüklerken işlevlerin stubbed sürümlerini koşullu olarak tanımlamak için kullandım. fn_exists foo || foo() { :; }
Harvey

2
Sen kullanarak grep kaydedebilir type -tve ==.
Roland Weber

Yerel ayar İngilizce olmadığında çalışmaz. type test_functiondiyor test_function on funktio.Fin yerel kullanırken ve ist eine FunktionAlman kullanırken.
Kimmo Lehto

3
İngilizce dışındaki yerel ayarlar için LC_ALL=Cresque için
gaRex

Yanıtlar:


191

Bence 'tip' komutunu arıyorsunuz. Bir şeyin bir işlev, yerleşik işlev, harici komut olup olmadığını veya tanımlanmadığını size söyleyecektir. Misal:

$ 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 $functionyemek biletidir.
Allan Wind

4
Neden cevap olarak göndermedin? :-)
terminus

Çünkü ilk önce beyanı kullanarak cevabımı yayınlamıştım :-)
Allan Wind

5
type [-t]size bir şeyin ne olduğunu söylemek güzeldir , ancak bir şeyin bir işlev olup olmadığını test ederken , her ikisi de bir alt işlem yaratan sırıtmak veya geri tepme kullanmak zorunda kaldığınız için yavaştır.
Lloeki

1
Yanlış okuduğum sürece, eşleşen bir dosya olup olmadığını kontrol etmek için türün kullanımı kabul edilebilir bir minimum erişim gerçekleştirmek zorunda kalacak. @Lloeki, oldukça haklısınız, ancak minimum çıktı üreten seçenektir ve yine de hata seviyesini kullanabilirsiniz. Sonucu bir alt işlem olmadan alabilirsiniz, örneğin type -t realpath > /dev/shm/tmpfile ; read < /dev/shm/tmpfile(kötü örnek). Ancak, bildirimi en iyi yanıttır, çünkü 0 disk io'dur.
Orwellophile

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

1
benim için harika çalıştı. Özellikle benim kabuk türü için -t bayrağı yok gibi (Ben "$ command" türü ile çok sorun vardı)
Dennis

2
Gerçekten de, zsh (rc komut dosyaları için yararlı) olarak çalışır ve tür için grep gerektirmez.
Lloeki

2
@DennisHodapp gerek yok type -t, bunun yerine çıkış durumuna güvenebilirsiniz. Uzun zamandan beri bir type program_name > /dev/null 2>&1 && program_name arguments || echo "error"şey arayabilip arayamayacağımı gördüm. Açıkçası type -tve yukarıdaki yöntem, yalnızca "çağrılabilir" olup olmadığını değil, türün algılanmasına da izin verir.
0xC0000022L

@ 0xC0000022L Program_adı bir işlev değilse ne olur?
David Winiecki

2
@ 0xC0000022L Çıkış durumunu kullanmanın program_adı bir işlev olup olmadığını nasıl bilmediğiniz hakkında nitpicking oldu, ama şimdi "Açıkçası type -t ve yukarıdaki yöntem de tür algılamak için izin verir dedi , yalnızca "çağrılabilir" olup olmadığını değil. Afedersiniz.
David Winiecki

40

Beyan, testten 10 kat daha hızlıysa, bu açık bir cevap gibi görünecektir.

Düzenleme: Aşağıda, -fseçenek BASH ile gereksizdir, dışarıda bırakmaktan çekinmeyin. Şahsen, hangi seçeneğin hangisini yaptığını hatırlamakta zorlanıyorum, bu yüzden her ikisini de kullanıyorum. -f işlevleri ve -F işlev adlarını gösterir.

#!/bin/sh

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

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

Bildirmek için "-F" seçeneği, tüm içerik yerine yalnızca bulunan işlevin adını döndürmesine neden olur.

/ Dev / null kullanımı için ölçülebilir bir performans cezası olmamalı ve sizi bu kadar endişelendiriyorsa:

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

Ya da kendi anlamsız zevkiniz için ikisini birleştirin. İkisi de çalışıyor.

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
'-F' seçeneği yedeklidir.
Rajish

3
-FSeçenek des değil (taşınabilirlik için yararlı) zsh var
Lloeki

-Fayrıca gerçekten gerekli değildir: sadece işlev tanımını / gövdesini bastırıyor gibi görünüyor.
14'te blueyed

1
@blueyed Gerekli olmayabilir, ancak çok arzu edilir, bir fonksiyonun var olduğunu doğrulamaya çalışıyoruz, tüm içeriğini listelemiyoruz (biraz verimsiz). Kullanarak bir dosyanın olup olmadığını kontrol eder misiniz cat "$fn" | wc -c? Zsh'a gelince, bash etiketi sizi ipucu vermediyse, belki de sorunun kendisi olmalıdır. Msgstr "Bir işlevin bash'ta olup olmadığını belirle". Ben ayrıca ederken, işaret etmek isterim -Fseçenek zsh içinde yok, aynı zamanda, hem -f dolayısıyla kullanımı bir hataya neden olmaz ve -F çek hem başarılı sağlayan zsh ve Bash hangi aksi takdirde olmaz .
Orwellophile

@Orwellophile -Fkayan nokta sayıları için zsh içinde kullanılır. Neden -Fbash kullanmanın daha iyi hale getirdiğini göremiyorum declare -fBash (dönüş kodu ile ilgili) aynı çalışır izlenimi aldım .
16:45 de blueyed

18

Diğer çözümlerden ve yorumlardan ödünç alarak, bunu buldum:

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

Olarak kullanılır ...

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

Verilen argümanın bir işlev olup olmadığını kontrol eder ve yeniden yönlendirmelerden ve diğer açılmadan kaçınır.


Güzel, gruptan benim fav! Tartışmanın etrafında da çift tırnak istemiyor musunuz? Olduğu gibi[ $(type -t "$1")"" == 'function' ]
quickshiftin

Teşekkürler @quickshiftin; Ben bu çift tırnak isteyip istemediğimi bilmiyorum, ama muhtemelen haklısın, ancak .. bir fonksiyon bile alıntılanması gereken bir isim ile ilan edilebilir?
Grégory Joseph

4
Sen bash kullanıyorsun, [[...]]bunun yerine kullan [...]ve alıntı hackinden kurtul. Ayrıca yavaşça çatal ters çevirir. declare -f $1 > /dev/nullBunun yerine kullanın .
Lloeki

3
Boş bağımsız değişkenlerle hataları önlemek, tırnak işaretlerini azaltmak ve '=' posix uyumlu eşitliği kullanarak, güvenli bir şekilde ::fn_exists() { [ x$(type -t $1) = xfunction ]; }
qneill

10

Eski bir gönderiyi taramak ... ama son zamanlarda bunu kullandım ve her iki alternatifi de test ettik:

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

bu oluşturulan:

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

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

beyan daha hızlı bir helluvalot olduğunu!


1
test_type_nogrep () { a () { echo 'a' ;}; local b=$(type a); c=${b//is a function/}; [ $? = 0 ] && return 1 || return 0; }
Grep

@qneill Cevabımda biraz daha kapsamlı bir test yaptım ,
jarno

BORU en yavaş elementtir. Bu test karşılaştırmaz typeve declare. O karşılaştırır type | grepile declare. Bu büyük bir fark.
kyb

7

Çıktıyı kontrol etmek veya çıkış kodunu kontrol etmek için 'declare' kullanmak üzere kaynar.

Çıktı tarzı:

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

Kullanımı:

isFunction some_name && echo yes || echo no

Bununla birlikte, bellek hizmet veriyorsa, null değerine yönlendirme çıktı ikamesinden daha hızlıdır (bahsetmek gerekirse, korkunç ve eski tarihli cmd` yöntemi kaldırılmalı ve bunun yerine $ (cmd) kullanılmalıdır). bulunamadı ve işlevler, işlevdeki son komutun çıkış kodunu döndürür, böylece açık bir dönüş genellikle gerekli değildir ve hata kodunu kontrol etmek bir dize değerini (null dize bile) kontrol etmekten daha hızlı olduğundan:

Durum stilinden çık:

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

Bu muhtemelen elde edebileceğiniz kadar özlü ve iyi huyludur.


3
Maksimum özlü kullanım içinisFunction() { declare -F "$1"; } >&-
Neil

3
isFunction() { declare -F -- "$@" >/dev/null; }benim tavsiyem. Bir isim listesi üzerinde de çalışır (yalnızca tüm işlevler başarılı olduğunda başarılı olur), adlarla başlayan adlarda sorun çıkarmaz -ve benim tarafımda ( bash4.2.25), declareçıktı kapatıldığında her zaman başarısız olur >&-, çünkü adı yazamaz bu durumda stdout'a
Tino

Ve unutmayın ki echobu bazen bazı platformlarda "kesintiye uğramış sistem çağrısı" ile başarısız olabilir. Bu durumda yine de çıktı "&& echo evet || yankı hayır kontrol" noeğer checkdoğrudur.
Tino

7

Farklı çözümleri test etme:

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

çıktılar örn:

test_declare (f işlevdir)

gerçek 0m0.055s kullanıcı 0m0.041s sys 0m0,004s çıkış kodu 0

test_declare2 (f işlevdir)

gerçek 0m0.042s kullanıcı 0m0.022s sys 0m0.017s çıkış kodu 0

test_type (f işlevdir)

gerçek 0m2.200s kullanıcı 0m1.619s sys 0m1.008s çıkış kodu 0

test_type2 (f işlevdir)

gerçek 0m0.746s kullanıcı 0m0.534s sys 0m0.237s çıkış kodu 0

test_declare (ayarlanmamış)

gerçek 0m0.040s kullanıcı 0m0.029s sys 0m0.010s çıkış kodu 1

test_declare2 (f unset)

gerçek 0m0.038s kullanıcı 0m0.038s sys 0m0.000s çıkış kodu 1

test_type (ayarlanmamış)

gerçek 0m2.438s kullanıcı 0m1.678s sys 0m1.045s çıkış kodu 1

test_türü2 (ayarlanmamış)

gerçek 0m0.805s kullanıcı 0m0.541s sys 0m0.274s çıkış kodu 1

test_declare (f dizedir)

gerçek 0m0.043s kullanıcı 0m0.034s sys 0m0,007s çıkış kodu 1

test_declare2 (f dizedir)

gerçek 0m0.039s kullanıcı 0m0.035s sys 0m0,003s çıkış kodu 1

test_type (f dizedir)

gerçek 0m2.394s kullanıcı 0m1.679s sys 0m1.035s çıkış kodu 1

test_type2 (f dizedir)

gerçek 0m0.851s kullanıcı 0m0.554s sys 0m0.294s çıkış kodu 1

Bu yüzden declare -F fen iyi çözüm gibi görünüyor.


Burada dikkat: declare -F fzsh'de f yoksa sıfır olmayan bir değer döndürmez, ancak bash yes. Kullanırken dikkatli olun. declare -f f, başka bir el ile, stdout (ki sinir bozucu olabilir ...) fonksiyon tanımını ekleyerek beklendiği gibi çalışır
Manoel Vilela

1
test_type3 () { [[ $(type -t f) = function ]] ; }Yerel bir var tanımlamanın marjinal bir maliyeti olduğunu denediniz mi (<% 10 olmasına rağmen)
Oliver

4

Başka bir yanıt hakkındaki yorumumdan (bu sayfaya geri döndüğümde kaybolmaya devam ediyorum)

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

Güncelleme

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

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

2

Bunu geliştirmek için:

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

Ve şu şekilde kullanın:

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

2

Bu, var olup olmadığını söyler, ancak bunun bir işlev olmadığını

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

2

Özellikle Grégory Joseph'in çözümünü beğendim

Ama ben "çift teklif çirkin hile" üstesinden biraz değiştirdim:

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

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

0

Herhangi bir harici komut olmadan 'type' kullanmak mümkündür, ancak bunu iki kez çağırmanız gerekir, bu yüzden hala 'declare' versiyonunun yaklaşık iki katı kadar yavaş sonuç verir:

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

Ayrıca bu POSIX sh'de çalışmaz, bu yüzden trivia dışında tamamen değersizdir!


test_type_nogrep () {a () {echo 'a';}; yerel b = $ (tip a); c = $ {b // bir işlevdir /}; [$? = 0] && dönüş 1 || dönüş 0; } - qneill
Alexx Roche
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.