写过 shell 脚本的人都知道,在编写的过程中,即便出现一些简单的语法错误,运行的时候也可能没有办法及时发现。有些看似运行正确的脚本,实际上可能在某些分支,或者在某些场景下仍然出现错误,而有的写法可能运行正常,但是却不符合 POSIX 标准,这样就让整个脚本不具备可移植性。

这些问题,我们可以借助一些工具帮助我们提前发现一些错误。shellcheck 就是这样的一个十分不错的工具。它可以在多种场景下使用,包括在线,命令行检查,编辑器配置等。

简介

shellcheck 是一款实用的 shell 脚本静态检查工具, 可以为 bash/sh shell 脚本提供警告和建议。

ShellCheck 的目标有三点:

  • 指出并澄清典型的初学者的语法问题,那通常会shell提供神秘的错误消息。
  • 指出并澄清典型的中级的语义问题,这些问题会导致shell出现奇怪且反直觉的行为。
  • 指出可能导致高级用户的脚本中,可能在未来某种情况下失败的陷阱。

无论你是初学者,还是 shell 高手,在编写 Bash 脚本时候都用上 shellcheck 来提升工作效率。

使用方式

有多种方法可以使用 ShellCheck,你可以根据自身需求选择合适方式,具方式如下:

在线使用

在网页  https://www.shellcheck.net  上,贴入你的脚本,运行检查即可输出结果。

命令行中

下载安装后,就可以在命令行中调用 shellcheck, 检查你写好的脚本,然后执行 shellcheck。例如,编写一个 myscript.sh ,保存文件:

  •  
#!/bin/bashif[ $# -eq 0 ]then    echo "no para"else    echo "$# para"fiexit 0

执行 shellcheck

  •  
shellcheck myscript.sh

编辑器中

当然也可以把它安装到你熟悉的编辑器中,虽然它们本身都有语法高亮的功能,但是并没有直接的信息提示,安装 shellcheck 类工具,达到编写即提示的效果。

  • Vim : 通过 ALE,Neomake,或 Syntastic 等方式;
  • Emacs: 通过 Flycheck 或 Flymake 来实现。
  • Sublime: 通过 SublimeLinter。
  • Atom: 通过 Linter。
  • VSCode: 通过 vscode-shellcheck。
  • 大多数其他编辑器,通过 GCC 错误兼容性。

集成到构建和测试环境中

虽然 ShellCheck 主要用于交互式使用,但是可以很容易地将它添加到构建或测试套件中。它规范地使用了退出码,因此您可以只添加一个 shellcheck 命令作为过程的一部分。例如,在 Makefile 中:

  •  
check-scripts:    # Fail if any of these files have warnings    shellcheck myscripts/*.sh

或在 Travis CI .travis.yml 文件中:

  •  
script:  # Fail if any of these files have warnings  - shellcheck myscripts/*.sh

已经预装了 ShellCheck 的服务和平台,可随时使用:

  • Travis CI
  • Codacy
  • Code Climate
  • Code Factor
  • Github(only Linux)

服务和平台的第三方插件:

  • SonarQube through sonar-shellcheck-plugin

大多数其他服务,包括 GitLab,都允许您自己安装 ShellCheck,或者通过系统的包管理器(参见安装),或者下载并解压一个二进制版本。

不管怎样,手动安装一个特定的 ShellCheck 版本是一个好主意。这避免了在发布带有新警告的新版本时出现意外的构建中断。

对于自定义的过滤或报告,ShellCheck 可以输出简单的 JSON、CheckStyle 兼容的 XML、GCC 兼容的警告以及人类可读的文本(带或不带 ANSI 颜色)。

安装

在本地安装 ShellCheck 的最简单方法是通过包管理器。

安装到 Cabal(安装到~/.cabal/bin):

  •  
cabal updatecabal install ShellCheck

安装到 Stack(安装到~/.local/bin):

  •  
stack updatestack install ShellCheck

基于 Debian 的发行版:

  •  
apt-get install shellcheck

