Using Shellcheck is good for me

October 18, 2017

A few months ago I wrote an entry about my views on Shellcheck where I said that I found it too noisy to be interesting or useful to me. Well, you know what, I have to take that back. What happened is that as I've been writing various shell scripts since then, I've increasingly found myself reaching for Shellcheck as a quick syntax and code check that I could use without trying to run my script. Shellcheck is a great tool for this, and as a bonus it can suggest some simplifications and improvements.

(Perhaps there are other programs that can do the same sort of checking that shellcheck does, but if so I don't think I've run across them yet. The closest I know of is shfmt.)

Yes, Shellcheck is what you could call nitpicky (it's a linter, not just a code checker, so part of its job is making style judgments). But going along with it doesn't hurt (I've yet to find a situation where a warning was actively wrong) and it's easier to spot real problems if 'shellcheck <script>' is otherwise completely silent. I can live with the cost of sprinkling a bunch of quotes over the use of shell variables, and the result is more technically correct even if it's unlikely to ever make a practical difference.

In other words, using Shellcheck is good for me and my shell scripts even if it can be a bit annoying. Technically more correct is still 'more correct', and Shellcheck is right about the things it complains about regardless of what I think about it.

(With that said, I probably wouldn't bother using Shellcheck and fixing its complaints about unquoted shell variable usage if that was all it did. The key to its success here is that it adds value over and above its nit-picking; that extra value pushes me to use it, and using it pushes me to do the right thing by fixing my variable quoting to be completely correct.)


Comments on this page:

I find it warns about unquoted variables in places where quoting actually does break things:

$ foo="-l -h" $ ls "$foo" ls: invalid option -- ' ' Try 'ls --help' for more information.

Without the quotes it works as expected.

Oh but you’re actively using word splitting there, Nolan. Do you not know that’s naughty? 😊

By Anon at 2017-10-25 01:08:38:

Thank you for letting us know that suggestions in your comments (https://utcc.utoronto.ca/~cks/space/blog/programming/BourneShellGlobalVariableOops?showcomments#comments ) went on to help you. Hopefully we'll keep posting useful stuff to justify you supporting them...

By root at 2024-04-28 08:35:53:

i tried shellcheck and the first thing it did was recommend removing quoting from case labels. this is wrong as case labels, like $variables, are subject to IFS expansion. pretending shell isnt a broken language is a great way to write broken shell programs. i would rather trust my own experience. the offending (POSIX and V7 compatible) script:


#! /bin/sh IFS=' ' case \ $*\ in \ -) test -t 0 -o -t 1 && cat >> /dev/tty << eot

WARNING: bang does not accept any -flags, only files.
DESCRIPTION.
  bang is like cat but interprets !command escapes with either
  (1) SHELL=$SHELL, or
  (2) eval (if SHELL is empty or SHELL=eval)

USAGE.
  i use bang as input to other commands:

    bang|dc
    !getconf INT_MAX

  to make simple/interactive notes:

    tee INDEX! | bang > INDEX
    welcome to my humble directory.
    !ls|nl
    ^D (ctrl+d)

  and as a basic preprocessor to avoid painful shell quoting:

    SHELL=dc bang file

BUGS.
  bang can only handle one-liners.
  bang is inefficient, each !command is a new process.
  bang might strip spaces/tabs if IFS behaves weirdly
  bang chokes on null \000 bytes ^@
  bang behaves differently if stdout is not a terminal

eot
       ;;
esac
case "$#" in
\0) cat;;
*) while :
   do cat <"$1"
   case "$#" in
   \1) break;;
   esac
   shift
   done
   ;;
esac | while IFS='
'
read -r line
do case "$line" in
\!*) IFS=' 	'
     case $SHELL in
     ''|eval) eval "`sed 's/!//'`";;
     *) sed 's/!//' | $SHELL;;
     esac | if test -t 1
     then cat
     else tee -a /dev/tty
     fi
     ;;
*) cat;;
esac << eot
$line
eot
done
By root at 2024-04-28 08:40:00:

that first line should be


#! /bin/sh
IFS=' ' case \ $*\ in \ -) test -t 0 -o -t 1 && cat >> /dev/tty << eot
By root at 2024-04-28 08:43:19:

oops. it should be


#! /bin/sh
IFS=' '
case \ $*\  in
*\ -*) test -t 0 -o -t 1 && cat >> /dev/tty << eot
By root at 2024-04-28 08:46:19:

it seems i was wrong, shellcheck was right

Written on 18 October 2017.
« I still like Python and often reach for it by default
Ext4, SSDs, and RAID stripe parameters »

Page tools: View Source, View Normal.
Search:
Login: Password:

Last modified: Wed Oct 18 23:55:25 2017
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.