Bash Style Guide

前言

在 Github 上看到有同学整理的 《 Bash Style Guide》,受教良多,翻译一份做备忘。

原作者的介绍

这篇 Guide 的目标是为了提高 Bash 脚本的安全和可读性, 基于这篇 wiki.

特别是这一页: BashGuide/Practices.

如果 Guide 中没有提及的,默认参考原 Wiki.

Bash 之美

  • TAB / 空格

    优先使用 TAB 做分隔符.

  • 分号

    既然敲命令行时不用分号, 那在 Bash 脚本里也不要用啦.

      # 错误的示范
      name='dave';
      echo "hello $name";
    
      # 正确的示范
      name='dave'
      echo "hello $name"
    
  • 函数

    不要使用function关键字, 所有函数作用域内的变量应该声明为local.

      # 错误的示范
      function foo {
          i=foo
      }
    
      # 正确的示范
      foo() {
          local i=foo
      }
    
  • 代码段落

    ifthen在同一行, dowhile在同一行.

      # 错误的示范
      if true
      then
          ...
      fi
    
      # 虽然看上去挺酷, 但是还是一个错误的示范
      true && {
          ...
      }
    
      # 正确的示范
      if true; then
          ...
      fi
    
  • 空行

    不要连续两个空行

  • 注释

    没有太多风格建议, 但不要因为审美而修改其他小伙伴的注释, 除非你在重写或者更新这块代码~

Bash 内建命令

这是一篇 Bash 编码风格建议, 这意味着, 给出的建议更倾向于内建的 Bash 命令或者关键字, 而非外部命令或者sh(1)这样的句法.

  • test(1)

    做条件判断时, 使用 [[ ... ]] 而非 [ .. ] 或者 test ...

      # 错误的示范
      test -d /etc
      [ -d /etc ]
    
      # 正确的示范
      [[ -d /etc ]]
    

    更多可以参考 http://mywiki.wooledge.org/BashFAQ/031

  • 序列

    优先使用 Bash 的内建命令

      n=10
    
      # 错误的示范
      for f in $(seq 1 5); do
          ...
      done
    
      # 错误的示范
      for f in $(seq 1 "$n"); do
          ...
      done
    
      # 正确的示范
      for f in {1..5}; do
          ...
      done
    
      # 正确的示范
      for ((i = 0; i < n; i++)); do
          ...
      done
    
  • 命令替换

    使用 $(...) 做命令替换

      # 错误的示范
      foo=`date`
    
      # 正确的示范
      foo=$(date)
    
  • 数学运算

    使用 ((...))$((...)). 千万不要使用let.

      a=5
      b=4
      # 错误的示范
      if [[ $a -gt $b ]]; then
          ...
      fi
      # 正确的示范
      if ((a > b)); then
          ...
      fi
    
  • 参数扩展

    参考parameter expansion, 优先使用内建命令, 而非echo, sed, awk这些.

      name='bahamas10'
    
      # 错误的示范
      prog=$(basename "$0")
      nonumbers=$(echo "$name" | sed -e 's/[0-9]//g')
    
      # 正确的示范
      prog=${0###*/}
      nonumbers=${name//[0-9]/}
    
  • 遍历文件

    优先使用内建命令, 不要使用parse ls(1)

      # 大错特错, 不安全
      for f in $(ls); do
          ...
      done
    
      # 正确的示范
      for f in *; do
          ...
      done
    
  • 可执行文件的路径

    简而言之, 你不应该知道这个路径; 如果你在尝试寻找可执行文件的全路径, 那你应该重新思考下软件设计. 更多可以参考这里.

  • 数组和列表

    尽可能使用 Bash 的内建数组, 而不是一串被空格分开的 string

      # 错误的示范
      modules='json httpserver jshint'
      for module in $modules; do
          npm install -g "$module"
      done
    
      # 正确的示范
      modules=(json httpserver jshint)
      for module in "${modules[@]}"; do
          npm install -g "$module"
      done
    
  • 使用read

    尽可能的使用内建命令read, 避免 fork

      # 正确的示范
      fqdn='computer1.daveeddy.com'
      IFS=. read hostname domain tld <<< "$fqdn"
      echo "$hostname is in $domain.$tld"
      # => "computer1 is in daveeddy.com"
    
      # 另一个按行读文件的正确示范
      while read line; do
          ...
      done < file_to_read
    

外部命令

  • GNU 工具

    世界不仅仅运行在 GNU 或者 Linux 上, 当 fork 外部命令时, 例如awk, sed, grep等等, 为了保持可移植性, 尽可能避免 GNU 的特殊选项. 当你使用 Bash 的强大内建命令写脚本时, 你会发现几乎用不到这些外部命令.

  • uuoc" class="anchor" href="#uuoc">UUOC

    不需要时别用cat(1), 如果程序支持从 stdin 读入, 使用重定向传递数据.

      # 错误的示范
      cat file | grep foo
    
      # 正确的示范
      grep foo < file
      grep foo file
    

    优先使用内建的命令行工具读文件, 而不是从 stdin 读入.

编码风格

  • 引号

    当需要变量扩展或者命令解释替换时使用双引号, 其他情况下使用单引号.

      # 正确的示范
      foo='Hello World'
      bar="You are $USER"
    
      # 错误的示范
      bar='You are $USER'
    

    变量如果会被分割(包含空格, TAB或者新行), 必须要加引号, 否则可以不加.

      foo='hello world'
      if [[ -n $foo ]]; then   # 不需要加引号
          echo "$foo"          # 需要加引号
      fi
      bar=$foo                 # 不需要加引号
    

    唯一的例外是变量明确不会被分割(生命周期有保证), 例如, basher 中有段代码:

      printf_date_supported=false
      if printf '%()T' &>/dev/null; then
          printf_date_supported=true
      fi
    
      # 这里可以不需要引号
      if $printf_date_supported; then
          ...
      fi
    

    同理, $, $?, $#, 也不需要引号, 因为他们不会包含空格, TAB或者新行. 如果不确定, 那还是都把引号加上吧, 个人更推荐这样, 简单又安全, quote all expansions.

  • 变量声明

    避免大写变量名, 除非确实有必要. 不要使用let或者readonly创建变量名. declare应该用在关联数组中. 在函数中尽量使用local.

      # 错误的示例
      declare -i foo=5
      let foo++
      readonly bar='something'
      FOOBAR=baz
    
      # 正确的示例
      i=5
      ((i++))
      bar='something'
      foobar=baz
    
  • 环境声明

    (原E文标题是 shebang, "家当"的意思, 可是为什么我想起了萨满的蛇棒……) Bash 不一定都在 /bin/bash, 所以使用下面的方式:

      #!/usr/bin/env bash
    
  • 错误检查

    举个例子, cd不一定成功, 使用 exit 或者 break 来保证检查可能出现的错误.

      # 错误的示范
      cd /some/path # 可能失败
      rm file       # 如果 cd 失败, 还删不删?
    
      # 正确的示范
      cd /some/path || exit
      rm file
    
  • set -e

    不要设置errexit, 和 C 一样, 一些时候会预期发生错误或者失败, 但是并不希望程序退出. 这里有详细的解释.

  • eval

    永远不要用!

错误示范

Bash Pitfalls

License

MIT Licencse