LoginSignup
1
0

bashスクリプトでコマンドとして実行されてるのかsourceされてるのかを判定する

Last updated at Posted at 2023-06-07

結論

結論から書くと以下のコードで判定できます。

if [[ $BASH_SOURCE == "$0" ]]; then
  echo "スクリプトとして実行されている"
else
  echo "sourceされている"
fi

元記事

元は $FUNCNAME を使う方法を書いてたんですが、翌々考えたら [[ $BASH_SOURCE == "$0" ]] を使うほうが良いと思ったので結論を変えました。以下は元記事です。

スニペットコード

以下のコードで判定できます。

if (_(){ [[ ${FUNCNAME[*]} == *main ]];};_); then
  echo "スクリプトとして実行されている"
else
  echo "sourceされている"
fi

これは Python でいう if __name__ == '__main__' と同じような役割をするコードです。

※後述してますが、コンソールに↑このコードを貼り付けて試しても正常な判定にはなりません。あくまでスクリプトファイル内で利用する際のスニペットと思ってください。

判定式部分を注目する

# スクリプトがコマンドとして実行されているか?
(_(){ [[ ${FUNCNAME[*]} == *main ]]; };_) # $? が 0 ならコマンドとして実行されている
# スクリプトがsourceで読み込まれているか?
(_(){ [[ ${FUNCNAME[*]} == *source ]];};_) # $? が 0 ならsourceされている

# ↓これだけでも判定できるけど後から見た人が意味を理解できないからおすすめしない
(_(){ [[ ${FUNCNAME[*]} == *n ]]; };_) # $? が 0 ならコマンドとして実行されている
(_(){ [[ ${FUNCNAME[*]} == *e ]]; };_) # $? が 0 ならsourceされている

# 汎用的な関数として定義するならこんな感じ
is_sourced() { [[ ${FUNCNAME[*]} == *source ]]; }
is_executed() { [[ ${FUNCNAME[*]} == *main ]]; }
is_interactive() { ! is_sourced && ! is_executed; }

説明

  • $FUNCNAME は配列変数で、[0]に現在の関数名、[1] 以降には呼び出し元の関数名が内側から順に入ってくる。
  • 一番外側の関数名にはスクリプトの使われ方によって main または source が入ってくるのでコレで判定できる。
  • $FUNCNAME は関数内でしか使えないので最低一度は関数定義をする必要がある。
  • $FUNCNAME 以外で判定する方法は多分無い。→ $BASH_SOURCE$0の比較で十分でした。
  • 丸括弧で囲ってサブシェル内で実行しているのは関数定義で名前空間を汚さないため。
  • 判定式について、ロジック的には ${FUNCNAME[${#FUNCNAME[@]}-1]}main または source と等しいかで判定するべきだが、${FUNCNAME[*]} として1つの文字列に連結した値の末尾一致でも判定可能なのでこうしてる。見た目の複雑さも小さめで読みやすいと思う。
  • bash-4.2以降では配列のインデックスに負数を与えると後ろから取れるようになったので ${FUNCNAME[-1]} と書けるけど、古い bash 1 だとエラーになるので避けた。

例外について

お気づきかもしれないが、汎用的な関数として定義した時に突然 is_interactive というのが登場している。

実は bash には sourcemain 以外でコードが実行されるケースがあるのだ。もったいぶってるけど実は一番馴染み深い使われ方で、要はインタラクティブなシェルとして使われている場合は「今実行されているコードはそもそもスクリプトじゃない」という状態であり、この場合 $FUNCNAME の一番外側が定義されない状態、すなわち $FUNCNAME は自身の関数名が1つはいっているだけのサイズ1の配列になるようだ。

そのため第3の状態を判定する is_interactive という関数も欲しいよねってことで追加したワケです。

※2023-06-09追記: [[ $BASH_SOURCE == "$0" ]] を使ったほうがインタラクティブシェルで source した場合も、source されたと判定できるのでそっちを使った方が良いです。

関数にする場合は関数名に注意

ちなみに、インタラクティブな利用を想定した場合は関数名に気をつけましょう。具体的には mainsource で終わる名前をつけてはいけません。

実は最初は is_main()is_source() という関数名にしてたんだけど、${FUNCNAME[*]} を1つの文字列にjoinして末尾で判定というロジックだとインタラクティブ時は大外の関数名が自分自身になっちゃうことから、is_main という関数名だと ${FUNCNAME[*]}is_main となってしまい、[[ is_main == *main ]] という式は常に真になっちゃってsouceとmainの区別も付かなくて駄目じゃんw というバグに遭遇します。

その結果、末尾をごまかすために is_sourced()is_executed() という名前に変えて記事にしてみたという経緯があります。

  1. 例えば macOS の /bin/bash は 2023-06-09 時点で未だに 3.2.57 です。

1
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0