Tentukan apakah ada fungsi di bash

187

Saat ini saya sedang melakukan beberapa unit test yang dijalankan dari bash. Tes unit diinisialisasi, dieksekusi dan dibersihkan dalam skrip bash. Script ini biasanya berisi fungsi init (), execute () dan cleanup (). Tetapi mereka tidak wajib. Saya ingin menguji apakah mereka didefinisikan atau tidak.

Saya melakukan ini sebelumnya dengan menangkap dan membius sumbernya, tetapi tampaknya salah. Apakah ada cara yang lebih elegan untuk melakukan ini?

Sunting: Sniplet berikut ini berfungsi seperti pesona:

fn_exists()
{
    LC_ALL=C type $1 | grep -q 'shell function'
}
ujung
sumber
Terima kasih. Saya menggunakan ini untuk secara kondisional mendefinisikan versi fungsi yang terhapus ketika memuat pustaka shell. fn_exists foo || foo() { :; }
Harvey
2
Anda dapat menyimpan grep dengan menggunakan type -tdan ==.
Roland Weber
Tidak berfungsi saat lokal bukan bahasa Inggris. type test_functionmengatakan test_function on funktio.saat menggunakan bahasa Finlandia dan ist eine Funktionsaat menggunakan bahasa Jerman.
Kimmo Lehto
3
Untuk lokal yang bukan Inggris LC_ALL=Cke
resque

Jawaban:

192

Saya pikir Anda sedang mencari perintah 'ketik'. Ini akan memberi tahu Anda apakah ada fungsi, fungsi bawaan, perintah eksternal, atau tidak didefinisikan. Contoh:

$ 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
sumber
120
type -t $functionadalah tiket makan.
Allan Wind
4
Mengapa Anda tidak mempostingnya sebagai jawaban? :-)
terminus
Karena saya telah memposting jawaban saya menggunakan mendeklarasikan pertama :-)
Allan Wind
5
type [-t]memang bagus untuk memberi tahu Anda apa masalahnya, tetapi ketika menguji apakah ada fungsi, itu lambat karena Anda harus menggunakan pipa untuk menangkap atau menggunakan backtick, yang keduanya menghasilkan subproses.
Lloeki
1
Kecuali saya salah membaca, menggunakan tipe harus melakukan akses yang diakui minimal, untuk memeriksa apakah ada file yang cocok. @Lloeki, Anda cukup benar, tetapi itu adalah opsi yang menghasilkan output minimal, dan Anda masih dapat menggunakan tingkat kesalahan. Anda bisa mendapatkan hasilnya tanpa proses, misalnya type -t realpath > /dev/shm/tmpfile ; read < /dev/shm/tmpfile(contoh buruk). Namun, menyatakan adalah jawaban terbaik karena memiliki 0 disk io.
Orwellophile
79
$ g() { return; }
$ declare -f g > /dev/null; echo $?
0
$ declare -f j > /dev/null; echo $?
1
Allan Wind
sumber
1
bekerja luar biasa untuk saya. Terutama karena shell saya tidak memiliki flag -t untuk tipe (saya mengalami banyak masalah dengan ketik "$ command")
Dennis
2
Memang, ini juga berfungsi di zsh (berguna untuk skrip rc), dan tidak perlu melakukan grep untuk jenisnya.
Lloeki
2
@DennisHodapp tidak perlu type -t, Anda dapat mengandalkan status keluar sebagai gantinya. Saya sudah lama terbiasa type program_name > /dev/null 2>&1 && program_name arguments || echo "error"melihat apakah saya bisa memanggil sesuatu. Jelas metode type -tdan di atas juga memungkinkan untuk mendeteksi tipe, bukan hanya apakah itu "bisa dipanggil".
0xC0000022L
@ 0xC0000022L bagaimana jika program_name bukan fungsi?
David Winiecki
2
@ 0xC0000022L Saya telah melakukan nitpicking tentang bagaimana menggunakan status keluar tidak memberi tahu Anda jika program_name adalah fungsi, tetapi sekarang saya pikir Anda benar-benar mengatasinya ketika Anda berkata "Jelas jenis -t dan metode di atas juga memungkinkan untuk mendeteksi tipe tersebut , tidak hanya apakah itu "bisa dipanggil". " Maaf.
David Winiecki
40

Jika menyatakan 10x lebih cepat dari tes, ini tampaknya jawaban yang jelas.

