zoukankan      html  css  js  c++  java
  • Shell:语法规范

    背景

    博客:https://www.cnblogs.com/Rohn

    使用哪一种Shell

    可执行文件必须以 #!/bin/bash 和最小数量的标志开始。请使用 set 来设置shell的选项,使得用 <script_name>调用你的脚本时不会破坏其功能。

    推荐使用:

    #!/usr/bin/env bash
    

    env一般固定在/usr/bin目录下,而其余解释器的安装位置就相对不那么固定。

    限制所有的可执行Shell脚本为bash使得我们安装在所有计算机中的shell语言保持一致性。

    无论你是为什么而编码,对此唯一例外的是当你被迫时可以不这么做的。其中一个例子是Solaris SVR4包,编写任何脚本都需要用纯Bourne shell

    [root@test ~]# echo $SHELL
    /bin/bash
    

    什么时候使用Shell

    使用Shell需要遵守的一些准则:

    • 如果你主要是在调用其他的工具并且做一些相对很小数据量的操作,那么使用Shell来完成任务是一种可接受的选择。
    • 如果你在乎性能,那么请选择其他工具,而不是使用Shell。
    • 如果你发现你需要使用数据而不是变量赋值(如 ${PHPESTATUS} ),那么你应该使用Python脚本。
    • 如果你将要编写的脚本会超过100行,那么你可能应该使用Python来编写,而不是Shell。

    请记住,当脚本行数增加,尽早使用另外一种语言重写你的脚本,以避免之后花更多的时间来重写。

    注释

    博客:https://www.cnblogs.com/Rohn

    Bash只支持单行注释,使用#开头的都被当作注释语句。

    顶层注释

    每个文件必须包含一个顶层注释,对其内容进行简要概述。版权声明和作者信息是可选的。
    例如:

    #!/usr/bin/env bash
    # Author: Rohn
    # Version: 1.0
    # Created Time: 2020/06/06
    # Perform hot backups of MySQL databases.
    
    • 第1行,指明解释器,使用bash

    #!叫做"Shebang"或者"Sha-bang"(Unix术语中,#号通常称为sharp,hash或mesh;而!则常常称为bang),指明了执行这个脚本文件的解释程序。当然,如果使用bash test.sh这样的命令来执行脚本,那么#!这一行将会被忽略掉。

    • 第2-5行,分别为作者、版本号、创建时间、功能说明。

    功能注释

    任何不是既明显又短的函数都必须被注释。任何库函数无论其长短和复杂性都必须被注释。

    其他人通过阅读注释(和帮助信息,如果有的话)就能够学会如何使用你的程序或库函数,而不需要阅读代码。

    所有的函数注释应该包含:

    • 函数的描述
    • 全局变量的使用和修改
    • 使用的参数说明
    • 返回值,而不是上一条命令运行后默认的退出状态

    例如:

    #!/usr/bin/env bash
    # Author: Rohn
    # Version: 1.0
    # Created Time: 2020/06/06
    # Perform hot backups of Oracle databases.
    
    export PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin'
    
    #######################################
    # Cleanup files from the backup dir
    # Globals:
    #   BACKUP_DIR
    #   ORACLE_SID
    # Arguments:
    #   None
    # Returns:
    #   None
    #######################################
    cleanup() {
      ...
    }
    

    TODO注释

    TODOs应该包含全部大写的字符串TODO,接着是括号中你的用户名。冒号是可选的。最好在TODO条目之后加上bug或者ticket的序号。

    例如:

    # TODO(mrmonkey): Handle the unlikely edge cases (bug ####)
    

    格式

    博客:https://www.cnblogs.com/Rohn

    缩进

    缩进两个空格,没有制表符。例如:

    if [ a > 1 ];then
      echo '${a} > 1'
    fi
    

    行的长度和长字符串

    行的最大长度为80个字符。例如:

    # DO use 'here document's
    cat <<END;
    I am an exceptionally long
    string.
    END
    
    # Embedded newlines are ok too
    long_string="I am an exceptionally
      long string."
    

    管道

    如果一行容不下整个管道操作,那么请将整个管道操作分割成每行一个管段。

    应该将整个管道操作分割成每行一个管段,管道操作的下一部分应该将管道符放在新行并且缩进2个空格。这适用于使用管道符|的合并命令链以及使用||&&的逻辑运算链。

    例如:

    # All fits on one line
    command1 | command2
    
    # Long commands
    command1 
      | command2 
      | command3 
      | command4
    

    循环

    if-else语句

    if; then放在同一行,;后空一格,else单独一行,fi单独一行,并与if垂直对齐。即:

    if condition; then
      statement(s)
    else
      statement(s)
    fi
    

    for-do和while-do语句

    while/for; do放在同一行,donewhile/for垂直对齐,即:

    # while structure
    while condition; do
      statement(s)
    done
    
    # for structure
    for condition; do
      statement(s)
    done
    

    例如:

    for dir in ${dirs_to_cleanup}; do
      if [[ -d "${dir}/${ORACLE_SID}" ]]; then
        log_date "Cleaning up old files in ${dir}/${ORACLE_SID}"
        rm "${dir}/${ORACLE_SID}/"*
        if [[ "$?" -ne 0 ]]; then
          error_message
        fi
      else
        mkdir -p "${dir}/${ORACLE_SID}"
        if [[ "$?" -ne 0 ]]; then
          error_message
        fi
      fi
    done
    

    case语句

    • 通过2个空格缩进可选项。
    • 在同一行可选项的模式右圆括号之后和结束符 ;;之前各需要一个空格。
    • 长可选项或者多命令可选项应该被拆分成多行,模式、操作和结束符;;在不同的行。

    匹配表达式比caseesac 缩进一级。多行操作要再缩进一级。一般情况下,不需要引用匹配表达式。模式表达式前面不应该出现左括号。避免使用;&;;&符号。即:

    # case structure
    case in expression in
      pattern1)
        statement1
        ;;
      pattern2)
        statement2
        ;;
      ...
      *)
        statementn
        ;;
    esac
    

    例如:

    case "${expression}" in
      a)
        variable="..."
        some_command "${variable}" "${other_expr}" ...
        ;;
      absolute)
        actions="relative"
        another_command "${actions}" "${other_expr}" ...
        ;;
      *)
        error "Unexpected expression '${expression}'"
        ;;
    esac
    

    只要整个表达式可读,简单的命令可以跟模式和;; 写在同一行。这通常适用于单字母选项的处理。当单行容不下操作时,请将模式单独放一行,然后是操作,最后结束符;; 也单独一行。当操作在同一行时,模式的右括号之后和结束符;;之前请使用一个空格分隔。

    verbose='false'
    aflag=''
    bflag=''
    files=''
    while getopts 'abf:v' flag; do
      case "${flag}" in
        a) aflag='true' ;;
        b) bflag='true' ;;
        f) files="${OPTARG}" ;;
        v) verbose='true' ;;
        *) error "Unexpected option ${flag}" ;;
      esac
    done
    

    变量扩展

    按优先级顺序:保持跟你所发现的一致;引用你的变量;推荐用${var}而不是$var

    例如

    # Section of recommended cases.
    
    # Preferred style for 'special' variables:
    echo "Positional: $1" "$5" "$3"
    echo "Specials: !=$!, -=$-, _=$_. ?=$?, #=$# *=$* @=$@ $=$$ ..."
    
    # Braces necessary:
    echo "many parameters: ${10}"
    
    # Braces avoiding confusion:
    # Output is "a0b0c0"
    set -- a b c
    echo "${1}0${2}0${3}0"
    
    # Preferred style for other variables:
    echo "PATH=${PATH}, PWD=${PWD}, mine=${some_var}"
    while read f; do
      echo "file=${f}"
    done < <(ls -l /tmp)
    
    # Section of discouraged cases
    
    # Unquoted vars, unbraced vars, brace-quoted single letter
    # shell specials.
    echo a=$avar "b=$bvar" "PID=${$}" "${1}"
    
    # Confusing use: this is expanded as "${1}0${2}0${3}0",
    # not "${10}${20}${30}
    set -- a b c
    echo "$10$20$30"
    

    特性

    博客:https://www.cnblogs.com/Rohn

    命令替换

    使用 $(command)而不是反引号。

    嵌套的反引号要求用反斜杠转义内部的反引号。而$(command) 形式嵌套时不需要改变,而且更易于阅读。

    例如:

    # This is preferred:
    var="$(command "$(command1)")"
    
    # This is not:
    var="`command \`command1\``"
    

    文件名的通配符扩展

    当进行文件名的通配符扩展时,请使用明确的路径。

    因为文件名可能以-开头,所以使用扩展通配符./**来得安全得多。

    # Here's the contents of the directory:
    # -f  -r  somedir  somefile
    
    # This deletes almost everything in the directory by force
    psa@bilby$ rm -v *
    removed directory: `somedir'
    removed `somefile'
    
    # As opposed to:
    psa@bilby$ rm -v ./*
    removed `./-f'
    removed `./-r'
    rm: cannot remove `./somedir': Is a directory
    removed `./somefile'
    

    命名约定

    博客:https://www.cnblogs.com/Rohn

    函数名

    使用小写字母,并用下划线分隔单词。使用双冒号 :: 分隔库。函数名之后必须有圆括号。关键词 function 是可选的,但必须在一个项目中保持一致。

    如果你正在写单个函数,请用小写字母来命名,并用下划线分隔单词。如果你正在写一个包,使用双冒号 :: 来分隔包名。大括号必须和函数名位于同一行(就像在Google的其他语言一样),并且函数名和圆括号之间没有空格。

    # Single function
    my_func() {
      ...
    }
    
    # Part of a package
    mypackage::my_func() {
      ...
    }
    

    当函数名后存在 () 时,关键词 function 是多余的。但是其促进了函数的快速辨识。

    变量名

    使用小写字母,循环的变量名应该和循环的任何变量同样命名。例如:

    for zone in ${zones}; do
      something_with "${zone}"
    done
    

    常量和环境变量名

    全部使用大写字母,用下划线分隔,声明在文件的顶部。例如:

    # Constant
    readonly PATH_TO_FILES='/some/path'
    
    # Both constant and environment
    declare -xr ORACLE_SID='PROD'
    

    源文件名

    使用小写字母,如果需要的话使用下划线分隔单词。例如: maketemplate 或者 make_template ,而不是 make-template

    只读变量

    使用小写字母,使用 readonly 或者 declare -r 来确保变量只读。

    因为全局变量在Shell中广泛使用,所以在使用它们的过程中捕获错误是很重要的。当你声明了一个变量,希望其只读,那么请明确指出。

    zip_version="$(dpkg --status zip | grep Version: | cut -d ' ' -f 2)"
    if [[ -z "${zip_version}" ]]; then
      error_message
    else
      readonly zip_version
    fi
    

    使用本地变量

    使用小写字母,使用 local 声明特定功能的变量。声明和赋值应该在不同行。

    使用 local 来声明局部变量以确保其只在函数内部和子函数中可见。这避免了污染全局命名空间和不经意间设置可能具有函数之外重要性的变量。

    当赋值的值由命令替换提供时,声明和赋值必须分开。因为内建的 local 不会从命令替换中传递退出码。

    my_func2() {
      local name="$1"
    
      # Separate lines for declaration and assignment:
      local my_var
      my_var="$(my_func)" || return
    
      # DO NOT do this: $? contains the exit code of 'local', not my_func
      local my_var="$(my_func)"
      [[ $? -eq 0 ]] || return
    
      ...
    }
    

    调用命令

    博客:https://www.cnblogs.com/Rohn

    检查返回值

    对于非管道命令,使用$?或直接通过一个if语句来检查以保持其简洁。例如:

    if ! mv "${file_list}" "${dest_dir}/" ; then
      echo "Unable to move ${file_list} to ${dest_dir}" >&2
      exit "${E_BAD_MOVE}"
    fi
    
    # Or
    mv "${file_list}" "${dest_dir}/"
    if [[ "$?" -ne 0 ]]; then
      echo "Unable to move ${file_list} to ${dest_dir}" >&2
      exit "${E_BAD_MOVE}"
    fi
    

    Bash也有 PIPESTATUS 变量,允许检查从管道所有部分返回的代码。如果仅仅需要检查整个管道是成功还是失败,以下的方法是可以接受的:

    tar -cf - ./* | ( cd "${dir}" && tar -xf - )
    if [[ "${PIPESTATUS[0]}" -ne 0 || "${PIPESTATUS[1]}" -ne 0 ]]; then
      echo "Unable to tar files to ${dir}" >&2
    fi
    

    可是,只要你运行任何其他命令, PIPESTATUS 将会被覆盖。如果你需要基于管道中发生的错误执行不同的操作,那么你需要在运行命令后立即将 PIPESTATUS 赋值给另一个变量(别忘了 [ 是一个会将 PIPESTATUS 擦除的命令)。

    tar -cf - ./* | ( cd "${DIR}" && tar -xf - )
    return_codes=(${PIPESTATUS[*]})
    if [[ "${return_codes[0]}" -ne 0 ]]; then
      do_something
    fi
    if [[ "${return_codes[1]}" -ne 0 ]]; then
      do_something_else
    fi
    
  • 相关阅读:
    《大道至简》读书笔记 第3篇
    《大道至简》读书笔记 第2篇
    返回一个二维整数数组中最大联通子数组的和(思路)
    《大道至简》读书笔记 第1篇
    给尊敬的王老师
    团队开发——冲刺2.g
    第一阶段冲刺的总结报告
    团队开发——冲刺2.f
    《人月神话》读书笔记 第3篇
    团队开发——冲刺2.e
  • 原文地址:https://www.cnblogs.com/Rohn/p/13123790.html
Copyright © 2011-2022 走看看