関数がbashに存在するかどうかを判別する


187

現在、bashから実行される単体テストをいくつか行っています。ユニットテストは、bashスクリプトで初期化、実行、クリーンアップされます。このスクリプトには、通常、init()、execute()、およびcleanup()関数が含まれています。ただし、必須ではありません。定義されているかどうかをテストしたいと思います。

私は以前ソースを手探りで落ち着かせることでこれを行いましたが、それは間違っているようでした。これを行うためのよりエレガントな方法はありますか?

編集:次のスニペットは魅力のように機能します:

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

ありがとう。これを使用して、シェルライブラリを読み込むときに関数のスタブされたバージョンを条件付きで定義しました。fn_exists foo || foo() { :; }
ハーベイ

2
type -tおよびを使用してgrepを保存できます==
Roland Weber

ロケールが英語以外の場合は機能しません。フィンランドのロケールを使用してtype test_functionいるtest_function on funktio.ときとist eine Funktionドイツ語を使用しているとき。
Kimmo Lehto

3
英語以外のロケールLC_ALL=Cでのresque
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

119
type -t $function食事券です。
アラン風

4
回答として投稿しなかったのはなぜですか。:-)
ターミナル

最初にdeclareを使用して自分の回答を投稿していたからです:-)
Allan Wind

5
type [-t]何が何であるかを伝えるのは良いことですが、何かが関数であるかどうかをテストする場合、パイプを使ってgrepに戻るか、バックティックを使用する必要があるため、どちらもサブプロセスを生成します。
Lloeki 2013

1
私が誤解しない限り、typeを使用すると、一致するファイルがあるかどうかを確認するために、最小限のアクセスを実行する必要があります。@Lloeki、あなたはまったく正しいですが、それは最小限の出力を生成するオプションであり、それでもエラーレベルを使用できます。あなたはサブプロセスなしで結果を得ることができます、例えばtype -t realpath > /dev/shm/tmpfile ; read < /dev/shm/tmpfile(悪い例)。ただし、ディスクIOが0であるため、declareが最良の答えです。
Orwellophile 2013

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

1
私にとっては素晴らしい仕事でした。特に私のシェルにはtypeの-tフラグがないので(タイプ "$ command"で多くの問題を抱えていました)
Dennis

2
実際、これはzsh(rcスクリプトに便利)でも機能し、タイプをgrepする必要はありません。
Lloeki 2013

2
@DennisHodappは必要ありませんtype -t。代わりに終了ステータスを使用できます。私は長い間、自分がtype program_name > /dev/null 2>&1 && program_name arguments || echo "error"何かを呼び出すことができるかどうかを確認するために使用しました。もちろん、type -t上記のメソッドでは、「呼び出し可能」かどうかだけでなく、タイプを検出することもできます。
0xC0000022L 2015

@ 0xC0000022L program_nameが関数でない場合はどうなりますか?
David Winiecki

2
@ 0xC0000022L終了ステータスを使用しても、program_nameが関数であるかどうかを知らせていなかったのですが、「当然、タイプ-tと上記の方法でもタイプを検出できるので、 「呼び出し可能」かどうかだけではありません。」ごめんなさい。
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

または、あなた自身の無意味な楽しみのために、2つを組み合わせてください。どちらも機能します。

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」オプションは冗長です。
ラジッシュ2013

3
この-Fオプションはzshには存在しません(移植性に役立ちます)
Lloeki

-Fまた、実際には必要ありません。関数の定義/本体のみを抑制しているようです。
2016

1
@blueyed必要ではないかもしれませんが、それは非常に望ましいことです。コンテンツ全体をリストするのではなく、関数が存在することを確認しようとしています(これはやや非効率的です)。を使用してファイルが存在するcat "$fn" | wc -cかどうかを確認しますか?zshに関しては、もしbashタグがあなたの手がかりにならないなら、おそらく質問自体が持っているべきです。「関数がbashに存在するかどうかを判断する」。さらに指摘し-Fておきますが、オプションはzshには存在しませんが、エラーも発生しません。したがって、-fと-Fの両方を使用すると、zshbashの両方でチェックを成功させることができます。 。
Orwellophile 2016

@Orwellophile -Fはzshで浮動小数点数に使用されます。なぜ-Fbashで使用すると改善されるのかわかりません!?私はdeclare -fbashでも同じように機能するという印象を受けました(戻りコードに関して)。
2016

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のジョセフ・

4
あなたはbashを使用しています、[[...]]代わりに使用して[...]、引用ハックを取り除いてください。また、遅いバッククォートフォークです。declare -f $1 > /dev/null代わりに使用してください。
Lloeki 2013

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

declareはより速く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私は自分の回答でやや広範なテストを行いました
jarno '19年

PIPEは最も遅い要素です。このテストは比較にならないtypedeclare。と比較type | grepdeclareます。これは大きな違いです。
kyb

7

つまり、「declare」を使用して、出力または終了コードをチェックします。

出力スタイル:

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

使用法:

isFunction some_name && echo yes || echo no

ただし、メモリが機能する場合、nullへのリダイレクトは出力置換よりも高速です(いわば、恐ろしく時代遅れの `cmd`メソッドを削除して、代わりに$(cmd)を使用する必要があります)。見つからず、関数は関数の最後のコマンドの終了コードを返すため、通常、明示的な戻りは必要ありません。エラーコードをチェックする方が、文字列値(null文字列でも)をチェックするよりも速いためです。

終了ステータススタイル:

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

それはおそらくあなたが得ることができるのと同じくらい簡潔で良性です。


3
最大簡潔使用するためにisFunction() { declare -F "$1"; } >&-
ニール・

3
isFunction() { declare -F -- "$@" >/dev/null; }私の推薦です。名前のリストでも機能し(すべてが関数である場合にのみ成功します)、で始まる名前には問題がなく-、私の側(bash4.2.25)では、declare出力をで閉じると常に失敗します。>&-名前を書き込めないためです。その場合stdoutへ
Tino

またecho、一部のプラットフォームでは、「システムコールの中断」で失敗する場合があることに注意してください。その場合、trueでnoあれば「check && echo yes || echo no」が出力されcheckます。
ティノ2016年

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(設定解除)

実際の0m0,038sユーザー0m0,038s sys 0m0,000s終了コード1

test_type(設定解除)

実際0m2,438sユーザー0m1,678s sys 0m1,045s終了コード1

test_type2(f unset)

実際の0m0,805sユーザー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終了コード1

test_type2(fは文字列)

実際の0m0,851sユーザー0m0,554s sys 0m0,294s終了コード1

だからdeclare -F f最良の解決策のようです。


ここでの注意:declare -F ffがzshに存在しない場合はゼロ以外の値を返しませんが、はいbashを返します。使用には注意してください。declare -f f、別の手で、標準出力の関数の定義をアタッチして期待どおりに機能します(これは煩わしいかもしれません...)
Manoel Vilela '30

1
test_type3 () { [[ $(type -t f) = function ]] ; }ローカル変数の定義にわずかなコストがかかりますか(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

私は特にグレゴリー・ジョセフのソリューションが好きでした

しかし、「二重引用符の醜いトリック」を克服するために少し修正しました。

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

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

0

外部コマンドなしで「type」を使用することは可能ですが、2回呼び出す必要があるため、「declare」バージョンの約2倍遅くなります。

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 = $(タイプa); c = $ {b // is a function /}; [$?= 0] && 1を返す|| 0を返します。} – qneill
Alexx Roche
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.