ตรวจสอบว่าฟังก์ชั่นที่มีอยู่ในทุบตี


187

ขณะนี้ฉันกำลังทำการทดสอบหน่วยที่ดำเนินการจากการทุบตี การทดสอบหน่วยจะเริ่มต้นใช้งานและทำความสะอาดในสคริปต์ทุบตี สคริปต์นี้เป็นปกติมีฟังก์ชัน init (), execute () และ cleanup () แต่พวกเขาไม่ได้บังคับ ฉันต้องการทดสอบว่าพวกเขาเป็นหรือไม่ได้กำหนดไว้

ก่อนหน้านี้ฉันทำแบบนี้โดยการ greping และ seding แหล่งที่มา แต่ดูเหมือนว่าผิด มีวิธีที่สง่างามกว่านี้หรือไม่?

แก้ไข: sniplet ต่อไปนี้ทำงานเหมือนเครื่องราง:

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

ขอบคุณ ฉันใช้สิ่งนี้เพื่อกำหนดฟังก์ชั่นเวอร์ชั่นที่มีเงื่อนไขออกมาเมื่อโหลดเชลล์ไลบรารี fn_exists foo || foo() { :; }
ฮาร์วีย์

2
คุณสามารถบันทึก grep โดยใช้และtype -t ==
Roland Weber

ไม่ทำงานเมื่อภาษาไม่ใช่ภาษาอังกฤษ type test_functionบอกว่าtest_function on funktio.เมื่อใช้ภาษาฟินแลนด์และist eine Funktionเมื่อใช้ภาษาเยอรมัน
Kimmo Lehto

3
สำหรับสถานที่ที่ไม่ใช่ภาษาอังกฤษสำหรับผู้ที่LC_ALL=Cต้องทนความร้อน
gaRex

คำตอบ:


191

ฉันคิดว่าคุณกำลังมองหาคำสั่ง 'type' มันจะบอกคุณว่าบางสิ่งบางอย่างเป็นฟังก์ชั่นฟังก์ชั่นในตัวคำสั่งภายนอกหรือเพียงแค่ไม่ได้กำหนด ตัวอย่าง:

$ 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คือตั๋วอาหาร
Allan Wind

4
ทำไมคุณไม่โพสต์มันเป็นคำตอบ? :-)
จุดประสงค์

เพราะฉันโพสต์คำตอบของฉันโดยใช้ประกาศ :-) ก่อน
อัลลันลม

5
type [-t]เป็นการดีที่จะบอกคุณว่าอะไรคือสิ่งที่ แต่เมื่อการทดสอบว่าบางสิ่งบางอย่างเป็นฟังก์ชั่นมันช้าเพราะคุณต้องไพพ์ไปที่ grep หรือใช้ backticks ซึ่งทั้งคู่วางไข่ subprocess
Lloeki

1
ยกเว้นว่าฉันอ่านผิดการใช้ประเภทจะต้องทำการเข้าถึงขั้นต่ำที่ยอมรับได้เพื่อตรวจสอบว่ามีไฟล์ที่ตรงกันหรือไม่ @loeki คุณค่อนข้างถูกต้อง แต่เป็นตัวเลือกที่ให้ผลลัพธ์น้อยที่สุดและคุณยังคงสามารถใช้ errorlevel ได้ คุณสามารถรับผลลัพธ์โดยไม่มีกระบวนการย่อยเช่นtype -t realpath > /dev/shm/tmpfile ; read < /dev/shm/tmpfile(ตัวอย่างที่ไม่ดี) อย่างไรก็ตามการประกาศเป็นคำตอบที่ดีที่สุดเนื่องจากมี 0 ดิสก์ io
Orwellophile

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

1
ทำงานได้ยอดเยี่ยมสำหรับฉัน โดยเฉพาะอย่างยิ่งเป็นเปลือกของฉันไม่ได้มีธง -t สำหรับประเภท (ผมมีปัญหามากกับประเภท "$ คำสั่ง" ก)
เดนนิส

2
แน่นอนมันยังทำงานใน zsh (มีประโยชน์สำหรับสคริปต์ rc) และไม่จำเป็นต้อง grep สำหรับประเภท
Lloeki

