设计模式 – shell脚本的设计模式或最佳实践
有没有人知道任何讨论shell脚本(sh,bash等)的最佳实践或设计模式的资源?
我编写了相当复杂的shell脚本,我的第一个建议是“不要”。原因是很容易犯一个小错误,妨碍你的脚本,甚至使它变得危险。
也就是说,我没有其他资源可以通过你,但我个人的经验。 调用 让你的脚本接受多空选项。要小心,因为有两个命令来解析选项,getopt和getopts。使用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 另一个重要的一点是,如果成功完成,程序应始终返回零,如果出现问题则应为非零。 函数调用 您可以在bash中调用函数,只需记住在调用之前定义它们。函数类似于脚本,它们只能返回数值。这意味着您必须创建一个不同的策略来返回字符串值。我的策略是使用一个名为RESULT的变量来存储结果,如果函数干净地完成则返回0。 当你调用一个函数时,函数的参数被分配给特殊的vars $ 0,$ 1等。我建议你把它们放到更有意义的名字中。将函数内部的变量声明为local: function foo { local bar="$0" } 容易出错的情况 在bash中,除非另行声明,否则将unset变量用作空字符串。这在拼写错误的情况下非常危险,因为不会报告严重类型的变量,并且它将被评估为空。使用 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中,你不能做面向对象的编程,除非你构建一个非常复杂的对象分配系统(我想到了。它是可行的,但是疯了)。 我所做的是:我将一个对象定义为一个模块(参见模块化条目)。然后我定义空变量(类似于成员变量),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。它不支持功能,一般来说很糟糕。 希望它有所帮助,但请注意。如果你必须使用我在这里写的那些东西,那就意味着你的问题太复杂了,无法用shell解决。用另一种语言。由于人为因素和遗产,我不得不使用它。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |