• Shell编码风格


    1.注释

    1.1文件头

    • 每个文件的开头是其文件内容的描述。

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

    例如:

    #!/bin/bash
    #
    # Perform hot backups of Oracle databases.
    

     

    1.2.功能注释

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

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

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

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

    例如:

    #!/bin/bash
    #
    # Perform hot backups of Oracle databases.
    
    export PATH='/usr/xpg4/bin:/usr/bin:/opt/csw/bin:/opt/goog/bin'
    
    #######################################
    # Cleanup files from the backup dir
    # Globals:
    #   BACKUP_DIR
    #   ORACLE_SID
    # Arguments:
    #   None
    # Returns:
    #   None
    #######################################
    cleanup() {
      ...
    }
    

     

    1.3.实现部分的注释

    • 注释你代码中含有技巧、不明显、有趣的或者重要的部分。

    这部分遵循谷歌代码注释的通用做法。不要注释所有代码。如果有一个复杂的算法或者你正在做一些与众不同的,放一个简单的注释。

    1.4.TODO注释

    • 使用TODO注释临时的、短期解决方案的、或者足够好但不够完美的代码。

    这与C++指南中的约定相一致。

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

    例如:

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

    2.格式

    2.1缩进

    • 缩进两个空格,没有制表符。

    在代码块之间请使用空行以提升可读性。缩进为两个空格。无论你做什么,请不要使用制表符。对于已有文件,保持已有的缩进格式。

    2.2.行的长度和长字符串

    • 行的最大长度为80个字符。

    如果你必须写长度超过80个字符的字符串,如果可能的话,尽量使用here document或者嵌入的换行符。长度超过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.3.管道

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

    如果一行容得下整个管道操作,那么请将整个管道操作写在同一行。

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

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

      

    2.4.循环

    • 请将 do , then 和 while , for , if 放在同一行。

    shell中的循环略有不同,但是我们遵循跟声明函数时的大括号相同的原则。也就是说, do , then 应该和 if/for/while 放在同一行。 else 应该单独一行,结束语句应该单独一行并且跟开始语句垂直对齐。

    例如:

    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
    

      

    2.5.case语句

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

    匹配表达式比 case 和 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
    

      

    2.6.变量扩展

    • 按优先级顺序:保持跟你所发现的一致;引用你的变量;推荐用 ${var} 而不是 $var ,详细解释如下。

    这些仅仅是指南,因为作为强制规定似乎饱受争议。

    以下按照优先顺序列出。

    1. 与现存代码中你所发现的保持一致。
    2. 引用变量参阅下面一节,引用。
    3. 除非绝对必要或者为了避免深深的困惑,否则不要用大括号将单个字符的shell特殊变量或定位变量括起来。推荐将其他所有变量用大括号括起来。
    # 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"
    

      

    2.7.引用

    • 除非需要小心不带引用的扩展,否则总是引用包含变量、命令替换符、空格或shell元字符的字符串。
    • 推荐引用是单词的字符串(而不是命令选项或者路径名)。
    • 千万不要引用整数。
    • 注意 [[ 中模式匹配的引用规则。
    • 请使用 $@ 除非你有特殊原因需要使用 $* 。
    # 'Single' quotes indicate that no substitution is desired.
    # "Double" quotes indicate that substitution is required/tolerated.
    
    # Simple examples
    # "quote command substitutions"
    flag="$(some_command and its args "$@" 'quoted separately')"
    
    # "quote variables"
    echo "${flag}"
    
    # "never quote literal integers"
    value=32
    # "quote command substitutions", even when you expect integers
    number="$(generate_number)"
    
    # "prefer quoting words", not compulsory
    readonly USE_INTEGER='true'
    
    # "quote shell meta characters"
    echo 'Hello stranger, and well met. Earn lots of $$$'
    echo "Process $$: Done making $$$."
    
    # "command options or path names"
    # ($1 is assumed to contain a value here)
    grep -li Hugo /dev/null "$1"
    
    # Less simple examples
    # "quote variables, unless proven false": ccs might be empty
    git send-email --to "${reviewers}" ${ccs:+"--cc" "${ccs}"}
    
    # Positional parameter precautions: $1 might be unset
    # Single quotes leave regex as-is.
    grep -cP '([Ss]pecial||?characters*)$' ${1:+"$1"}
    
    # For passing on arguments,
    # "$@" is right almost everytime, and
    # $* is wrong almost everytime:
    #
    # * $* and $@ will split on spaces, clobbering up arguments
    #   that contain spaces and dropping empty strings;
    # * "$@" will retain arguments as-is, so no args
    #   provided will result in no args being passed on;
    #   This is in most cases what you want to use for passing
    #   on arguments.
    # * "$*" expands to one argument, with all args joined
    #   by (usually) spaces,
    #   so no args provided will result in one empty string
    #   being passed on.
    # (Consult 'man bash' for the nit-grits ;-)
    
    set -- 1 "2 two" "3 three tres"; echo $# ; set -- "$*"; echo "$#, $@")
    set -- 1 "2 two" "3 three tres"; echo $# ; set -- "$@"; echo "$#, $@")
    

      

    3.特性及错误

    3.1.命令替换

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

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

    例如:

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

      

    3.2.test,[和[[

    • 推荐使用 [[ ... ]] ,而不是 [ , test , 和 /usr/bin/ [ 。

    因为在 [[ 和 ]] 之间不会有路径名称扩展或单词分割发生,所以使用 [[ ... ]] 能够减少错误。而且 [[ ... ]] 允许正则表达式匹配,而 ... ] 不允许。

    # This ensures the string on the left is made up of characters in the
    # alnum character class followed by the string name.
    # Note that the RHS should not be quoted here.
    # For the gory details, see
    # E14 at http://tiswww.case.edu/php/chet/bash/FAQ
    if [[ "filename" =~ ^[[:alnum:]]+name ]]; then
      echo "Match"
    fi
    
    # This matches the exact pattern "f*" (Does not match in this case)
    if [[ "filename" == "f*" ]]; then
      echo "Match"
    fi
    
    # This gives a "too many arguments" error as f* is expanded to the
    # contents of the current directory
    if [ "filename" == f* ]; then
      echo "Match"
    fi
    

      

    3.3.测试字符串

    尽可能使用引用,而不是过滤字符串。

    Bash足以在测试中处理空字符串。所以,请使用空(非空)字符串测试,而不是过滤字符,使得代码更易于阅读。

    # Do this:
    if [[ "${my_var}" = "some_string" ]]; then
      do_something
    fi
    
    # -z (string length is zero) and -n (string length is not zero) are
    # preferred over testing for an empty string
    if [[ -z "${my_var}" ]]; then
      do_something
    fi
    
    # This is OK (ensure quotes on the empty side), but not preferred:
    if [[ "${my_var}" = "" ]]; then
      do_something
    fi
    
    # Not this:
    if [[ "${my_var}X" = "some_stringX" ]]; then
      do_something
    fi

    为了避免对你测试的目的产生困惑,请明确使用`-z`或者`-n`

    # Use this
    if [[ -n "${my_var}" ]]; then
      do_something
    fi
    
    # Instead of this as errors can occur if ${my_var} expands to a test
    # flag
    if [[ "${my_var}" ]]; then
      do_something
    fi
    

      

    3.4.文件名的通配符扩展

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

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

    # 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'
    

      

    3.5.Eval

    • 应该避免使用eval。

    当用于给变量赋值时,Eval解析输入,并且能够设置变量,但无法检查这些变量是什么。

    # What does this set?
    # Did it succeed? In part or whole?
    eval $(set_my_variables)
    
    # What happens if one of the returned values has a space in it?
    variable="$(eval some_function)"
    

      

    3.6.管道导向while循环

    • 请使用过程替换或者for循环,而不是管道导向while循环。在while循环中被修改的变量是不能传递给父shell的,因为循环命令是在一个子shell中运行的。

    管道导向while循环中的隐式子shell使得追踪bug变得很困难。

    last_line='NULL'
    your_command | while read line; do
      last_line="${line}"
    done
    
    # This will output 'NULL'
    echo "${last_line}"
    

      

    如果你确定输入中不包含空格或者特殊符号(通常意味着不是用户输入的),那么可以使用一个for循环。

    total=0
    # Only do this if there are no spaces in return values.
    for value in $(command); do
      total+="${value}"
    done
    

      

    使用过程替换允许重定向输出,但是请将命令放入一个显式的子shell中,而不是bash为while循环创建的隐式子shell。

    total=0
    last_file=
    while read count filename; do
      total+="${count}"
      last_file="${filename}"
    done < <(your_command | uniq -c)
    
    # This will output the second field of the last line of output from
    # the command.
    echo "Total = ${total}"
    echo "Last one = ${last_file}"
    

      

    当不需要传递复杂的结果给父shell时可以使用while循环。这通常需要一些更复杂的“解析”。请注意简单的例子使用如awk这类工具可能更容易完成。当你特别不希望改变父shell的范围变量时这可能也是有用的。

    # Trivial implementation of awk expression:
    #   awk '$3 == "nfs" { print $2 " maps to " $1 }' /proc/mounts
    cat /proc/mounts | while read src dest type opts rest; do
      if [[ ${type} == "nfs" ]]; then
        echo "NFS ${dest} maps to ${src}"
      fi
    done
    

      

  • 相关阅读:
    写给大数据开发初学者的话 | 附教程
    Mysql 到 Hbase 数据如何实时同步,强大的 Streamsets 告诉你
    如何学习大数据?阿里大数据开发师分享学习知识
    最简大数据Spark2.1.0
    从技术 Leader 的招聘需求看,如何转岗为当前紧缺的大数据相关人才?
    Redis内核原理及读写一致企业级架构深入剖析1综合组件环境实战
    为什么85%的大数据项目总是失败?
    js中的this关键字
    php百度api调用简单实例
    nginx常用命令
  • 原文地址:https://www.cnblogs.com/shwang/p/12112505.html
Copyright © 2020-2023  润新知