shellcheck 是一个 GPLv3 工具,它为 bash/sh shell 脚本提供警告和建议:

shellcheck 的目标是

  • 指出并澄清导致 shell 的典型初学者语法问题 给出隐晦的错误消息。
  • 指出并澄清典型的中级语义问题 使外壳表现出奇怪的、与直觉相反的行为。
  • 指出可能会导致风险的一些微妙的注意事项、角落案例和陷阱。 高级用户的其他工作脚本在将来的情况下将失败.

1、安装步骤:

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

在带有 cabal 的系统上(安装到 ~/.cabal/bin):

cabal update
cabal install ShellCheck

在具有堆栈的系统上(安装到 ~/.local/bin):

stack update
stack install ShellCheck

在基于 Debian 的 DiStros 上:

sudo apt install shellcheck

在基于 Arch Linux 的 DiStros 上:

pacman -S shellcheck

或者从 Aur 获得不依赖项 shellcheck-bin。

基于 Gentoo 的发行版:

emerge --ask shellcheck

基于 EPEL 的发行版:

sudo yum -y install epel-release
sudo yum install ShellCheck

基于 Fedora 的发行版:

dnf install ShellCheck

在 FreeBSD 上:

pkg install hs-ShellCheck

在带有 Homebrew 的 macOS 上:

brew install shellcheck

或使用 MacPorts:

sudo port 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

或 Windows(通过 scoop):

C:\> scoop install shellcheck

来自 conda-forge:

conda install -c conda-forge shellcheck

来自 Snap Store:

snap install --channel=edge shellcheck

从 docker 中心:

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

如果你想要扩展一个更大的基于 Alpine Linux 的映像,也可以使用 koalaman/shellcheck-alpine。它的工作原理与普通的 Alpine 图像完全一样,但预装了 shellcheck 。

使用 NIX 软件包管理器:

nix-env -iA nixpkgs.shellcheck

或者,你可以在以下站点下载最新版本的预编译二进制文件:

  • Linux, x86_64(静态链接)
  • Linux, armv6hf,即树莓派(静态连接)
  • Linux, aarch64 又名 arm64(静态链接)
  • macOS, x86_64
  • Windows, x86

或查看 GitHub Releases 的其他版本(包括用于每日 Git 构建的 latest 元版本)。

发行包已经带有 man 页面。如果你编译来自 Source,则可以将其安装为:

pandoc -s -f markdown-smart -t man shellcheck.1.md -o shellcheck.1
sudo mv shellcheck.1 /usr/share/man/man1



2、错误代码图库

那么 shellcheck 寻找什么样的东西呢?以下是已检测到的问题的不完整列表。

2.1引号问题

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 #过早扩展陷阱


2.2条件判断

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 ((..)) #在((..))中使用比较



2.3常见的对命令的错误使用

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 # 重定向sudo

time --format=%s sleep 10 # Passing time(1) flags to time builtin # 将time(1)的标志传递给内建的time

while read h; do ssh "$h" uptime # Commands eating while loop input # 一个获取输入的while循环中,使用同样会获取输入的命令

alias archive='mv $1 /backup' # Defining aliases with arguments # 定义使用参数的alias

tr -cd '[a-zA-Z0-9]' # [] around ranges in tr # 在tr的参数范围外使用[]

exec foo; echo "Done!" # Misused 'exec' # 错误地使用exec

find -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 #在外部使用内部函数


2.4 初学者的常见错误

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 if

f; f() { echo "hello world; }    # Using function before definition #在函数定义之前使用函数

[ false ] # 'false' being true # 此处false为true

if ( -f file ) # Using (..) instead of test #使用()取代测试条件



2.5 风格

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


[[ -z $(find /tmp | grep mpg) ]]   # Use grep -q instead #改成使用grep -q

a >> log; b >> log; c >> log    # Use a redirection block instead    #改成使用重定向块

echo "The time is `date`" # Use $() instead #改成使用$()

cd dir; process *; cd ..; # Use subshells instead #改成使用subshell

echo $[1+2] # Use standard $((..)) instead of old $[] #改成使用标准的$((..))

echo $(($RANDOM % 6)) # Don't use $ on variables in $((..)) #在$((..))中不要使用$

echo "$(date)" # Useless use of echo # 没必要的echo

cat file | grep foo # Useless use of cat #没必要的cat


2.6 数据和拼写错误

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参数数量不匹配


2.7 鲁棒性

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


rm -rf "$STEAMROOT/"* # Catastrophic rm # 可能导致灾难性后果的rm

touch ./-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分支


2.8 可移植性

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 中未定义local

time sleep 1 | sleep 5 # Undefined uses of 'time' # 使用了time未定义的用法



2.9 其他杂七杂八的问题

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标志