[解決済み] シェルスクリプトのデザインパターンやベストプラクティス [終了しました]。
質問事項
シェルスクリプト(sh、bashなど)のベストプラクティスやデザインパターンについて書かれたリソースをご存知の方はいらっしゃいますか?
どのように解決するのですか?
私はかなり複雑なシェルスクリプトを書きましたが、最初の提案は「しない」です。その理由は、小さなミスを犯すとスクリプトに支障をきたしたり、危険な状態に陥ったりすることがかなりあるからです。
とはいえ、私の個人的な経験以外に渡せる資料がないのですが。 以下は、私が普段行っていることで、やりすぎではありますが、堅実である傾向があります。 非常に を冗長にしています。
インヴォケーション
オプションの解析にはgetoptとgetoptsという2つのコマンドがあるので、注意してください。getoptを使うとトラブルが少ないです。
CommandLineOptions__config_file=""
CommandLineOptions__debug_level=""
getopt_results=`getopt -s bash -o c:d:: --long config_file:,debug_level:: -- "$@"`
if test $? != 0
then
echo "unrecognized option"
exit 1
fi
eval set -- "$getopt_results"
while true
do
case "$1" in
--config_file)
CommandLineOptions__config_file="$2";
shift 2;
;;
--debug_level)
CommandLineOptions__debug_level="$2";
shift 2;
;;
--)
shift
break
;;
*)
echo "$0: unparseable option $1"
EXCEPTION=$Main__ParameterException
EXCEPTION_MSG="unparseable option $1"
exit 1
;;
esac
done
if test "x$CommandLineOptions__config_file" == "x"
then
echo "$0: missing config_file parameter"
EXCEPTION=$Main__ParameterException
EXCEPTION_MSG="missing config_file parameter"
exit 1
fi
もう一つの重要な点は、プログラムが正常に終了した場合は常に0を返し、何か問題があった場合は0以外を返すことです。
関数の呼び出し
bashでは関数を呼び出すことができますが、呼び出す前に関数を定義することを忘れないでください。関数はスクリプトのようなもので、数値しか返すことができません。つまり、文字列の値を返すには別の方法を考えなければならないのです。私の戦略は、RESULTという変数を使って結果を保存し、関数がきれいに完了したら0を返すというものです。 また、0以外の値を返す場合は例外を発生させ、2つの例外変数(私の場合はEXCEPTIONとEXCEPTION_MSG)を設定し、1つ目には例外の種類、2つ目には人間が読みやすいメッセージを入れます。
関数を呼び出すと、関数のパラメータが$0, $1 などの特殊な変数に代入されます。関数内の変数は、ローカル変数として宣言することをお勧めします。
function foo {
local bar="$0"
}
エラーが発生しやすい状況
bashでは、特に宣言しない限り、設定されていない変数は空文字列として使用されます。これはタイプミスの場合に非常に危険で、タイプミスのあった変数は報告されず、空文字として評価されることになります。
set -o nounset
を使えば、このような事態を防ぐことができます。しかし、このようなことをすると、未定義の変数を評価するたびにプログラムが異常終了してしまうので、注意が必要です。このため、変数が未定義かどうかを調べるには、次のような方法しかありません。
if test "x${foo:-notset}" == "xnotset"
then
echo "foo not set"
fi
変数をreadonlyとして宣言することができます。
readonly readonly_var="foo"
モジュール化
以下のコードを使用すると、"python like" モジュール化を実現することができます。
set -o nounset
function getScriptAbsoluteDir {
# @description used to get the script path
# @param $1 the script $0 parameter
local script_invoke_path="$1"
local cwd=`pwd`
# absolute path ? if so, the first character is a /
if test "x${script_invoke_path:0:1}" = 'x/'
then
RESULT=`dirname "$script_invoke_path"`
else
RESULT=`dirname "$cwd/$script_invoke_path"`
fi
}
script_invoke_path="$0"
script_name=`basename "$0"`
getScriptAbsoluteDir "$script_invoke_path"
script_absolute_dir=$RESULT
function import() {
# @description importer routine to get external functionality.
# @description the first location searched is the script directory.
# @description if not found, search the module in the paths contained in $SHELL_LIBRARY_PATH environment variable
# @param $1 the .shinc file to import, without .shinc extension
module=$1
if test "x$module" == "x"
then
echo "$script_name : Unable to import unspecified module. Dying."
exit 1
fi
if test "x${script_absolute_dir:-notset}" == "xnotset"
then
echo "$script_name : Undefined script absolute dir. Did you remove getScriptAbsoluteDir? Dying."
exit 1
fi
if test "x$script_absolute_dir" == "x"
then
echo "$script_name : empty script path. Dying."
exit 1
fi
if test -e "$script_absolute_dir/$module.shinc"
then
# import from script directory
. "$script_absolute_dir/$module.shinc"
elif test "x${SHELL_LIBRARY_PATH:-notset}" != "xnotset"
then
# import from the shell script library path
# save the separator and use the ':' instead
local saved_IFS="$IFS"
IFS=':'
for path in $SHELL_LIBRARY_PATH
do
if test -e "$path/$module.shinc"
then
. "$path/$module.shinc"
return
fi
done
# restore the standard separator
IFS="$saved_IFS"
fi
echo "$script_name : Unable to find module $module."
exit 1
}
とすると、拡張子が .shinc のファイルを次の構文でインポートできます。
インポート "AModule/ModuleFile"
SHELL_LIBRARY_PATHで検索されます。グローバルな名前空間で常にインポートするように、すべての関数と変数に適切な接頭辞を付けることを忘れないでください、さもなければ名前の衝突の危険があります。私はダブルアンダースコアをpythonのドットとして使用しています。
また、モジュールの最初に次のように記述してください。
# avoid double inclusion
if test "${BashInclude__imported+defined}" == "defined"
then
return 0
fi
BashInclude__imported=1
オブジェクト指向プログラミング
bashでは、オブジェクトの割り当てについてかなり複雑なシステムを構築しない限り、オブジェクト指向プログラミングはできません(私はそれを考えました。実現可能ですが、非常識です)。 しかし、実際には、quot;シングルトン指向プログラミング"を行うことができます:各オブジェクトのインスタンスを1つだけ持ちます。
私がやっていることは、オブジェクトをモジュールに定義することです(モジュール化のエントリを参照)。それから、この例のコードのように、空の変数(メンバー変数に似ている)、init関数(コンストラクター)、メンバー関数を定義します。
# avoid double inclusion
if test "${Table__imported+defined}" == "defined"
then
return 0
fi
Table__imported=1
readonly Table__NoException=""
readonly Table__ParameterException="Table__ParameterException"
readonly Table__MySqlException="Table__MySqlException"
readonly Table__NotInitializedException="Table__NotInitializedException"
readonly Table__AlreadyInitializedException="Table__AlreadyInitializedException"
# an example for module enum constants, used in the mysql table, in this case
readonly Table__GENDER_MALE="GENDER_MALE"
readonly Table__GENDER_FEMALE="GENDER_FEMALE"
# private: prefixed with p_ (a bash variable cannot start with _)
p_Table__mysql_exec="" # will contain the executed mysql command
p_Table__initialized=0
function Table__init {
# @description init the module with the database parameters
# @param $1 the mysql config file
# @exception Table__NoException, Table__ParameterException
EXCEPTION=""
EXCEPTION_MSG=""
EXCEPTION_FUNC=""
RESULT=""
if test $p_Table__initialized -ne 0
then
EXCEPTION=$Table__AlreadyInitializedException
EXCEPTION_MSG="module already initialized"
EXCEPTION_FUNC="$FUNCNAME"
return 1
fi
local config_file="$1"
# yes, I am aware that I could put default parameters and other niceties, but I am lazy today
if test "x$config_file" = "x"; then
EXCEPTION=$Table__ParameterException
EXCEPTION_MSG="missing parameter config file"
EXCEPTION_FUNC="$FUNCNAME"
return 1
fi
p_Table__mysql_exec="mysql --defaults-file=$config_file --silent --skip-column-names -e "
# mark the module as initialized
p_Table__initialized=1
EXCEPTION=$Table__NoException
EXCEPTION_MSG=""
EXCEPTION_FUNC=""
return 0
}
function Table__getName() {
# @description gets the name of the person
# @param $1 the row identifier
# @result the name
EXCEPTION=""
EXCEPTION_MSG=""
EXCEPTION_FUNC=""
RESULT=""
if test $p_Table__initialized -eq 0
then
EXCEPTION=$Table__NotInitializedException
EXCEPTION_MSG="module not initialized"
EXCEPTION_FUNC="$FUNCNAME"
return 1
fi
id=$1
if test "x$id" = "x"; then
EXCEPTION=$Table__ParameterException
EXCEPTION_MSG="missing parameter identifier"
EXCEPTION_FUNC="$FUNCNAME"
return 1
fi
local name=`$p_Table__mysql_exec "SELECT name FROM table WHERE id = '$id'"`
if test $? != 0 ; then
EXCEPTION=$Table__MySqlException
EXCEPTION_MSG="unable to perform select"
EXCEPTION_FUNC="$FUNCNAME"
return 1
fi
RESULT=$name
EXCEPTION=$Table__NoException
EXCEPTION_MSG=""
EXCEPTION_FUNC=""
return 0
}
シグナルのトラッピングとハンドリング
例外をキャッチして処理するのに便利だと思いました。
function Main__interruptHandler() {
# @description signal handler for SIGINT
echo "SIGINT caught"
exit
}
function Main__terminationHandler() {
# @description signal handler for SIGTERM
echo "SIGTERM caught"
exit
}
function Main__exitHandler() {
# @description signal handler for end of the program (clean or unclean).
# probably redundant call, we already call the cleanup in main.
exit
}
trap Main__interruptHandler INT
trap Main__terminationHandler TERM
trap Main__exitHandler EXIT
function Main__main() {
# body
}
# catch signals and exit
trap exit INT TERM EXIT
Main__main "$@"
ヒント
何らかの理由でうまくいかない場合、コードの順番を変えてみてください。順序は重要であり、必ずしも直感的に理解できるものではありません。
tcshは関数をサポートしていませんし、一般に恐ろしいものです。
お役に立てれば幸いです。ただし、ご注意ください。もし、ここに書いたようなことをしなければならないのなら、それはあなたの問題がシェルで解決するには複雑すぎるということです。私は人的要因やレガシーの関係で使わざるを得ませんでした。
関連
-
[解決済み] シングルトン・パターンの欠点やデメリットは何ですか?[クローズド]
-
[解決済み] LinuxのシェルスクリプトでYes/No/Cancelの入力を促すにはどうしたらいいですか?
-
[解決済み] シェルスクリプトで部分文字列を別の文字列に置き換える
-
[解決済み] シェルスクリプトにプログレスバーを追加するには?
-
[解決済み】関数型プログラミングはGoFデザインパターンに取って代わるか?
-
[解決済み】bashで1つのコマンドでシェル変数にデフォルト値を割り当てる。
-
[解決済み】リレーショナルデータベースデザインパターン?[クローズド]
-
[解決済み】REST APIのログインパターン
-
[解決済み] Observerデザインパターンと "Listeners "の比較
-
[解決済み] ServiceLocatorはアンチパターンか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】FactoryとAbstract Factoryのデザインパターンの基本的な違いは何ですか?[クローズド]
-
[解決済み】インターフェイスを作成するタイミングはどのように判断するのでしょうか?
-
[解決済み】ファサードデザインパターンとは何ですか?
-
[解決済み] シェルスクリプトのデザインパターンやベストプラクティス [終了しました]。
-
[解決済み] Facade、Proxy、Adapter、Decoratorのデザインパターンの違い?
-
[解決済み] クロスカットの懸念事項の例
-
[解決済み] デザインパターンとアーキテクチャパターンの違いは何ですか?
-
[解決済み] ソーシャルネットワークにアクティビティストリームを実装する方法
-
[解決済み] デザインパターン 抽象ファクトリーとファクトリーメソッド
-
[解決済み] FacadeパターンとAdapterパターンの違いは何ですか?