Xác định nếu một hàm tồn tại trong bash


187

Hiện tại tôi đang thực hiện một số bài kiểm tra đơn vị được thực hiện từ bash. Các bài kiểm tra đơn vị được khởi tạo, thực hiện và dọn sạch trong tập lệnh bash. Tập lệnh này thường chứa các hàm init (), exec () và cleanup (). Nhưng chúng không bắt buộc. Tôi muốn kiểm tra xem chúng có hoặc không được xác định.

Tôi đã làm điều này trước đây bằng cách gre và sed nguồn, nhưng nó có vẻ sai. Có một cách thanh lịch hơn để làm điều này?

Chỉnh sửa: Sniplet sau hoạt động như một bùa mê:

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

Cảm ơn. Tôi đã sử dụng điều này để xác định một cách có điều kiện các phiên bản hàm bị lỗi khi tải thư viện shell. fn_exists foo || foo() { :; }
Harvey

2
Bạn có thể lưu grep bằng cách sử dụng type -t==.
Roland Weber

Không hoạt động khi ngôn ngữ không phải là tiếng Anh. type test_functionnói test_function on funktio.khi sử dụng ngôn ngữ Phần Lan và ist eine Funktionkhi sử dụng tiếng Đức.
Kimmo Lehto

3
Đối với các địa phương không phải tiếng Anh LC_ALL=Cđể bán lại
gaRex

Câu trả lời:


191

Tôi nghĩ rằng bạn đang tìm kiếm lệnh 'loại'. Nó sẽ cho bạn biết liệu một cái gì đó là một chức năng, chức năng tích hợp, lệnh bên ngoài hoặc chỉ không được xác định. Thí dụ:

$ 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

119
type -t $functionlà vé bữa ăn.
Gió Allan

4
Tại sao bạn không đăng nó như một câu trả lời? :-)
ga cuối

Bởi vì tôi đã đăng câu trả lời của mình bằng cách sử dụng khai báo trước :-)
Allan Wind

5
type [-t]thật tuyệt khi nói cho bạn biết đó là một thứ gì, nhưng khi kiểm tra xem có thứ gì đó là hàm hay không, nó sẽ chậm vì bạn phải chuyển sang grep hoặc sử dụng backticks, cả hai đều sinh ra một quy trình con.
Lloeki

1
Trừ khi tôi đọc sai, sử dụng loại sẽ phải thực hiện quyền truy cập tối thiểu được thừa nhận, để kiểm tra xem có tệp phù hợp hay không. @Lloeki, bạn khá chính xác, nhưng đó là tùy chọn tạo ra sản lượng tối thiểu và bạn vẫn có thể sử dụng errorlevel. Bạn có thể nhận được kết quả mà không cần một quy trình con, ví dụ type -t realpath > /dev/shm/tmpfile ; read < /dev/shm/tmpfile(ví dụ xấu). Tuy nhiên, khai báo là câu trả lời tốt nhất vì có 0 đĩa io.
Orwellophile

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

1
làm việc tuyệt vời cho tôi Đặc biệt là vì shell của tôi không có cờ -t cho loại (tôi đã gặp nhiều rắc rối với loại "$ lệnh")
Dennis

2
Thật vậy, nó cũng hoạt động trong zsh (hữu ích cho các tập lệnh RC) và không yêu cầu grep cho loại.
Lloeki

2
@DennisHodapp không cần type -t, thay vào đó bạn có thể dựa vào trạng thái thoát. Tôi đã sử dụng từ lâu type program_name > /dev/null 2>&1 && program_name arguments || echo "error"để xem liệu tôi có thể gọi một cái gì đó không. Rõ ràng type -tphương thức và phương pháp trên cũng cho phép phát hiện loại, không chỉ là liệu nó có thể gọi được hay không.
0xC0000022L

@ 0xC0000022L nếu chương trình không phải là hàm thì sao?
David Winiecki

2
@ 0xC0000022L Tôi đã hiểu về cách sử dụng trạng thái thoát không cho bạn biết nếu chương trình là một hàm, nhưng bây giờ tôi nghĩ bạn đã giải quyết điều đó khi bạn nói "Rõ ràng kiểu -t và phương thức trên cũng cho phép phát hiện loại , không chỉ là "nó có thể gọi được". " Lấy làm tiếc.
David Winiecki

40

Nếu khai báo nhanh hơn 10 lần so với thử nghiệm, đây có vẻ là câu trả lời rõ ràng.

Chỉnh sửa: Dưới đây, -ftùy chọn là không cần thiết với BASH, vui lòng bỏ qua. Cá nhân, tôi gặp khó khăn trong việc ghi nhớ tùy chọn nào, vì vậy tôi chỉ sử dụng cả hai. -f hiển thị các hàm và -F hiển thị tên hàm.