基于 Arch Linux 的发行版:

  •  
pacman -S shellcheck

或者从 AUR 中获得免费的 shellcheck-static 依赖。

基于 Gentoo 的发行版:

  •  
emerge --ask shellcheck

基于 EPEL 的发行版:

  •  
yum -y install epel-releaseyum install ShellCheck

基于 Fedora 的发行版:

  •  
dnf install ShellCheck

在 FreeBSD 上:

  •  
pkg install hs-ShellCheck

在 OS X 上使用 homebrew:

  •  
brew install shellcheck

在 OpenBSD 上:

  •  
pkg_add shellcheck

在 openSUSE 上:

  •  
zypper in ShellCheck

或者使用 OneClickInstall - https://software.opensuse.org/package/ShellCheck

在 Solus 上:

  •  
eopkg install shellcheck

在 Windows 上:

通过 chocolatey 方式:

  •  
C:\> choco install shellcheck

通过 scoop 方式:

  •  
C:\> scoop install shellcheck

通过 Snap 商店方式:

  •  
snap install --channel=edge shellcheck

通过 Docker Hub 方式:

  •  
docker run --rm -v "$PWD:/mnt" koalaman/shellcheck:stable myscript# Or :v0.4.7 for that version, or :latest for daily builds

Travis CI

Travis CI 现在默认集成了 ShellCheck,所以您不需要手动安装。

如果您还想升级,或者确保您使用的是最新版本,请按照以下步骤安装二进制版本。

安装预编译的二进制文件

预编译的二进制文件以 tar 格式提供。xz 文件。要解压缩它们,请确保安装了 xz。在 Debian/Ubuntu/Mint 上,你可以安装 xz-utils。在 Redhat/Fedora/CentOS 上,yum -y 安装 xz。

一个简单的安装程序可能会这样做:

  •  
scversion="stable" # or "v0.4.7", or "latest"wget -qO- "https://github.com/koalaman/shellcheck/releases/download/${scversion?}/shellcheck-${scversion?}.linux.x86_64.tar.xz" | tar -xJvcp "shellcheck-${scversion}/shellcheck" /usr/bin/shellcheck --version

从源代码编译

本节描述如何从源目录构建 ShellCheck。ShellCheck 是用 Haskell 写的,需要 2GB 的内存来编译。具体方法 可以查阅 shellcheck 的 github 首页介绍内容。

常见问题

那么 ShellCheck 会发现什么样的问题呢?以下是检测到的问题的不完整列表。

引号问题

  •  
echo $1                           # Unquoted variables  #变量未加引号find . -name *.ogg                # Unquoted find/grep patterns #find/grep 的匹配模式未加引号rm "~/my file.txt"                # Quoted tilde expansion #引号中的波浪符扩展v='--verbose="true"'; cmd $v      # Literal quotes in variables # 变量中的字面引号for f in "*.ogg"                  # Incorrectly quoted 'for' loops # 错误的for循环touch $@                          # Unquoted $@  # $@未加引号echo 'Don't forget to restart!'   # Singlequote closed by apostrophe  # 单引号被撇号意外关闭了echo 'Don\'t try this at home'    # Attempting to escape ' in ''  #试图在单引号括起来的部分中加上一个单引号echo 'Path is $PATH'              # Variables in single quotes # 将变量用单引号括起来trap "echo Took ${SECONDS}s" 0    # Prematurely expanded trap #过早扩展陷阱

条件判断

ShellCheck 可以识别大多数不正确的条件判断语句。

  •  
