Paste shell script, get feedback: ShellCheck project update

tl;dr: ShellCheck is a bash/sh static analysis and linting tool. Paste a shell command or script on ShellCheck.net and get feedback about many common issues, both in scripts that currently fail and scripts that appear to work just fine.

There’s been a lot of progress since I first posted about it seven months ago. It has a new home on ShellCheck.net with a simplified and improved interface, and the parser has been significantly bugfixed so that parsing errors for correct code are now fairly rare.

However, the best thing is that it can detect a heaping bunch of new problems! This post mentions merely a subset of them.

 

Quiz: ShellCheck is aware of many common usage problems. Are you?

  • find . -name *.mp3
  • sudo echo 3 > /proc/sys/vm/drop_caches
  • PS1='\e[0;32m\$\e[0m '
  • find . | grep "*.mp3"
  • [ $n > 7 ]
  • [[ $n > 7 ]]
  • tr 'A-Z' 'a-z'
  • cmd 2>&1 > log
  • array=(1, 2, 3)
  • echo $10
  • [[ $a=$b ]]
  • [[ $a = $b ]]
  • progress=$((i/total*100))
  • trap "echo \"Time used: $SECONDS\"" EXIT
  • find dir -exec cp {} /backup && rm {} \;
  • [[ $keep = [yY] ]] && mv file /backup || rm file

 
 
ShellCheck gives more helpful messages for many Bash syntax errors

Bash says ShellCheck points to the exact position and says
: command not found Literal carriage return. Run script through tr -d ‘\r’
unexpected token: `fi’ Can’t have empty then clauses (use ‘true’ as a no-op)
unexpected token `(‘ Shells are space sensitive. Use ‘< <(cmd)', not '<<(cmd)'
unexpected token `(‘ ‘(‘ is invalid here. Did you forget to escape it?
echo foo: command not found This is a &nbsp;. Delete it and retype as space.

 
 
ShellCheck suggests style improvements

Code ShellCheck suggestion
basename "$var" Use parameter expansion instead, such as ${var##*/}
ls | grep 'mp3$' Don’t use ls | grep. Use a glob or a for loop with a condition.
expr 3 + 2 Use $((..)), ${} or [[ ]] in place of antiquated expr.
cat foo | grep bar Useless cat. Consider ‘cmd < file | ..' or 'cmd file | ..' instead.
length=$(echo "$var" | wc -c") See if you can use ${#variable} instead

 
 
ShellCheck recognizes common but wrong attempts at doing things

Code ShellCheck tip
var$n=42 For indirection, use (associative) arrays or ‘read “var$n” <<< "value"'".
(Bash says “var3=42: command not found”)
${var$n} To expand via indirection, use name=”foo$n”; echo “${!name}”
(Bash says “bad substitution”. )
echo 'It\'s time' Are you trying to escape that single quote? echo ‘You’\”re doing it wrong’
(Bash says “unexpected end of file”)
[ grep a b ] Use ‘if cmd; then ..’ to check exit code, or ‘if [[ $(cmd) == .. ]]’ to check output
(Bash says “[: a: binary operator expected”)
var=grep a b To assign the output of a command, use var=$(cmd)
(Bash says “a: command not found”)

 
ShellCheck can help with POSIX sh compliance and bashisms

When a script is declared with #!/bin/sh, ShellCheck checks for POSIX sh compliance, much like checkbashisms.

 
ShellCheck is free software, and can be used online and locally

ShellCheck is of course Free Software, and has a cute cli frontend in addition to the primary online version.

 
ShellCheck wants your feedback and suggestions!
Does ShellCheck give you incorrect suggestions? Does it fail to parse your working code? Is there something it could have warned about, but didn’t? After pasting a script on ShellCheck.net, a tiny “submit feedback” link appears in the top right of the annotated script area. Click it to submit the code plus your comments, and I can take a look!

3 thoughts on “Paste shell script, get feedback: ShellCheck project update”

  1. Hi Vidar

    That is really a good and useful tool, however during it’s check I found something that it’s not prepared for (though it’s not a minor bug):

    example script:
    #!/bin/bash
    VAR1=1
    VAR2=2
    echo $((${VAR1}+${VAR2}))

    When I run the analyser against this code, it will report \SC2004 $ on variables in (( )) is unnecessary.\ error against it. Needless to say, that ‘echo $(({VAR1}+{VAR2}))’ just doesn’t work, instead the code should look like ‘echo $((VAR1+VAR2))’, though the analyser doesn’t highlight that.

    cheers
    Gabor

  2. Hi,

    One Suggestion: unused function (declared but never used)

    Another (off topic) suggestion: Call graph for bash functions

    I need it for documentation purposes (very useful for resuming shell developing after a long perid). I was thinking to make it with python + graphviz + exuberant ctags. But ctags only find the beginning of the function, and finding the end seems no easy task (needs a bash parser).

    Sounds interesting to you? May be a standalone sellgraph script or a –call-graph option into shellcheck.
    Have you done (or know of) something like this?

    Examples:
    In python: http://blog.prashanthellina.com/2007/11/14/generating-call-graphs-for-understanding-and-refactoring-python-code/
    Generic: https://en.wikipedia.org/wiki/Call_graph

  3. @Gabor Lukacs
    You’re right. The comment text has been updated to clarify.

    @Dani
    This would definitely something that could be done with ShellCheck’s parsed output. I rarely see scripts large enough for this to matter though, and ShellCheck doesn’t (yet) handle multi-file scripts well, so it’s not something I plan on doing myself. If you’re feeling adventurous, you can link ShellCheck’s parser and look for all T_Function nodes for declarations and matching T_SimpleCommand for invocations.

Leave a Reply to Dani Cancel reply