
ShellCheck is a static analysis tool that gives warnings and suggestions for bash/sh shell scripts.
How To Use ShellCheck
There are a number of ways to use ShellCheck.
- On The Web
- From Your Terminal
- In Your Editor
-
- Vim, through ALE, Neomake, or Syntastic
- Emacs, through Flycheck
- Sublime, through SublimeLinter.
- Atom, through Linter.
- VSCode, through vscode-shellcheck.
- Most other editors, through GCC error compatibility.
- In your build or test suites
What kind of things does ShellCheck look for?
Here is an incomplete list of detected issues:
- Quoting:
echo $1 # Unquoted variables find . -name *.ogg # Unquoted find/grep patterns rm "~/my file.txt" # Quoted tilde expansion v='--verbose="true"'; cmd $v # Literal quotes in variables for f in "*.ogg" # Incorrectly quoted 'for' loops 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
- Conditionals:
[[ 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 ((..))
- Frequently misused commands:
ShellCheck can recognize instances where commands are used incorrectly:
grep '*foo*' file # Globs in regex contexts find . -exec foo {} && bar {} \; # Prematurely terminated find -exec sudo echo 'Var=42' > /etc/profile # Redirecting sudo time --format=%s sleep 10 # Passing time(1) flags to time builtin while read h; do ssh "$h" uptime # Commands eating while loop input alias archive='mv $1 /backup' # Defining aliases with arguments tr -cd '[a-zA-Z0-9]' # [] around ranges in tr exec foo; echo "Done!" # Misused 'exec' find -name \*.bak -o -name \*~ -delete # Implicit precedence in find f() { whoami; }; sudo f # External use of internal functions
- Common beginner’s mistakes:
ShellCheck recognizes many common beginner’s syntax errors:
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 "Argument 10 is $10" # Positional parameter misreference if $(myfunction); then ..; fi # Wrapping commands in $() else if othercondition; then .. # Using 'else if'
- Style:
[[ -z $(find /tmp | grep mpg) ]] # Use grep -q instead 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 echo $[1+2] # Use standard $((..)) instead of old $[] echo $(($RANDOM % 6)) # Don't use $ on variables in $((..)) echo "$(date)" # Useless use of echo cat file | grep foo # Useless use of cat
- Data and typing errors:
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
- Robustness:
rm -rf "$STEAMROOT/"* # Catastrophic rm touch ./-l; ls * # Globs that could become options find . -exec sh -c 'a && b {}' \; # Find -exec shell injection printf "Hello $name" # Variables in printf format for f in $(ls *.txt); do # Iterating over ls output export MYVAR=$(cmd) # Masked exit codes
- Portability:
echo {1..$n} # Works in ksh, but not bash/dash/sh echo {1..10} # Works in ksh and bash, but not dash/sh echo -n 42 # Works in ksh, bash and dash, undefined in 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 local var=value # local is undefined in sh time sleep 1 | sleep 5 # Undefined uses of 'time'
- Miscellaneous:
PS1='\e[0;32m\$\e[0m ' # PS1 colors not in \[..\] PATH="$PATH:~/bin" # Literal tilde in $PATH rm “file” # Unicode quotes echo "Hello world" # Carriage return / DOS line endings echo hello \ # Trailing spaces after \ var=42 echo $var # Expansion of inlined environment #!/bin/bash -x -e # Common shebang errors echo $((n/180*100)) # Unnecessary loss of precision ls *[:digit:].txt # Bad character class globs sed 's/foo/bar/' file > file # Redirecting to input
Installation:
The easiest way to install ShellCheck locally is through your package manager.
- On systems with Cabal (installs to ~/.cabal/bin):
cabal update cabal install ShellCheck
- On systems with Stack (installs to ~/.local/bin):
stack update stack install ShellCheck
- On Debian based distros:
apt-get install shellcheck
- On Arch Linux based distros:
pacman -S shellcheck
- On Gentoo based distros:
emerge --ask shellcheck
- On EPEL based distros:
yum -y install epel-release yum install ShellCheck
- On Fedora based distros:
dnf install ShellCheck
- On OS X with homebrew:
brew install shellcheck
- On OS X with MacPorts:
port install shellcheck
- On openSUSE:Tumbleweed:
zypper in ShellCheck
- On other openSUSE distributions:
zypper ar http://download.opensuse.org/repositories/devel:/languages:/haskell/ openSUSE_$(version)/devel:languages:haskell.repo
zypper in ShellCheck
- On Solus:
eopkg install shellcheck
- From Docker Hub:
docker pull koalaman/shellcheck:latest # Or :v0.4.6 for a release version docker run -v "$PWD:/mnt" koalaman/shellcheck myscript
or use koalaman/shellcheck-alpine if you want a larger Alpine Linux based image to extend.
Travis CI Setup
language: bash addons: apt: sources: - debian-sid # Grab ShellCheck from the Debian repo packages: - shellcheck