[[ n != 0 ]]                      # Constant test expressions  # 常量测试表达式[[ -e *.mpg ]]                    # Existence checks of globs # 对文件是否存在进行检查时,使用通配符[[ $foo==0 ]]                     # Always true due to missing spaces #由于缺乏空格,结果总是为真[[ -n "$foo " ]]                  # Always true due to literals #由于字面值存在,结果总是为真[[ $foo =~ "fo+" ]]               # Quoted regex in =~   # 在 =~ 中使用正则表达式[ foo =~ re ]                     # Unsupported [ ] operators # 不支持的[]运算符[ $1 -eq "shellcheck" ]           # Numerical comparison of strings # 比较数字和字符串[ $n && $m ]                      # && in [ .. ]  # 在[]中使用&&运算符[ grep -q foo file ]              # Command without $(..)  #命令缺少了$(..)[[ "$$file" == *.jpg ]]           # Comparisons that can't succeed #无法成功的比较(( 1 -lt 2 ))                     # Using test operators in ((..)) #在((..))中使用比较

常见的对命令的错误使用

ShellCheck 可以识别对一些命令的错误使用

  •  
grep '*foo*' file                 # Globs in regex contexts  #在grep的正则表达式中前后使用通配符find . -exec foo {} && bar {} \;  # Prematurely terminated find -exec  # 使find -exec 过早结束sudo echo 'Var=42' > /etc/profile # Redirecting sudo # 重定向sudotime --format=%s sleep 10         # Passing time(1) flags to time builtin # 将time(1)的标志传递给内建的timewhile read h; do ssh "$h" uptime  # Commands eating while loop input  # 一个获取输入的while循环中,使用同样会获取输入的命令alias archive='mv $1 /backup'     # Defining aliases with arguments # 定义使用参数的aliastr -cd '[a-zA-Z0-9]'              # [] around ranges in tr # 在tr的参数范围外使用[]exec foo; echo "Done!"            # Misused 'exec'  # 错误地使用execfind -name \*.bak -o -name \*~ -delete  # Implicit precedence in find  # 在find中的隐式优先级# find . -exec foo > bar \;       # Redirections in find  #find中的重定向f() { whoami; }; sudo f           # External use of internal functions #在外部使用内部函数

初学者的常见错误

ShellCheck 识别很多初学者的语法错误

  •  
var = 42                          # Spaces around = in assignments #等号两边的空格$foo=42                           # $ in assignments # 对变量赋值时使用了 $for $var in *; do ...             # $ in for loop variables  # 在循环变量处使用 $var$n="Hello"                     # Wrong indirect assignment #错误的变量echo ${var$n}                     # Wrong indirect reference #错误的引用var=(1, 2, 3)                     # Comma separated arrays #逗号分割数组array=( [index] = value )         # Incorrect index initialization #错误的索引初始化echo $var[14]                     # Missing {} in array references #引用数组缺少{}echo "Argument 10 is $10"         # Positional parameter misreference #错误的位置参数引用if $(myfunction); then ..; fi     # Wrapping commands in $() #在命令外加上 $()else if othercondition; then ..   # Using 'else if'  #使用else iff; f() { echo "hello world; }     # Using function before definition 在函数定义之前使用函数[ false ]                         # 'false' being true # 此处false为trueif ( -f file )                    # Using (..) instead of test #使用()取代测试条件

代码风格

ShellCheck 可以提出一些代码风格改进建议:

  •  
[[ -z $(find /tmp | grep mpg) ]]  # Use grep -q instead  #改成使用grep -qa >> log; b >> log; c >> log      # Use a redirection block instead #改成使用重定向块echo "The time is `date`"         # Use $() instead #改成使用 $()cd dir; process *; cd ..;         # Use subshells instead   #改成使用subshellecho $[1+2]                       # Use standard $((..)) instead of old $[]  #改成使用标准的 $((..))echo $(($RANDOM % 6))             # Don't use $ on variables in $((..)) #在 $((..))中不要使用 $echo "$(date)"                    # Useless use of echo # 没必要的echocat file | grep foo               # Useless use of cat #没必要的cat

数据和拼写错误

ShellCheck 可以识别一些数据和拼写错误,例如:

  •  