2
@DennisHodapp ไม่จำเป็นต้องใช้type -tคุณสามารถพึ่งพาสถานะทางออกแทน ฉันคุ้นเคยมานานแล้วtype program_name > /dev/null 2>&1 && program_name arguments || echo "error"ว่าฉันจะสามารถโทรหาบางสิ่งได้หรือไม่ เห็นได้ชัดว่าวิธีการtype -tและข้างต้นยังช่วยให้สามารถตรวจจับประเภทได้ไม่ว่าจะเป็น "callable"
0xC0000022L

@ 0xC0000022L จะเกิดอะไรขึ้นหาก program_name ไม่ใช่ฟังก์ชั่น
David Winiecki

2
@ 0xC0000022L ฉันมีปัญหาเกี่ยวกับการใช้สถานะออกไม่ให้คุณรู้ว่า program_name เป็นฟังก์ชั่น แต่ตอนนี้ฉันคิดว่าคุณทำที่อยู่เมื่อคุณพูดว่า "เห็นได้ชัดว่าประเภท -t และวิธีการข้างต้นยังช่วยในการตรวจสอบประเภท ไม่ใช่เพียงว่าเป็น "callable" ขอโทษ
David Winiecki

40

หากการประกาศเร็วกว่าการทดสอบ 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

2
ตัวเลือก '-f' ซ้ำซ้อน
Rajish

3
-Fตัวเลือก des ไม่มีอยู่ใน zsh (มีประโยชน์สำหรับการพกพา)
Lloeki

-Fยังไม่จำเป็นจริงๆ: ดูเหมือนว่าจะระงับการกำหนดฟังก์ชั่น / ร่างกายเท่านั้น
blueyed

1
@blueyed อาจไม่จำเป็น แต่เป็นที่ต้องการอย่างมากเราพยายามยืนยันฟังก์ชั่นที่มีอยู่ไม่ใช่รายการเนื้อหาทั้งหมด (ซึ่งค่อนข้างไม่มีประสิทธิภาพ) คุณช่วยตรวจสอบว่ามีไฟล์ที่ใช้อยู่cat "$fn" | wc -cหรือไม่? สำหรับ zsh ถ้าแท็กbashไม่ได้แย้มคุณอาจเป็นคำถามที่ควรมี msgstr "กำหนดว่าฟังก์ชั่นมีอยู่ใน bash หรือไม่" ฉันจะชี้ให้เห็นเพิ่มเติมว่าในขณะที่-Fตัวเลือกไม่มีอยู่ใน zsh แต่ก็ไม่ได้ทำให้เกิดข้อผิดพลาดดังนั้นการใช้ทั้ง -f และ -F ช่วยให้การตรวจสอบประสบความสำเร็จทั้งในzshและbashซึ่งไม่เช่นนั้น .
Orwellophile

@Orwellophile -Fใช้ใน zsh สำหรับตัวเลขทศนิยม ฉันไม่เห็นว่าทำไมการใช้-Fทำให้ดีขึ้นในการทุบตี?! ฉันได้รับความประทับใจที่declare -fทำงานเหมือนกันในทุบตี (เกี่ยวกับรหัสส่งคืน)
blueyed

18

การยืมตัวจากโซลูชั่นและความคิดเห็นอื่น ๆ ฉันได้สิ่งนี้:

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

มันตรวจสอบว่าอาร์กิวเมนต์ที่กำหนดเป็นฟังก์ชั่นและหลีกเลี่ยงการเปลี่ยนเส้นทางและ grepping อื่น ๆ


ดีที่ฉันชอบจากกลุ่ม! คุณไม่ต้องการอัญประกาศรอบการโต้แย้งด้วยใช่ไหม เช่นเดียวกับใน[ $(type -t "$1")"" == 'function' ]
quickshiftin

ขอบคุณ @quickshiftin; ฉันไม่รู้ว่าฉันต้องการอัญประกาศคู่นั้นหรือเปล่า แต่คุณอาจพูดถูก แต่ฟังก์ชันสามารถถูกประกาศด้วยชื่อที่ต้องยกมาได้หรือไม่?
Grégory Joseph

