メニュー

sourceとshの違い、sourceコマンドを使用するときの注意点

趣味で作った「Open JTalkを使ったローカル用mp3音声作成ツール」で、2回目の処理から「read-only variable」が表示されるようになった。

実際はコード側に問題があるのではなく、「sh」ではなくうっかり「source」を実行してしまったことが原因だった。

「source」から実行することで影響を受けたのは「readonly」だけでなかった。
sourceからファイルを実行したあとに続けてコマンドを打つと、ファイルに書かれていた「set -eu -o pipefail」や「trap ‘source alert-error.sh $do_error_text’ ERR」までもが実行された。

原因と、原因解明の参考記事

「source」からファイル実行してしまうと、現在開いているシェルから実行するため、その後の実行にも影響を受ける。

そのため、今回見られたいくつかのエラーの原因は、以下の通りと想定する。

  • 「source」を使って2回同じファイル実行すると、「read-only variable」というエラーが出る
    →ファイル内に書かれた変数が再定義されてしまうため(readonlyは読み取り専用、通常は編集不可)
    →ただし、1回目に「source」を使用し、2回目以降「sh」を使う場合は影響を受けない。「sh」で起動する新しいシェルは、「source」から設定した内容を引き継がないため、再定義とはならない。
  • 「source」を使ってファイル実行したあと、source以外のコマンドを打っても強制終了してしまう
    →実行ファイルに書かれていた「set -eu -o pipefail」が、現在のシェルの設定に影響を受けているため。
  • 「source」を使ってファイル実行したあと、source以外のコマンドを打っても「trap ‘source alert-error.sh $do_error_text’ ERR」が実行されてしまう
    →「set -eu -o pipefail」と同じく、現在のシェルの設定に影響を受けているため。

実行ファイル内の「source」の扱いについて

今回の実行ファイルは、内部で他のスクリプトファイルを「source」から実行している。
試しに、内部で動かすファイルの中に「readonly」を付けた変数を入れてテストしてみると、特にエラーはなかった。

内部処理で「source」を実行する場合、同一プロセスと扱われる。そのため、内部処理側の「source」で設定することは、シェル画面から使用した「sh」が影響を受ける。

function fn_do_run() {
  # 内部で動かす実行ファイル
  local -r do_files=("output.sh" "output-delete.sh")

  # アラート用)装飾
  local -r separate_start='*--*--*--*--* START *--*--*--*--*'
  local -r separate_end='*--*--*--*--*  END  *--*--*--*--*'

  echo "the selected file:\n${MYPATH}/target/${SETNAME}\n\n\n${separate_start}\n\n"

  for do_file in "${do_files[@]}"; do
    source $do_file $SETNAME
    source alert-success.sh $(basename $do_file)
  done

  echo "\n$separate_end\n\n"
}
fn_do_run

今回開発したツールだと、run.shを動かすときにoutput.shとoutput-delete.shを実行する。
output.shとoutput-delete.shの中に試しに「readonly HOGE=’aaa’」を入れてみると、output.shの後に実行するoutput-delete.shで「read-only variable」のエラーが出た。内部処理を使用するときには、変数の定義の仕方に気を付けた方がよさそう。