args="$@"                         # Assigning arrays to strings # 将数组赋值给字符串files=(foo bar); echo "$files"    # Referencing arrays as strings # 把数字当成字符串引用declare -A arr=(foo bar)          # Associative arrays without index # 不带索引组合数组printf "%s\n" "Arguments: $@."    # Concatenating strings and arrays # 连接字符串和数组[[ $# > 2 ]]                      # Comparing numbers as strings # 把数字当成字符串比较var=World; echo "Hello " var      # Unused lowercase variables # 未使用的小写变量echo "Hello $name"                # Unassigned lowercase variables # 未赋值的小写变量cmd | read bar; echo $bar         # Assignments in subshells # 在subshells中进行赋值cat foo | cp bar                  # Piping to commands that don't read # 通过管道传递数据给一个不会做读取的程序printf '%s: %s\n' foo             # Mismatches in printf argument count # pirintf参数数量不匹配

鲁棒性

ShellCheck 可以做出一些增强脚本鲁棒性的建议,例如:

  •  
rm -rf "$STEAMROOT/"*            # Catastrophic rm # 可能导致灾难性后果的rmtouch ./-l; ls *                 # Globs that could become options # 使用了可能变成选项的通配符find . -exec sh -c 'a && b {}' \;# Find -exec shell injection # Find -exec shell注入printf "Hello $name"             # Variables in printf format # 在printf的格式化参数中使用变量for f in $(ls *.txt); do         # Iterating over ls output # 在ls的输出上进行迭代export MYVAR=$(cmd)              # Masked exit codes # 使退出码模糊case $version in 2.*) :;; 2.6.*) # Shadowed case branches # 隐蔽的case分支

可移植性

ShellCheck 警告你使用了 shebang 不支持的特性.。比如,当你设置 shebang 为  #!/bin/sh 时, ShellCheck 对类似  checkbashisms  的可移植性问题发出警告。

  •  
echo {1..$n}                     # Works in ksh, but not bash/dash/sh    #在 ksh 中可用,在 bash/dash/sh 中不可用echo {1..10}                     # Works in ksh and bash, but not dash/sh #在 ksh 中可用,在 bash/dash/sh 中不可用echo -n 42                       # Works in ksh, bash and dash, undefined in sh #在 ksh/bash/dash 中可用,在 sh 中不可用trap 'exit 42' sigint            # Unportable signal spec # 不具有可移植性的信号细节cmd &> file                      # Unportable redirection operator # 不具有可移植性的重定向操作read foo < /dev/tcp/host/22      # Unportable intercepted files  # 不具有可移植性的截获的文件foo-bar() { ..; }                # Undefined/unsupported function name # 未定义/不支持的函数名[ $UID = 0 ]                     # Variable undefined in dash/sh # dash/sh 中未定义的变量local var=value                  # local is undefined in sh # sh 中未定义localtime sleep 1 | sleep 5           # Undefined uses of 'time' # 使用了time未定义的用法

其他问题

ShellCheck 可以识别到一些其他问题,例如:

  •  
PS1='\e[0;32m\$\e[0m '            # PS1 colors not in \[..\]  # PS1 的颜色不在\[..\] 中PATH="$PATH:~/bin"                # Literal tilde in $PATH # $PATH中的波浪号rm “file”                         # Unicode quotes #Unicode 引号echo "Hello world"                # Carriage return / DOS line endings # 传输返回DOS行结束符/echo hello \                      # Trailing spaces after \   # \后面的行尾空格var=42 echo $var                  # Expansion of inlined environment # 展开内联环境变量#!/bin/bash -x -e                 # Common shebang errors # shebang  命令错误echo $((n/180*100))               # Unnecessary loss of precision # 不必要的精度丢失ls *[:digit:].txt                 # Bad character class globs # 不好的通配符sed 's/foo/bar/' file > file      # Redirecting to input # 重定向到输入while getopts "a" f; do case $f in "b")   # Unhandled getopts flags # 未处理的getopts标志

总结

工欲善其事必先利其器,有一个好用的工具,可以大幅提升代码质量和工作效率。有了 ShellCheck,编写 Shell 再也不会有痛苦的体验。

 

 

 

利用shellcheck高效编写Shell脚本_shellcheck

 

利用shellcheck高效编写Shell脚本_shellcheck _02