4
คุณกำลังใช้ bash ใช้[[...]]แทน[...]และกำจัดแฮ็กอ้าง เช่นกัน backticks fork ซึ่งช้า ใช้declare -f $1 > /dev/nullแทน
Lloeki

3
หลีกเลี่ยงข้อผิดพลาดด้วยอาร์กิวเมนต์ที่ว่างเปล่าการลดราคาและการใช้ความเท่าเทียมตามมาตรฐาน '=' posix จะสามารถลดได้อย่างปลอดภัยถึง :: fn_exists() { [ x$(type -t $1) = xfunction ]; }
qneill

10

กำลังขุดโพสต์เก่า ... แต่เมื่อไม่นานมานี้ฉันได้ใช้สิ่งนี้และทดสอบทั้งสองทางเลือกที่อธิบายด้วย:

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 เร็วขึ้น!


1
สามารถทำได้โดยไม่ต้อง grep: test_type_nogrep () { a () { echo 'a' ;}; local b=$(type a); c=${b//is a function/}; [ $? = 0 ] && return 1 || return 0; }
qneill

@qneill ฉันทำทดสอบค่อนข้างกว้างขวางมากขึ้นในคำตอบของฉัน ,
จาร์โนท

PIPE เป็นองค์ประกอบที่ช้าที่สุด การทดสอบนี้ไม่ได้เปรียบเทียบและtype declareก็จะเปรียบเทียบกับtype | grep declareนี่คือความแตกต่างที่ยิ่งใหญ่
kyb

7

มันลดลงโดยใช้ 'ประกาศ' เพื่อตรวจสอบผลลัพธ์หรือรหัสทางออก

สไตล์เอาท์พุท:

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

การใช้งาน:

isFunction some_name && echo yes || echo no

อย่างไรก็ตามหากหน่วยความจำทำหน้าที่การเปลี่ยนเส้นทางไปเป็นโมฆะเร็วกว่าการทดแทนเอาต์พุต (พูดถึงวิธีการ 'cmd' ที่น่ากลัวและล้าสมัยควรถูกระงับและใช้ $ (cmd) แทน) และเนื่องจากประกาศคืนค่าจริง / เท็จหากพบ / ไม่พบและฟังก์ชั่นส่งคืนรหัสทางออกของคำสั่งสุดท้ายในฟังก์ชั่นดังนั้นจึงมักไม่จำเป็นต้องส่งคืนอย่างชัดเจนและเนื่องจากการตรวจสอบรหัสข้อผิดพลาดนั้นเร็วกว่าการตรวจสอบค่าสตริง (แม้แต่สตริง null):

รูปแบบสถานะการออก:

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

นั่นอาจเกี่ยวกับรวบรัดและอ่อนโยนเท่าที่คุณจะทำได้


3
สำหรับการใช้งานที่กระชับที่สุดisFunction() { declare -F "$1"; } >&-
Neil

3
isFunction() { declare -F -- "$@" >/dev/null; }คือคำแนะนำของฉัน มันทำงานในรายการชื่อเช่นกัน (สำเร็จเฉพาะเมื่อทุกฟังก์ชั่น) ไม่มีปัญหากับชื่อที่ขึ้นต้นด้วย-และที่ด้านข้างของฉัน ( bash4.2.25) declareล้มเหลวเสมอเมื่อปิดเอาต์พุต>&-เนื่องจากมันไม่สามารถเขียนชื่อได้ เพื่อ stdout ในกรณีนั้น
Tino

และโปรดทราบว่าechoบางครั้งอาจล้มเหลวด้วย "การเรียกระบบขัดจังหวะ" ในบางแพลตฟอร์ม ในกรณีดังกล่าว "check && echo yes || echo no" ยังสามารถแสดงผลได้noหากcheckเป็นจริง
Tino

7

ทดสอบวิธีแก้ปัญหาต่าง ๆ :

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

เอาท์พุทเช่น:

test_declare (f คือฟังก์ชัน)

ผู้ใช้จริง 0m0,055s 0m0,041s sys 0m0,004s รหัสทางออก 0

test_declare2 (f คือฟังก์ชัน)

ผู้ใช้ 0m0,042s จริง 0m0,022s sys 0m0,017s รหัสทางออก 0

test_type (f คือฟังก์ชัน)

ผู้ใช้จริง 0m2,200s 0m1,619s sys 0m1,008s รหัสทางออก 0

test_type2 (f คือฟังก์ชัน)

ผู้ใช้ 0m0,746s จริง 0m0,534s sys 0m0,237s รหัสทางออก 0

test_declare (f unset)

ผู้ใช้ 0m0,040s จริง 0m0,029s sys 0m0,010s รหัสทางออก 1

test_declare2 (ไม่มีการตั้งค่า f)

ผู้ใช้จริง 0m0,038s 0m0,038s sys 0m0,000s รหัสทางออก 1

test_type (ยกเลิกการตั้งค่า f)

ผู้ใช้ 0m2,438s จริง 0m1,678s sys รหัสทางออก 0m1,045s 0

test_type2 (ไม่มีการตั้งค่า f)

จริง 0m0,805 ผู้ใช้ 0m0,541s sys 0m0,274s รหัสทางออก 1

test_declare (f คือสตริง)

ผู้ใช้ 0m0,043s จริง 0m0,034s sys 0m0,007s รหัสทางออก 1

test_declare2 (f คือสตริง)

ผู้ใช้ 0m0,039s จริง 0m0,035s sys 0m0,003s รหัสทางออก 1

test_type (f คือสตริง)

ผู้ใช้ 0m2,394s จริง 0m1,679s sys รหัสทางออก 0m1,035s 0

test_type2 (f คือสตริง)

ผู้ใช้ 0m0,851s จริง 0m0,554s sys 0m0,294s รหัสทางออก 1

ดังนั้นdeclare -F fดูเหมือนจะเป็นทางออกที่ดีที่สุด


ข้อควรสนใจที่นี่: declare -F fจะไม่ส่งคืนค่าที่ไม่เป็นศูนย์หาก f ไม่มีอยู่ใน zsh แต่กลับใช้ใช่ ใช้ความระมัดระวัง declare -f fอีกทางหนึ่งทำงานได้ตามที่คาดหมายไว้แนบนิยามของฟังก์ชันบน stdout (ซึ่งอาจสร้างความรำคาญ ... )
Manoel Vilela

1
คุณเคยลองมาtest_type3 () { [[ $(type -t f) = function ]] ; }แล้วหรือยังมีต้นทุนส่วนเพิ่มในการกำหนด var ท้องถิ่น (แม้ว่า <10%)
Oliver

4

จากความคิดเห็นของฉันในคำตอบอื่น (ซึ่งฉันหายไปเมื่อฉันกลับมาที่หน้านี้)

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

ปรับปรุง

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

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

2

ฉันจะปรับปรุงให้เป็น:

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

2

สิ่งนี้จะบอกคุณว่ามีอยู่หรือไม่ แต่ไม่ใช่ว่าเป็นฟังก์ชั่น

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

2

ฉันชอบวิธีแก้ปัญหาจากGrégory Joseph

แต่ฉันได้ปรับเปลี่ยนนิดหน่อยเพื่อเอาชนะ

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

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

0

เป็นไปได้ที่จะใช้ 'type' โดยไม่มีคำสั่งภายนอก แต่คุณต้องเรียกมันสองครั้งดังนั้นมันจึงยังคงช้าลงประมาณสองเท่าของรุ่น 'ประกาศ':

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

นอกจากนี้ยังใช้ไม่ได้กับ POSIX sh ดังนั้นจึงไม่มีค่าทั้งหมดยกเว้นเรื่องเล็กน้อย!


test_type_nogrep () {a () {echo 'a';}; ท้องถิ่น b = $ (พิมพ์); c = $ {b // เป็นฟังก์ชัน /}; [$? = 0] && ส่งคืน 1 || กลับ 0 } - qneill
Alexx Roche
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.