Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/design-patterns/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Design patterns shell脚本的设计模式或最佳实践_Design Patterns_Bash_Shell - Fatal编程技术网

Design patterns shell脚本的设计模式或最佳实践

Design patterns shell脚本的设计模式或最佳实践,design-patterns,bash,shell,Design Patterns,Bash,Shell,有人知道关于shell脚本(sh、bash等)的最佳实践或设计模式的任何资源吗?简单: 使用python而不是shell脚本。 您的可读性提高了近100倍,而无需将不需要的内容复杂化,并且保留了将脚本的一部分演变为函数、对象、持久对象(zodb)、分布式对象(pyro)的能力,几乎不需要任何额外的代码。或与Joao所说的类似的旧引用: “使用perl。您可能想了解bash,但不想使用它。” 遗憾的是,我忘了是谁说的 是的,这些天我推荐python而不是perl。使用set-e,这样您就不会在出错

有人知道关于shell脚本(sh、bash等)的最佳实践或设计模式的任何资源吗?

简单: 使用python而不是shell脚本。
您的可读性提高了近100倍,而无需将不需要的内容复杂化,并且保留了将脚本的一部分演变为函数、对象、持久对象(zodb)、分布式对象(pyro)的能力,几乎不需要任何额外的代码。

或与Joao所说的类似的旧引用:

“使用perl。您可能想了解bash,但不想使用它。”

遗憾的是,我忘了是谁说的


是的,这些天我推荐python而不是perl。

使用set-e,这样您就不会在出错后继续前进。如果您希望它在非linux上运行,请尝试使其与sh兼容,而不依赖bash。

了解shell脚本编写方面的许多智慧,不仅仅是bash

不要听别人叫你看其他可能更复杂的语言。如果shell脚本满足您的需要,请使用它。你想要的是功能,而不是幻想。新的语言为你的简历提供了有价值的新技能,但如果你有工作要做,而且你已经了解shell,这对你没有帮助


如上所述,shell脚本并没有很多“最佳实践”或“设计模式”。不同的用途有不同的指导方针和偏见——就像任何其他编程语言一样

今年(2008年)在OSCON上有一个很好的会议,主题是:

知道什么时候使用它。要将命令快速而肮脏地粘合在一起,没关系。如果您需要做的非琐碎的决策、循环或任何事情都不多,那么可以选择Python、Perl和模块化

shell最大的问题往往是最终结果看起来就像一个大泥球,4000行的重击和成长。。。你无法摆脱它,因为现在你的整个项目都依赖于它。当然,它从beautiful bash的40行开始。

要找到一些“最佳实践”,看看Linux发行版(例如Debian)是如何编写init脚本的(通常在/etc/init.d中找到)

它们中的大多数没有“bash-isms”,并且对配置设置、库文件和源格式有很好的分离

我的个人风格是编写一个主shell脚本,它定义一些默认变量,然后尝试加载(“源”)一个可能包含新值的配置文件

我尽量避免使用函数,因为它们会使脚本更加复杂。(Perl就是为此而创建的。)


要确保脚本可移植,请不仅使用#进行测试/bin/sh,但也要使用#/垃圾箱/灰烬,#/bin/dash等。您很快就会发现特定于Bash的代码。

shell脚本是一种设计用于操作文件和进程的语言。 虽然这很好,但它不是通用语言, 因此,请始终尝试从现有的实用程序中粘合逻辑,而不是 在shell脚本中重新创建新逻辑


除此之外,我还收集了一些。我编写了相当复杂的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。 此外,如果返回的值不同于零,则可以引发异常,然后设置两个“异常变量”(mine:exception和exception_MSG),第一个包含异常类型,第二个包含人类可读的消息

调用函数时,函数的参数被分配给特殊变量$0、$1等。我建议您将它们放入更有意义的名称中。将函数中的变量声明为局部变量:

function foo {
   local bar="$0"
}
容易出错的情况

在bash中,除非另有声明,否则unset变量将用作空字符串。在输入错误的情况下,这是非常危险的,因为类型错误的变量将不会被报告,并且它将被计算为空。使用

set -o nounset
以防止这种情况发生。但是要小心,因为如果这样做,每次计算未定义的变量时,程序都会中止。因此,检查是否未定义变量的唯一方法如下:

if test "x${foo:-notset}" == "xnotset"
then
    echo "foo not set"
fi
您可以将变量声明为只读:

readonly readonly_var="foo"
模块化

如果使用以下代码,可以实现“类似python”的模块化:

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_路径中搜索。由于总是在全局名称空间中导入,请记住在所有函数和变量前面加上适当的前缀,否则可能会发生名称冲突。我使用双下划线作为python点

另外,将此作为模块中的第一件事

# avoid double inclusion
if test "${BashInclude__imported+defined}" == "defined"
then
    return 0
fi
BashInclude__imported=1
面向对象编程

在bash中,除非构建一个相当复杂的对象分配系统,否则无法进行面向对象编程(我曾想过,这是可行的,但很疯狂)。 但在实践中,你可以做“单打”
# 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 "$@"