#!/bin/sh

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

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

Tùy chọn "-F" để khai báo khiến nó chỉ trả về tên của hàm tìm thấy, thay vì toàn bộ nội dung.

Không nên có bất kỳ hình phạt hiệu suất có thể đo lường nào khi sử dụng / dev / null và nếu điều đó làm bạn lo lắng nhiều như vậy:

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

Hoặc kết hợp cả hai, để tận hưởng vô nghĩa của riêng bạn. Cả hai đều làm việc.

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
Tùy chọn '-f' là dự phòng.
Rajish

3
Các -Ftùy chọn des không tồn tại trong zsh (hữu dụng cho di động)
Lloeki

-Fcũng không thực sự cần thiết: nó dường như chỉ triệt tiêu định nghĩa hàm / thân.
xanh da trời

1
@blueyed Có thể không cần thiết, nhưng rất mong muốn, chúng tôi đang cố gắng xác nhận một chức năng tồn tại, không liệt kê toàn bộ nội dung của nó (có phần không hiệu quả). Bạn sẽ kiểm tra nếu một tập tin hiện diện bằng cách sử dụng cat "$fn" | wc -c? Đối với zsh, nếu thẻ bash không gợi ý cho bạn, có lẽ chính câu hỏi nên có. "Xác định nếu một chức năng tồn tại trong bash". Tôi sẽ chỉ ra thêm rằng, mặc dù -Ftùy chọn không tồn tại trong zsh, nhưng nó cũng không gây ra lỗi, do đó việc sử dụng cả -f và -F cho phép kiểm tra thành công trong cả zshbash mà nếu không thì sẽ không .
Orwellophile

@Orwellophile -Fđược sử dụng trong zsh cho các số dấu phẩy động. Tôi không thể thấy lý do tại sao sử dụng -Flàm cho nó tốt hơn trong bash?! Tôi có ấn tượng declare -fhoạt động tương tự trong bash (liên quan đến mã trả về).
xanh da trời

18

Mượn từ các giải pháp và ý kiến ​​khác, tôi đã đưa ra điều này:

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

Được dùng như ...

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

Nó kiểm tra xem đối số đã cho có phải là hàm hay không và tránh chuyển hướng và grepping khác.


Đẹp, fav của tôi từ nhóm! Bạn không muốn báo giá gấp đôi xung quanh đối số quá? Như trong[ $(type -t "$1")"" == 'function' ]
quickshiftin

Cảm ơn @quickshiftin; Tôi không biết nếu tôi muốn những trích dẫn kép đó, nhưng có lẽ bạn đúng, mặc dù .. một chức năng thậm chí có thể được khai báo với một tên cần được trích dẫn không?
Grégory Joseph

4
Bạn đang sử dụng bash, sử dụng [[...]]thay vì [...]và thoát khỏi hack hack. Cũng backticks ngã ba, đó là chậm. Sử dụng declare -f $1 > /dev/nullthay thế.
Lloeki

3
Tránh các lỗi với các đối số trống, giảm dấu ngoặc kép và sử dụng đẳng thức tuân thủ posix '=', nó có thể được giảm xuống một cách an toàn thành :: fn_exists() { [ x$(type -t $1) = xfunction ]; }
qneill

10

Nạo vét một bài viết cũ ... nhưng gần đây tôi đã sử dụng điều này và đã thử nghiệm cả hai phương án được mô tả với:

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

điều này tạo ra:

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

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

tuyên bố là một helluvalot nhanh hơn!


