تحديد ما إذا كانت الوظيفة موجودة في باش
سؤال
أقوم حاليًا بإجراء بعض اختبارات الوحدة التي يتم تنفيذها من bash.تتم تهيئة اختبارات الوحدة وتنفيذها وتنظيفها في برنامج نصي bash.يحتوي هذا البرنامج النصي عادةً على وظائف init() وexecute() وcleanup().لكنها ليست إلزامية.أود اختبار ما إذا كانت محددة أم لا.
لقد فعلت ذلك سابقًا عن طريق إمساك المصدر وضبطه، لكن بدا الأمر خاطئًا.هل هناك طريقة أكثر أناقة للقيام بذلك؟
يحرر:المقتطف التالي يعمل مثل السحر:
fn_exists()
{
LC_ALL=C type $1 | grep -q 'shell function'
}
المحلول
أعتقد أنك تبحث عن الأمر "النوع".سيخبرك ما إذا كان الشيء عبارة عن وظيفة، أو وظيفة مضمنة، أو أمر خارجي، أو لم يتم تحديده.مثال:
$ 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
نصائح أخرى
$ g() { return; }
$ declare -f g > /dev/null; echo $?
0
$ declare -f j > /dev/null; echo $?
1
إذا كان الإعلان أسرع بـ 10 مرات من الاختبار، فقد يبدو هذا هو الجواب الواضح.
يحرر:تحت ال -f
الخيار غير ضروري مع BASH، فلا تتردد في تركه.شخصيًا، أجد صعوبة في تذكر الخيار الذي يفعل أيًا منهما، لذلك أستخدم كليهما فقط. -F يظهر وظائف، و -F يظهر أسماء الوظائف.
#!/bin/sh
function_exists() {
declare -f -F $1 > /dev/null
return $?
}
function_exists function_name && echo Exists || echo No such function
يؤدي خيار "-F" للإعلان إلى إرجاع اسم الوظيفة التي تم العثور عليها فقط، بدلاً من المحتويات بأكملها.
لا ينبغي أن يكون هناك أي عقوبة أداء قابلة للقياس لاستخدام /dev/null، وإذا كان ذلك يقلقك كثيرًا:
fname=`declare -f -F $1`
[ -n "$fname" ] && echo Declare -f says $fname exists || echo Declare -f says $1 does not exist
أو اجمع بين الاثنين لتتمتع بمتعة لا طائل من ورائها.كلاهما يعمل.
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
بالاقتراض من الحلول والتعليقات الأخرى، توصلت إلى ما يلي:
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' ]
}
تستعمل ك ...
if ! fn_exists $FN; then
echo "Hey, $FN does not exist ! Duh."
exit 2
fi
يتحقق مما إذا كانت الوسيطة المحددة دالة، ويتجنب عمليات إعادة التوجيه وغيرها من عمليات الإمساك.
ازالة مشاركة قديمة...لكنني استخدمت هذا مؤخرًا واختبرت كلا البديلين الموصوفين بـ:
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
تم إنشاء هذا :
real 0m0.064s
user 0m0.040s
sys 0m0.020s
type
real 0m2.769s
user 0m1.620s
sys 0m1.130s
أعلن هو helluvalot أسرع!
يتلخص الأمر في استخدام "الإعلان" إما للتحقق من رمز الإخراج أو الخروج.
نمط الإخراج:
isFunction() { [[ "$(declare -Ff "$1")" ]]; }
الاستخدام:
isFunction some_name && echo yes || echo no
ومع ذلك، إذا كانت الذاكرة مفيدة، فإن إعادة التوجيه إلى القيمة null تكون أسرع من استبدال المخرجات (بالحديث عن ذلك، يجب استبعاد طريقة `cmd` الفظيعة والعفا عليها الزمن واستخدام $(cmd) بدلاً من ذلك.) وبما أن التصريح يُرجع صواب/خطأ إذا تم العثور عليه/ لم يتم العثور عليه، وتقوم الوظائف بإرجاع رمز الخروج للأمر الأخير في الوظيفة، لذلك لا يكون الإرجاع الصريح ضروريًا عادةً، وبما أن التحقق من رمز الخطأ أسرع من التحقق من قيمة السلسلة (حتى سلسلة فارغة):
نمط حالة الخروج:
isFunction() { declare -Ff "$1" >/dev/null; }
من المحتمل أن يكون هذا موجزًا وحميدًا قدر الإمكان.
سرعة اختبار الحلول المختلفة
#!/bin/bash
f () {
echo 'This is a test function.'
echo 'This has more than one command.'
return 0
}
test_declare () {
declare -f f > /dev/null
}
test_declare2 () {
declare -F f > /dev/null
}
test_type () {
type -t f | grep -q 'function'
}
test_type2 () {
local var=$(type -t f)
[[ "${var-}" = function ]]
}
post=
for j in 1 2; do
echo
echo 'declare -f' $post
time for i in $(seq 1 1000); do test_declare; done
echo
echo 'declare -F' $post
time for i in $(seq 1 1000); do test_declare2; done
echo
echo 'type with grep' $post
time for i in $(seq 1 1000); do test_type; done
echo
echo 'type with var' $post
time for i in $(seq 1 1000); do test_type2; done
unset -f f
post='(f unset)'
done
المخرجات على سبيل المثال:
أعلن -f
user 0m0.037s user 0m0.024s sys 0m0.012s
أعلن -F
المستخدم الحقيقي 0M0.030S 0M0.020S SYS 0M0.008S
اكتب مع grep
user 0m1.772s user 0m0.084s sys 0m0.340s
اكتب مع فار
المستخدم الحقيقي 0M0.770S 0M0.096S SYS 0M0.160S
أعلن -f (f غير محدد)
ureal 0m0.031s user 0m0.028s sys 0m0.000s
أعلن -F (f غير محدد)
user 0m0.031s user 0m0.020s sys 0m0.008s
اكتب باستخدام grep (f غير محدد)
user 0m1.859s user 0m0.100s sys 0m0.348s
اكتب مع var (f unset)
user 0m0.683s user 0m0.092s sys 0m0.160s
لذا declare -F f && echo function f exists. || echo function f does not exist.
يبدو أن الحل الأفضل.
fn_exists()
{
[[ $(type -t $1) == function ]] && return 0
}
تحديث
isFunc ()
{
[[ $(type -t $1) == function ]]
}
$ isFunc isFunc
$ echo $?
0
$ isFunc dfgjhgljhk
$ echo $?
1
$ isFunc psgrep && echo yay
yay
$
هذا يخبرك إذا كانت موجودة، ولكن ليس أنها وظيفة
fn_exists()
{
type $1 >/dev/null 2>&1;
}
أعجبني بشكل خاص الحل من غريغوري جوزيف
لكنني قمت بتعديله قليلاً للتغلب على "خدعة الاقتباس المزدوج القبيحة":
function is_executable()
{
typeset TYPE_RESULT="`type -t $1`"
if [ "$TYPE_RESULT" == 'function' ]; then
return 0
else
return 1
fi
}
من تعليقي على إجابة أخرى (والتي أفتقدها عندما أعود إلى هذه الصفحة)
$ 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
سأقوم بتحسينه إلى:
fn_exists()
{
type $1 2>/dev/null | grep -q 'is a function'
}
واستخدامها مثل هذا:
fn_exists test_function
if [ $? -eq 0 ]; then
echo 'Function exists!'
else
echo 'Function does not exist...'
fi
من الممكن استخدام الأمر 'type' دون أي أوامر خارجية، ولكن يجب عليك استدعاؤه مرتين، لذلك يظل في النهاية أبطأ بحوالي ضعف الإصدار 'declare':
test_function () {
! type -f $1 >/dev/null 2>&1 && type -t $1 >/dev/null 2>&1
}
بالإضافة إلى أن هذا لا يعمل في POSIX sh، لذا فهو لا قيمة له على الإطلاق باستثناء كونه معلومات تافهة!