趣味で作った「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」のエラーが出た。内部処理を使用するときには、変数の定義の仕方に気を付けた方がよさそう。