1
Nó có thể được thực hiện mà không cần grep: test_type_nogrep () { a () { echo 'a' ;}; local b=$(type a); c=${b//is a function/}; [ $? = 0 ] && return 1 || return 0; }
qneill

@qneill Tôi đã thực hiện một số thử nghiệm sâu rộng hơn trong câu trả lời của mình ,
jarno

PIPE là yếu tố chậm nhất. Thử nghiệm này không so sánh typedeclare. Nó so sánh type | grepvới declare. Đây là một sự khác biệt lớn.
kyb

7

Nó sử dụng 'khai báo' để kiểm tra mã đầu ra hoặc mã thoát.

Kiểu đầu ra:

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

Sử dụng:

isFunction some_name && echo yes || echo no

Tuy nhiên, nếu bộ nhớ phục vụ, việc chuyển hướng sang null nhanh hơn thay thế đầu ra (nói về, phương thức `cmd` khủng khiếp và lỗi thời nên được sử dụng và thay vào đó sử dụng $ (cmd).) Và vì khai báo trả về true / false nếu tìm thấy / không tìm thấy và các hàm trả về mã thoát của lệnh cuối cùng trong hàm nên thường không cần trả về rõ ràng và vì việc kiểm tra mã lỗi nhanh hơn kiểm tra giá trị chuỗi (thậm chí là chuỗi null):

Thoát kiểu trạng thái:

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

Đó có lẽ là về cô đọng và lành tính như bạn có thể nhận được.


3
Để sử dụng cô đọng tối đaisFunction() { declare -F "$1"; } >&-
Neil

3
isFunction() { declare -F -- "$@" >/dev/null; }là đề nghị của tôi. Nó cũng hoạt động trên một danh sách các tên (chỉ thành công nếu tất cả đều là hàm), không có vấn đề gì với các tên bắt đầu -và, ở phía tôi ( bash4.2.25), declareluôn bị lỗi khi đóng đầu ra >&-, vì nó không thể ghi tên để xuất hiện trong trường hợp đó
Tino

Và xin lưu ý rằng, echođôi khi có thể thất bại với "cuộc gọi hệ thống bị gián đoạn" trên một số nền tảng. Trong trường hợp đó, "check && echo yes | | echo no" vẫn có thể xuất nonếu checkđúng.
Tino

7

Thử nghiệm các giải pháp khác nhau:

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

đầu ra, ví dụ:

test_declare (f là hàm)

người dùng thực 0m0,055s 0m0,041s sys 0m0.004s mã thoát 0

test_declare2 (f là hàm)

người dùng thực 0m0,042s 0m0,022s sys 0m0,017s mã thoát 0

test_type (f là hàm)

thực 0m2.200s người dùng 0m1.619s sys 0m1.008s mã thoát 0

test_type2 (f là hàm)

người dùng thực 0m0,746s 0m0,534s sys 0m0,237s mã thoát 0

test_declare (chưa đặt)

người dùng thực 0m0,040s 0m0,029s sys 0m0,010s mã thoát 1

test_declare2 (chưa đặt)

người dùng thực 0m0,038s 0m0,038s sys 0m0,000s mã thoát 1

test_type (không đặt)

thực 0m2,438s người dùng 0m1,678s sys 0m1,045s mã thoát 1

test_type2 (chưa đặt)

người dùng thực 0m0,805s 0m0,541s sys 0m0,274s mã thoát 1

test_declare (f là chuỗi)

người dùng thực 0m0,043s 0m0,034s sys 0m0.007s mã thoát 1

test_declare2 (f là chuỗi)

người dùng thực 0m0,039s 0m0,035s sys 0m0,003s mã thoát 1

test_type (f là chuỗi)

thực 0m2.394s người dùng 0m1.679s sys 0m1.035s mã thoát 1

test_type2 (f là chuỗi)

người dùng thực 0m0,851s 0m0,554s sys 0m0,294s mã thoát 1

Vì vậy, declare -F fdường như là giải pháp tốt nhất.


Chú ý ở đây: declare -F fkhông trả về giá trị khác không nếu f không tồn tại trên zsh, nhưng bash có. Hãy cẩn thận khi sử dụng nó. declare -f f, mặt khác, hoạt động như mong đợi gắn định nghĩa của hàm trên thiết bị xuất chuẩn (có thể gây khó chịu ...)
Manoel Vilela

1
Bạn đã thử test_type3 () { [[ $(type -t f) = function ]] ; }có một chi phí cận biên khi xác định var cục bộ (mặc dù <10%)
Oliver

4

Từ nhận xét của tôi về một câu trả lời khác (mà tôi vẫn thiếu khi quay lại trang này)

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

cập nhật

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

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

2

Tôi sẽ cải thiện nó để:

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

Và sử dụng nó như thế này:

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

2

Điều này cho bạn biết nếu nó tồn tại, nhưng đó không phải là một chức năng

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

2

Tôi đặc biệt thích giải pháp từ Grégory Joseph

Nhưng tôi đã sửa đổi nó một chút để vượt qua "cú đúp xấu xí":

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

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

0

Có thể sử dụng 'loại' mà không cần bất kỳ lệnh bên ngoài nào, nhưng bạn phải gọi nó hai lần, do đó, nó vẫn kết thúc chậm khoảng hai lần so với phiên bản 'khai báo':

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

Thêm vào đó, điều này không hoạt động trong POSIX sh, vì vậy nó hoàn toàn vô giá trị trừ những chuyện vặt vãnh!


test_type_nogrep () {a () {echo 'a';}; địa phương b = $ (loại a); c = $ {b // là một hàm /}; [$? = 0] && trả lại 1 || trả về 0; } - qneill
Alexx Roche
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.