特性及错误

命令替换

Tip

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

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

例如:

  1. # This is preferred:
  2. var="$(command "$(command1)")"
  3.  
  4. # This is not:
  5. var="`command \`command1\``"

test,[和[[

Tip

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

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

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

测试字符串

Tip

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

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

  1. # Do this:
  2. if [[ "${my_var}" = "some_string" ]]; then
  3. do_something
  4. fi
  5.  
  6. # -z (string length is zero) and -n (string length is not zero) are
  7. # preferred over testing for an empty string
  8. if [[ -z "${my_var}" ]]; then
  9. do_something
  10. fi
  11.  
  12. # This is OK (ensure quotes on the empty side), but not preferred:
  13. if [[ "${my_var}" = "" ]]; then
  14. do_something
  15. fi
  16.  
  17. # Not this:
  18. if [[ "${my_var}X" = "some_stringX" ]]; then
  19. do_something
  20. fi

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

  1. # Use this
  2. if [[ -n "${my_var}" ]]; then
  3. do_something
  4. fi
  5.  
  6. # Instead of this as errors can occur if ${my_var} expands to a test
  7. # flag
  8. if [[ "${my_var}" ]]; then
  9. do_something
  10. fi

文件名的通配符扩展

Tip

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

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

  1. # Here's the contents of the directory:
  2. # -f -r somedir somefile
  3.  
  4. # This deletes almost everything in the directory by force
  5. psa@bilby$ rm -v *
  6. removed directory: `somedir'
  7. removed `somefile'
  8.  
  9. # As opposed to:
  10. psa@bilby$ rm -v ./*
  11. removed `./-f'
  12. removed `./-r'
  13. rm: cannot remove `./somedir': Is a directory
  14. removed `./somefile'

Eval

Tip

应该避免使用eval。

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

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

管道导向while循环

Tip

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

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

  1. last_line='NULL'
  2. your_command | while read line; do
  3. last_line="${line}"
  4. done
  5.  
  6. # This will output 'NULL'
  7. echo "${last_line}"

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

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

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

  1. total=0
  2. last_file=
  3. while read count filename; do
  4. total+="${count}"
  5. last_file="${filename}"
  6. done < <(your_command | uniq -c)
  7.  
  8. # This will output the second field of the last line of output from
  9. # the command.
  10. echo "Total = ${total}"
  11. echo "Last one = ${last_file}"

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

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

原文: https://zh-google-styleguide.readthedocs.io/en/latest/google-shell-styleguide/features_and_bugs/