Sunting: Di bawah, -fopsi ini berlebihan dengan BASH, jangan ragu untuk meninggalkannya. Secara pribadi, saya mengalami kesulitan mengingat opsi mana yang melakukan itu, jadi saya hanya menggunakan keduanya. -f menunjukkan fungsi, dan -F menunjukkan nama fungsi.

#!/bin/sh

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

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

Opsi "-F" untuk menyatakan menyebabkannya hanya mengembalikan nama fungsi yang ditemukan, daripada seluruh konten.

Seharusnya tidak ada penalti kinerja yang terukur untuk penggunaan / dev / null, dan jika itu membuat Anda sangat khawatir:

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

Atau kombinasikan keduanya, untuk kesenangan tak berguna Anda sendiri. Keduanya bekerja.

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
sumber
2
Opsi '-f' berlebihan.
Rajish
3
The -Fpilihan des tidak ada dalam zsh (berguna untuk portabilitas)
Lloeki
-Fjuga tidak perlu benar-benar: tampaknya menekan definisi fungsi / tubuh saja.
blueyed
1
@blueyed Mungkin tidak perlu, tetapi sangat diinginkan, kami mencoba mengonfirmasi fungsi yang ada, tidak mencantumkan seluruh konten (yang agak tidak efisien). Apakah Anda akan memeriksa apakah file hadir menggunakan cat "$fn" | wc -c? Sedangkan untuk zsh, jika tag bash tidak memberi tahu Anda, mungkin pertanyaan itu seharusnya ada. + Msgstr "Tentukan apakah suatu fungsi ada di bash". Saya lebih lanjut akan menunjukkan, bahwa sementara -Fopsi tidak ada di zsh, itu juga tidak menyebabkan kesalahan, oleh karena itu penggunaan kedua -f dan -F memungkinkan cek berhasil di kedua zsh dan bash yang jika tidak maka tidak akan .
Orwellophile
@Orwellophile -Fdigunakan dalam zsh untuk angka floating point. Saya tidak mengerti mengapa menggunakan -Fmembuatnya lebih baik di bash ?! Saya mendapat kesan yang declare -fbekerja sama di bash (mengenai kode pengembalian).
blueyed
18

Meminjam dari solusi dan komentar lain, saya datang dengan ini:

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

Digunakan sebagai ...

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

Ia memeriksa apakah argumen yang diberikan adalah fungsi, dan menghindari pengalihan dan grepping lainnya.

Grégory Joseph
sumber
Bagus, teman saya dari grup! Apakah Anda tidak ingin tanda kutip ganda di sekitar argumen juga? Seperti di[ $(type -t "$1")"" == 'function' ]
quickshiftin
Terima kasih @quickshiftin; Saya tidak tahu apakah saya ingin tanda kutip ganda itu, tetapi Anda mungkin benar, meskipun .. bisakah suatu fungsi bahkan dideklarasikan dengan nama yang perlu dikutip?
Grégory Joseph
4
Anda menggunakan bash, gunakan [[...]]alih-alih, [...]dan singkirkan retas kutipan. Garpu backticks, yang lambat. Gunakan declare -f $1 > /dev/nullsebagai gantinya.
Lloeki
3
Menghindari kesalahan dengan argumen kosong, mengurangi kuotasi, dan menggunakan kesetaraan compliant '=' posix, dapat dikurangi dengan aman menjadi :: fn_exists() { [ x$(type -t $1) = xfunction ]; }
qneill
10

Pengerukan pos lama ... tapi saya baru-baru ini menggunakan ini dan menguji kedua alternatif yang dijelaskan dengan:

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

ini dihasilkan:

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

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

menyatakan adalah helluvalot lebih cepat!

