この質問は、重要であればBashの文脈で最も適していますが、言語間の解決策を期待しています。
私が経験している問題は、あるスクリプトを別のスクリプトに「インポート」した場合に発生しますが、これを再帰的に実行することです。問題はこのことを無限にするようだということだ。
具体的なケースは次のとおりです。同じディレクトリに別のbashスクリプトをロードするための単純なbashスクリプトを作成しました。私のプロジェクトでは、すべてのスクリプトが同じディレクトリにあるので、今動作します。
したがって、「スクリプトローダー」は次のようになります(_source_script.sh
次の機能を定義するファイル)。
_source_script()
{
# Define a few colors for the possible error messages below.
RED=$(tput setaf 1)
NORMAL=$(tput sgr0)
EXPECTED_DIR="scripts"
if [ "$#" -ge "1" ]
then
EXPECTED_DIR=$1
fi
# Based on: http://stackoverflow.com/a/1371283/3924118
CURRENT_DIR=${PWD##*/}
if [ "$CURRENT_DIR" = "$EXPECTED_DIR" ]
then
for (( arg = 2; arg <= $#; arg++ ))
do
# Based on:
# - http://askubuntu.com/questions/306851/how-to-import-a-variable-from-a-script
# - http://unix.stackexchange.com/questions/114300/whats-the-meaning-of-a-dot-before-a-command-in-shell
# - http://stackoverflow.com/questions/20094271/bash-using-dot-or-source-calling-another-script-what-is-difference
printf ". ./${!arg}.sh\n"
# Try to load script ${!arg}.sh
. ./${!arg}.sh
# If it was not loaded successfully, exit with status 1.
if [ $? -ne 0 ]
then
printf "${RED}Script '${!arg}.sh' not loaded successfully. Exiting...${NORMAL}\n"
exit 1
fi
done
else
printf "No script loaded: $CURRENT_DIR != $EXPECTED_DIR.\n"
exit 1
fi
}
この「スクリプトローダー」を使用するには、まず次のように他のスクリプトから「インポート」する必要があります。
. ./_source_script.sh
問題は、関数を介して別のスクリプトを含めようとしたときです_source_script
。たとえば、スクリプトで以下を実行しようとすると、次のようになりますsome_script.sh
。
_source_script scripts colors asserts clean_environment
永久に実行され続けます。
内部にあるcolors.sh
:
#!/usr/bin/env bash
# Colors used when printing.
export GREEN=$(tput setaf 2)
export RED=$(tput setaf 1)
export NORMAL=$(tput sgr0)
export YELLOW=$(tput setaf 3)
内部にあるasserts.sh
:
...
. ./_source_script.sh
_source_script scripts colors
その中にclean_environment.sh
私はまた次のものがあります:
. ./_source_script.sh
_source_script scripts colors
私の理解によると、これは何もロードしないか、ループを探すスクリプトが見つかるまで再帰的に実行する必要がありますが、そうではありません。
だから私の解決策は次のとおりですsome_script.sh
。
_source_script scripts colors
_source_script scripts asserts
_source_script scripts clean_environment
つまり、個別に実行します。
それでは、ループで複数のスクリプトを「インポート」できないのはなぜですか?
答え1
コードをあまり詳しく見ることなく、一般的なアプローチにはCの「ヘッダガード」に似たものが含まれます。
#ifndef HEADER_H
#define HEADER_H
/* The contents of the header file, with typedefs etc. */
#endif
このヘッダに関連するCプリプロセッサマクロはどこにHEADER_H
ありますか?ヘッダーがすでに含まれている場合は、ヘッダーを再度含めない場合にのみ使用されます。私は通常MCMC_H
、ヘッダーファイル自体から名前が派生したマクロを作成しますmcmc.h
。
シェルスクリプトでは、次のように簡単にできます。
if [ -z "$script_source_guard" ]; then
script_source_guard=1
# ... do things (define functions etc.)
fi
変数名はscript_source_guard
このファイルにのみ適用する必要があります。同様に、名前はソースファイル名から任意に派生できます。名前付きファイルには、myfuncts.shlib
名前付き保護変数があるか、別の名前を持つことができます。MYFUNCTS
_MYFUNCTS
guard_myfuncts
答え2
これは範囲の問題です。意図的に、関数は実行され、呼び出されると_source_script
実際に自分自身を再帰的に呼び出します。ただし、シェルスクリプトの変数はデフォルトでグローバルです。したがって、(スタック内で)同時にアクティブにされた複数の関数実装は同じです。. ./${!arg}.sh
_source_script
_source_script
arg
詳細:
いつasserts.sh
、またはclean_environment.sh
呼び出されますか?
_source_script scripts colors
関数は= 2_source_script
になります$#
。したがって、for (( arg = 2; arg <= $#; arg++ ))
ループは$arg
<= 2の間実行され、最終的に3になります。. asserts.sh
呼び出されたら にある$arg
ので既に 3 です。asserts
$3
_source_script scripts colors asserts clean_environment
コマンドはsome_script.sh
。$arg
置く 到着 3では、何もしないで元の状態にasserts.sh
戻ります。$arg
しかし、. clean_environment.sh
呼び出されたらにあるので$arg
4です。clean_environment
$4
_source_script scripts colors asserts clean_environment
注文する。したがって、in(呼び出し時に)が$arg
3に設定されている場合、(in)の最上位レベルの実装はループ内の位置を失い、戻って再実行されます。何度も何度も…clean_environment.sh
_source_script
_source_script
some_script.sh
for (( arg = 2; arg <= $#; arg++ ))
. clean_environment.sh
解決策
もちろん、解決策は単に local arg
関数に宣言を入れることです_source_script
。
スクリプトに関する追加の注:
次のような状況があります。
- さまざまなもの(定数、関数、エイリアスなど)を定義するファイルがあります。
- 他のファイルがあります使用上で定義したものは
.
(ソース)定義を含むファイル たとえば、一部のファイルは両方のグループに属し、呼び出し元にいくつかのリソースを提供できますが、そのファイルに定義されている値を取得するために読み取ることもできます
asserts.sh
。おそらく階層があるでしょう。clean_environment.sh
colors.sh
some_script.sh
着信電話colors.sh
some_script.sh
着信電話asserts.sh
asserts.sh
着信電話colors.sh
some_script.sh
着信電話clean_environment.sh
clean_environment.sh
着信電話colors.sh
など。
一部のユーザーは、シェルコマンドファイルが何度も処理されるのを防ぐために「ヘッダガード」を使用することをお勧めします。このアドバイスはあなたにとって有用で関連性がありますが、状態 (上記のように)それはあなたと何が関係しているのか本当にわかりません。質問。
- あなたがそれを使用する必要があることを除いて
_source_script.sh
それ自体。上記の階層では、次の事実を無視しました。ファイルごと着信電話
_source_script.sh
。これは、次の_source_script
機能を意味します。はい実行時にオーバーライド。 これは非常に危険に聞こえるかもしれません。あなたは言う
if [ "$#" -ge "1" ] then EXPECTED_DIR=$1 fi ︙
直接話すこともできます
if [ "$#" -le 1 ] then exit 1 # or maybe return 1 fi EXPECTED_DIR=$1 ︙
引数が 2 個未満であれば何もしないからです。
printf
必須ではない場合は、型文字列(最初の引数)に変数を使用しない方が安全です。だからprintf ". ./${!arg}.sh\n" printf "${RED}Script '${!arg}.sh' not loaded successfully. Exiting...${NORMAL}\n" printf "No script loaded: $CURRENT_DIR != $EXPECTED_DIR.\n"
しなければならない
printf ". ./%s.sh\n" "${!arg}" printf "%sScript '%s.sh' not loaded successfully. Exiting...%s\n" \ "$RED" "${!arg}" "$NORMAL" printf "No script loaded: %s != %s.\n" "$CURRENT_DIR" "$EXPECTED_DIR"
引用符をより頻繁に使用する必要があります。
. ./${!arg}.sh
しなければならない
. "./${!arg}.sh"
そして厳密に言えば、本当に
編集証的な安全を$?
維持するために"$?"
…"$?"
しかし、明示的にテストする代わりにif ! . "./${!arg}.sh" then printf "%sScript '%s.sh' not loaded successfully. Exiting...%s\n" \ "$RED" "${!arg}" "$NORMAL" exit 1 fi
?
参考にしてください
.
$?
成功したコマンドは、ファイルの最後のコマンドの終了状態に設定されます。したがって、ヘッダーファイル(.sh
使用する他のファイルを定義するファイル)が正しい終了ステータスを返すことに注意してください。
答え3
親ファイルを介して購入をリンクしたい場合でも問題ありません。私は関数にソースステートメントを入れません。親ファイルのみをインポートする場合は、すべての子ファイルもインポートする必要があります。
内部コマンドの bash のマニュアルページからsource
:
[It] ファイル名からコマンドを読み出して実行します。現在のシェル環境そして、filenameで実行された最後のコマンドの終了ステータスを返します。
現在、シェル環境が引き続き機能を実行していることを保証できない場合は、構成をより慎重に検討できます。