環境変数を定義するスクリプトを実行する

exportを使用して環境変数を定義するスクリプトを呼び出すときにスクリプト自体を実行するとサブシェルで実行されるため呼び出し元には環境変数が定義されない。

$ unset TEST
$ cat << EOS > script.sh
#!/bin/sh
export TEST=1
env | grep TEST || echo NOT FOUND
EOS
$ chmod 755 script.sh
$ ./script.sh
TEST=1
$ env | grep TEST || echo NOT FOUND
NOT FOUND

source.で読み込むと呼び出し元のシェルで実行されるため呼び出し元に環境変数が定義される。

$ . ./script.sh
TEST=1
$ env | grep TEST || echo NOT FOUND
TEST=1

コマンドで読み込まれたかどうかを判定するのは難しい

How to detect if a script is being sourced で議論されているがシェルの種類によって判定方法が異なり、汎用的な方法は存在しない。

スクリプトを実行したときはメッセージを表示する

Shebangにsedを指定することで直接実行した場合はメッセージを表示することができる。ただし、bash script.shのように新しいシェルを実行した場合はそのまま実行される。

$ unset TEST
$ cat << EOS > script.sh
#!/usr/bin/sed 2,5!d;s/^\x23//;s/^\x20//
# This script must be sourced from within a shell
#
# Usage:
#   . script.sh
export TEST=1
env | grep TEST || echo NOT FOUND
EOS
$ chmod 755 script.sh
$ ./script.sh
This script must be sourced from within a shell

Usage:
  . script.sh
$ . ./script.sh
TEST=1
$ env | grep TEST || echo NOT FOUND
TEST=1

また、引数を渡すとsedに全て渡されてしまうため正しく処理されない。

./script.sh argument1 argument2
This script must be sourced from within a shell

Usage:
  . script.sh
sed: argument1: No such file or directory
sed: argument2: No such file or directory

BashとZshで実行されたときのみチェックする

macOSで使われるシェルは主にBashとZshのためこの2つだけチェックを行う。

$ unset TEST
$ cat << EOS > script.sh
#!/bin/bash
if [[ "\$BASH_SOURCE" = "\$0" || ! "\$ZSH_EVAL_CONTEXT" =~ :file\$ ]] ; then
  echo "This script must be sourced from within a shell"
  echo ""
  echo "Usage:"
  echo "  . script.sh"
  exit 1
fi
export TEST=1
env | grep TEST || echo NOT FOUND
EOS
$ chmod 755 script.sh
$ ./script.sh
This script must be sourced from within a shell

Usage:
  . script.sh
$ bash ./script.sh
This script must be sourced from within a shell

Usage:
  . script.sh
$ zsh ./script.sh
This script must be sourced from within a shell

Usage:
  . script.sh
$ . ./script.sh
TEST=1
$ env | grep TEST || echo NOT FOUND
TEST=1