jonathanserafini
sumber
1
Ini dapat dilakukan tanpa grep: test_type_nogrep () { a () { echo 'a' ;}; local b=$(type a); c=${b//is a function/}; [ $? = 0 ] && return 1 || return 0; }
qneill
@Qneill Saya membuat tes yang agak lebih luas dalam jawaban saya ,
jarno
PIPE adalah elemen paling lambat. Tes ini tidak membandingkan typedan declare. Dibandingkan type | grepdengan declare. Ini perbedaan besar.
kyb
7

Itu bermuara menggunakan 'menyatakan' baik untuk memeriksa output atau kode keluar.

Gaya keluaran:

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

Pemakaian:

isFunction some_name && echo yes || echo no

Namun, jika memori berfungsi, pengalihan ke null lebih cepat daripada substitusi output (berbicara tentang, metode `cmd` yang mengerikan dan ketinggalan zaman harus dibuang dan $ (cmd) digunakan sebagai gantinya.) Dan karena menyatakan mengembalikan benar / salah jika ditemukan / tidak ditemukan, dan fungsi mengembalikan kode keluar dari perintah terakhir dalam fungsi sehingga pengembalian eksplisit biasanya tidak diperlukan, dan karena memeriksa kode kesalahan lebih cepat daripada memeriksa nilai string (bahkan string nol):

Keluar dari gaya status:

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

Itu mungkin tentang singkat dan jinak yang Anda bisa dapatkan.

Scott
sumber
3
Untuk penggunaan ringkas dan ringkasisFunction() { declare -F "$1"; } >&-
Neil
3
isFunction() { declare -F -- "$@" >/dev/null; }adalah rekomendasi saya. Ia bekerja pada daftar nama juga (hanya berhasil jika semua fungsi), tidak memberikan masalah dengan nama yang dimulai dengan -dan, di sisi saya ( bash4.2.25), declareselalu gagal ketika output ditutup dengan >&-, karena tidak dapat menulis nama untuk stdout dalam kasus itu
Tino
Dan perlu diketahui, bahwa echokadang-kadang bisa gagal dengan "panggilan sistem terputus" pada beberapa platform. Dalam hal itu "centang && gema ya || gema tidak" masih dapat menampilkan noapakah checkitu benar.
Tino
7

Menguji berbagai solusi:

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

output misalnya:

test_declare (f is function)

real 0m0,055s pengguna 0m0,041s sys 0m0,004s kode keluar 0

test_declare2 (f is function)

real 0m0,042s pengguna 0m0,022s sys 0m0,017s kode keluar 0

test_type (f is function)

real 0m2,200 pengguna 0m1,619s sys 0m1,008s kode keluar 0

test_type2 (f is function)

real 0m0,746s pengguna 0m0,534s sys 0m0,237s kode keluar 0

test_declare (f unset)

real 0m0,040s pengguna 0m0,029s sys 0m0,010s kode keluar 1

test_declare2 (f unset)

real 0m0,038s pengguna 0m0,038s sys 0m0,000s kode keluar 1

test_type (f unset)

real 0m2,438s pengguna 0m1,678s sys 0m1,045s kode keluar 1

test_type2 (f unset)

real 0m0,805s pengguna 0m0,541s sys 0m0,274s kode keluar 1

test_declare (f adalah string)

real 0m0,043s pengguna 0m0,034s sys 0m0,007s kode keluar 1

test_declare2 (f adalah string)

real 0m0,039s pengguna 0m0,035s sys 0m0,003s kode keluar 1

test_type (f is string)

real 0m2,394s pengguna 0m1,679s sys 0m1,035s kode keluar 1

test_type2 (f adalah string)

0m0,851s nyata pengguna 0m0,554s sys 0m0,294s kode keluar 1

Jadi declare -F fsepertinya menjadi solusi terbaik.

jarno
sumber
Perhatian di sini: declare -F ftidak mengembalikan nilai non-nol jika f tidak ada di zsh, tetapi bash ya. Hati-hati menggunakannya. declare -f f, dengan tangan lain, berfungsi seperti yang diharapkan melampirkan definisi fungsi pada stdout (yang dapat mengganggu ...)
Manoel Vilela
1
Sudahkah Anda mencoba test_type3 () { [[ $(type -t f) = function ]] ; }ada biaya marjinal untuk menentukan var lokal (walaupun <10%)
Oliver
4

Dari komentar saya pada jawaban lain (yang saya lewatkan saat kembali ke halaman ini)

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

memperbarui

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

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

Saya akan meningkatkannya menjadi:

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

Dan gunakan seperti ini:

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

sumber
2

Ini memberi tahu Anda jika ada, tetapi bukan berarti itu adalah fungsi

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

Saya sangat menyukai solusi dari Grégory Joseph

Tetapi saya telah memodifikasinya sedikit untuk mengatasi "trik jelek kutipan ganda":

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

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

Dimungkinkan untuk menggunakan 'ketik' tanpa perintah eksternal, tetapi Anda harus memanggilnya dua kali, sehingga masih berakhir sekitar dua kali lebih lambat dari versi 'nyatakan':

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

Plus ini tidak berfungsi di POSIX sh, jadi itu sama sekali tidak berharga kecuali sebagai hal sepele!

Noah Spurrier
sumber
test_type_nogrep () {a () {echo 'a';}; lokal b = $ (ketik a); c = $ {b // adalah fungsi /}; [$? = 0] && mengembalikan 1 || return 0; } - qneill
Alexx Roche