CLOVER🍀

That was when it all began.

Bashのビルトインコマンドdeclare/typeで関数の存在確認をする

これは、なにをしたくて書いたもの?

Bashで関数の存在確認をする方法を知りたい、ということで。

あくまで対象とするのは「関数」です。

declare -F or type?

Bashのドキュメントのシェル関数の記載を見てみましょう。

Bash Features / Basic Shell Features / Shell Functions

シェル関数は通常のコマンドと同じように実行されます。

They are executed just like a "regular" simple command.

ただ、新しいプロセスが作られるわけではないのが違いですね。

there is no new process created to interpret them.

これを見ると、関数の確認にはdeclareコマンド(一覧は-fオプション、定義は-Fオプション)かtypesetコマンドを使うのが
よさそうです。

The -f option to the declare (typeset) builtin command (see Bash Builtin Commands) lists function names and definitions. The -F option to declare or typeset lists the function names only (and optionally the source file and line number, if the extdebug shell option is enabled).

ちなみにtypesetはdeclareのシノニムということになっています。

The typeset command is supplied for compatibility with the Korn shell. It is a synonym for the declare builtin command.

Bash Features / Shell Builtin Commands / Bash Builtin Commands / typeset

というわけで、今回のお題にはdeclare、それからtypeを使うのがよさそうです。

ちなみに関数の削除はunsetの-fオプションです。

Functions may be exported so that child shell processes (those created when executing a separate shell invocation) automatically have them defined with the -f option to the export builtin (see Bourne Shell Builtins). The -f option to the unset builtin (see Bourne Shell Builtins) deletes a function definition.

少し試してみましょう。

環境

今回の環境はこちら。

$ bash --version
GNU bash, バージョン 5.2.21(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2022 Free Software Foundation, Inc.
ライセンス GPLv3+: GNU GPL バージョン 3 またはそれ以降 <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.


$ apt search ^bats.*
ソート中... 完了
全文検索... 完了
bats/noble,noble,now 1.10.0-1 all [インストール済み]
  bash automated testing system

bats-assert/noble,noble,now 2.1.0-3 all [インストール済み]
  Helper library providing common assertions for Bats

bats-file/noble,noble,now 0.4.0-1 all [インストール済み]
  Helper library providing filesystem-related assertions for Bats

bats-support/noble,noble,now 0.3.0-4 all [インストール済み、自動]
  Supporting library to test bats helper libraries

確認してみる

こんなファイルを用意。

functions.bash

function hello() {
    echo 'Hello World!!'
}

現在のシェルに読み込ませます。

$ source functions.bash

確認。

$ hello
Hello World!!

declare -Fで確認してみます。

$ declare -F hello
hello


$ echo $?
0

なお、declare自体は変数宣言の組み込みコマンドで、-Fオプションをつけて関数名を指定することで、関数名と属性のみを
表示するオプションです。

通常は結果は不要なので、こう使うことになると思います。

$ declare -F hello > /dev/null

存在しない関数に対して実行するとなにも表示されませんし、終了ステータスは1になります。

$ declare -F not_exists_function


$ echo $?
1

typeはそのまま実行するとこうなります。

$ type hello
hello は関数です
hello ()
{
    echo 'Hello World!!'
}

-tオプションをつけることで、対象がどのようなものであるかを返してくれます。関数の場合はfunctionですね。

$ type -t hello
function


$ echo $?
0

終了ステータスは0です。

全部でaliaskeywordfunctionbuiltinfileがあります。

$ type -t echo
builtin


$ type -t gcc
file


$ type -t grep
alias


$ type -t if
keyword

対象がない場合は、declare -Fと似た挙動になります。

$ type -t not_exists_function


$ echo $?
1

if文で使うならこんな感じでしょうか。

if declare -F hello > /dev/null; then
    echo 'exists hello function'
fi

if ! declare -F non_exists_function > /dev/null; then
    echo 'non exists hello function'
fi

if type -t hello > /dev/null; then
    echo 'exists hello function'
fi

if ! type -t non_exists_function > /dev/null; then
    echo 'non exists hello function'
fi

Batsでテストも書いておきました。

test/functions.bats

setup() {
    bats_load_library bats-support
    bats_load_library bats-assert

    DIR=$(cd "$(dirname "$BATS_TEST_FILENAME")" > /dev/null 2>&1 && pwd)
    # shellcheck disable=SC1091
    source "${DIR}/../src/functions.bash"
}

@test "test declare -F for exists function" {
    run declare -F hello
    # 実際に使う場合
    # declare -F hello > /dev/null

    assert_success
    assert_output "hello"
}

@test "test declare -F for non exists function" {
    run declare -F non_exists_function

    assert_failure 1
    assert_output ""
}

@test "test type -t for exists function" {
    run type -t hello
    # 実際に使う場合
    # type -t hello > /dev/null

    assert_success
    assert_output "function"
}

@test "test type -t for non exists function" {
    run type -t non_exists_function

    assert_failure 1
    assert_output ""
}