Perform shell script analysis using shellcheck, a shell script static analysis tool.
Installation
Install shellcheck
.
$ sudo apt install shellcheck
Display a summary of the command-line usage.
$ shellcheck
No files specified. Usage: shellcheck [OPTIONS...] FILES... -a --check-sourced Include warnings from sourced files -C[WHEN] --color[=WHEN] Use color (auto, always, never) -e CODE1,CODE2.. --exclude=CODE1,CODE2.. Exclude types of warnings -f FORMAT --format=FORMAT Output format (checkstyle, gcc, json, tty) -s SHELLNAME --shell=SHELLNAME Specify dialect (sh, bash, dash, ksh) -V --version Print version information -x --external-sources Allow 'source' outside of FILES
Sample output
This shell script was created in 2005 and is obsolete now, but will serve as a good example.
#!/bin/sh # # Warning! Do not use! # I wrote this script in 2005 so it is oboslete. # I have spent a lot of great time using FreeBSD ;) # # Plik: neo # # Info: # Skrypt startowy (rc) połączenia dla użytkowników Neostrady. # # Parametry: # start - połącz się # restart - restart połączenia (cron) # beonline - stara się zapewnić nieprzerwane połączenie (cron) # stop - zatrzymaj połączenie # # Zmienne: # modem_driver - określa położenie firmware'u # ppp_flags - parametry polecenia ppp # modem_run_flags - parametry polecenia modem_run # ... # # Konfiguracja: # echo 'neo_enable="yes"' >> /etc/rc.conf # echo 'modem_driver="/etc/ppp/st330"' >> /etc/rc.conf # # Konfiguracja cron'a: # */5 * * * * root /etc/rc.d/neo beonline # # Komentarz: # Paremetry "start" oraz "stop" służą do rozpoczęcia i zakończenia połaczenia. # Parametr restart przeznaczony jest do okresowego restartowania połaczenia # używając cron'a (tylko z założenia), natomiast "beonline" służy # do restartowania połączenia w przypadku jego utraty (ping), aktualizacji (ppp -ddial) # i również przeznaczony jest dla cron'a. # # Konfiguracja wymaga skopiowania skrytpu do katalogu /etc/rc.d/ # i zdefiniowania w pliku /etc/rc.conf zmiennych neo_enable oraz modem_driver. # # Dla skryptów startowych wymagających wcześniejszego połączenia z siecią # należy zmodyfikować regułę REQUIRE dodając parametr neo. # # milosz.galazka@gmail.com # # # PROVIDE: neo # REQUIRE: netif mountcrit local # KEYWORD: nojail # . /etc/rc.subr modem_driver=${modem_driver:-"/etc/ppp/st330"} name="neo" rcvar=`set_rcvar` extra_commands="beonline" start_cmd="neo_start" stop_cmd="neo_stop" restart_cmd="neo_start" beonline_cmd="neo_beonline" #Polecenia sh_command=${sh_command:-"/bin/sh"} kill_command=${kill_command:-"/bin/kill"} basename_command=${basename:-"/usr/bin/basename"} xauth_command=${xauth_command:-"/usr/X11R6/bin/xauth"} hostname_command=${hostname_command:-"/bin/hostname"} domainname_command=${domainname_command:-"/bin/domainname"} grep_command=${grep_command:-"/usr/bin/grep"} ifconfig_command=${ifconfig_command:-"/sbin/ifconfig"} awk_command=${awk_command:-"/usr/bin/awk"} expr_command=${expr_command:-"/bin/expr"} route_command=${route_command:-"/sbin/route"} chown_command=${chown_command:-"/usr/sbin/chown"} rm_command=${rm_command:-"/bin/rm"} modem_run_command=${modem_run_command:-"/usr/sbin/modem_run"} modem_run_flags=${modem_run_flags:-"-f"} ppp_command=${ppp_command:-"/usr/sbin/ppp"} ppp_flags=${ppp_flags:-"-quiet -ddial adsl"} ppp_iface=${ppp_iface:-"tun0"} ping_command=${ping_command:-"/sbin/ping"} ping_flags=${ping_flags:-"-c1"} ping_host=${ping_host:-"www.wp.pl"} # Funkcja odpowiedzialna za restart firewall'a # Wymaga dostrojenia do własnych potrzeb neo_firewall(){ $sh_command /etc/rc.firewall } # Funkcja odpowiedzialna za wczytanie firmware'u neo_driver_load(){ local modem_run_check modem_run_check=`check_process $modem_run_command` echo -n "Neo driver: " if [ -z "$modem_run_check" ]; then echo "loading" $modem_run_command $modem_run_flags $modem_driver >/dev/null else echo "already loaded" fi } # Funkcja sprawdzająca, czy mamy połączenie # sprawdzając proces ppp neo_connection_check(){ local ppp_check ppp_check=`check_process $ppp_command` if [ -n "$ppp_check" ]; then echo "wait" fi } # Funkcja wywołująca ppp neo_connection_start(){ $ppp_command $ppp_flags 2>/dev/null } # Funkcja zatrzymująca ppp neo_connection_stop(){ local ppp_pid ppp_pid=`check_process $ppp_command` if [ -n "$ppp_pid" ]; then $kill_command $sig_stop $ppp_pid 2>/dev/null wait_for_pids $ppp_pid > /dev/null fi } # Funkcja pobierająca adres ip # Zwraca pusty string w przypadku niepowodzenia neo_ip_get(){ local ip=""; local ppp_loop=1 while [ -z "$ip" ] && [ "$ppp_loop" -le 12 ]; do sleep 4 ip=`$ifconfig_command $ppp_iface | $grep_command netmask | $awk_command '/inet/ {print $2}'` ppp_loop=`$expr_command $ppp_loop \+ 1` done if [ "$ppp_loop" -eq "13" ]; then echo "" exit 1 fi echo $ip } # Funkcja odpowiedzialna za czynności po wykonaniu połączenia # Wymaga dostrojenia do własnych potrzeb neo_postconfigure(){ # Pobierz adres IP local ip="" local hostname="" local name_stat="" ip=`neo_ip_get` while [ -z "$ip" ]; do echo "Error" sleep 10 exit 1 done echo "IP: " $ip # Firewall echo "Firewall: configuring" neo_firewall 2> /dev/null # Pobranie nowej nazwy hosta oraz wywołanie poleceń # hostname, domainname hostname="" while [ -z "$hostname" ]; do hostname=`$route_command get $ip | $awk_command '/route/ {print $3}'` if [ -n $hostname ]; then name_stat=`echo $hostname | $grep_command "tpnet.pl"` if [ -z "$name_stat" ]; then hostname=${hostname}.neoplus.adsl.tpnet.pl fi else sleep 2 fi done echo "Hostname: " $hostname $hostname_command $hostname $domainname_command $hostname # Restart określonych usług po zmianie adresu IP #if [ -n "$ppp_restarted" ]; then #fi # X'y + xauth # Aktualizacja .Xauthority local xauth_file=/home/milosz/.Xauthority local mcookie=`dd if=/dev/urandom bs=16 count=1 2>/dev/null | hexdump -e \\"%08x\\"` $rm_command $xauth_file $xauth_command -f $xauth_file add $hostname:0 MIT-MAGIC_COOKIE-1 $mcookie 2>/dev/null $xauth_command -f $xauth_file add $hostname/unix:0 MIT-MAGIC-COOKIE-1 $mcookie 2>/dev/null $chown_command milosz:milosz $xauth_file } # Funkcja oficjalnie rozpoczynająca połączenie neo_start() { local ppp_loop=1 local ppp_check="" local ip="" if [ -f "$modem_driver" ]; then neo_driver_load ppp_check=`neo_connection_check` echo -n "Connection: " if [ -n "$ppp_check" ]; then echo "restarting" neo_connection_stop ppp_restarted=1 while [ "$ppp_check" = "wait" ]; do if [ "$ppp_loop" -eq "13" ]; then echo "Error: IP address not assigned" sleep 10 exit 1 fi sleep 3 ppp_check=`neo_connection_check` ppp_loop=`$expr_command $ppp_loop \+ 1` done else echo "starting" fi neo_connection_start ip=`neo_ip_get` echo $ip > /var/run/neo_ip neo_postconfigure fi } # Funkcja oficjalnie zatrzymująca połączenie neo_stop(){ echo "Connection: stopped" neo_connection_stop 2>/dev/null }
Static analysis.
$ shellcheck freebsd_neo.sh
In freebsd_neo.sh line 52: . /etc/rc.subr ^-- SC1091: Not following: /etc/rc.subr was not specified as input (see shellcheck -x). In freebsd_neo.sh line 57: rcvar=`set_rcvar` ^-- SC2034: rcvar appears unused. Verify use (or export if used externally). ^-- SC2006: Use $(..) instead of legacy `..`. In freebsd_neo.sh line 58: extra_commands="beonline" ^-- SC2034: extra_commands appears unused. Verify use (or export if used externally). In freebsd_neo.sh line 59: start_cmd="neo_start" ^-- SC2034: start_cmd appears unused. Verify use (or export if used externally). In freebsd_neo.sh line 60: stop_cmd="neo_stop" ^-- SC2034: stop_cmd appears unused. Verify use (or export if used externally). In freebsd_neo.sh line 61: restart_cmd="neo_start" ^-- SC2034: restart_cmd appears unused. Verify use (or export if used externally). In freebsd_neo.sh line 62: beonline_cmd="neo_beonline" ^-- SC2034: beonline_cmd appears unused. Verify use (or export if used externally). In freebsd_neo.sh line 68: basename_command=${basename:-"/usr/bin/basename"} ^-- SC2034: basename_command appears unused. Verify use (or export if used externally). In freebsd_neo.sh line 99: local modem_run_check ^-- SC2039: In POSIX sh, 'local' is undefined. In freebsd_neo.sh line 101: modem_run_check=`check_process $modem_run_command` ^-- SC2006: Use $(..) instead of legacy `..`. ^-- SC2086: Double quote to prevent globbing and word splitting. In freebsd_neo.sh line 102: echo -n "Neo driver: " ^-- SC2039: In POSIX sh, echo flags are undefined. In freebsd_neo.sh line 105: $modem_run_command $modem_run_flags $modem_driver >/dev/null ^-- SC2086: Double quote to prevent globbing and word splitting. ^-- SC2086: Double quote to prevent globbing and word splitting. In freebsd_neo.sh line 114: local ppp_check ^-- SC2039: In POSIX sh, 'local' is undefined. In freebsd_neo.sh line 116: ppp_check=`check_process $ppp_command` ^-- SC2006: Use $(..) instead of legacy `..`. ^-- SC2086: Double quote to prevent globbing and word splitting. In freebsd_neo.sh line 124: $ppp_command $ppp_flags 2>/dev/null ^-- SC2086: Double quote to prevent globbing and word splitting. In freebsd_neo.sh line 129: local ppp_pid ^-- SC2039: In POSIX sh, 'local' is undefined. In freebsd_neo.sh line 131: ppp_pid=`check_process $ppp_command` ^-- SC2006: Use $(..) instead of legacy `..`. ^-- SC2086: Double quote to prevent globbing and word splitting. In freebsd_neo.sh line 133: $kill_command $sig_stop $ppp_pid 2>/dev/null ^-- SC2154: sig_stop is referenced but not assigned. ^-- SC2086: Double quote to prevent globbing and word splitting. ^-- SC2086: Double quote to prevent globbing and word splitting. In freebsd_neo.sh line 134: wait_for_pids $ppp_pid > /dev/null ^-- SC2086: Double quote to prevent globbing and word splitting. In freebsd_neo.sh line 141: local ip=""; ^-- SC2039: In POSIX sh, 'local' is undefined. In freebsd_neo.sh line 142: local ppp_loop=1 ^-- SC2039: In POSIX sh, 'local' is undefined. In freebsd_neo.sh line 145: ip=`$ifconfig_command $ppp_iface | $grep_command netmask | $awk_command '/inet/ {print $2}'` ^-- SC2006: Use $(..) instead of legacy `..`. ^-- SC2086: Double quote to prevent globbing and word splitting. ^-- SC2016: Expressions don't expand in single quotes, use double quotes for that. In freebsd_neo.sh line 146: ppp_loop=`$expr_command $ppp_loop \+ 1` ^-- SC2006: Use $(..) instead of legacy `..`. In freebsd_neo.sh line 153: echo $ip ^-- SC2086: Double quote to prevent globbing and word splitting. In freebsd_neo.sh line 161: local ip="" ^-- SC2039: In POSIX sh, 'local' is undefined. In freebsd_neo.sh line 162: local hostname="" ^-- SC2039: In POSIX sh, 'local' is undefined. In freebsd_neo.sh line 163: local name_stat="" ^-- SC2039: In POSIX sh, 'local' is undefined. In freebsd_neo.sh line 165: ip=`neo_ip_get` ^-- SC2006: Use $(..) instead of legacy `..`. In freebsd_neo.sh line 173: echo "IP: " $ip ^-- SC2086: Double quote to prevent globbing and word splitting. In freebsd_neo.sh line 183: hostname=`$route_command get $ip | $awk_command '/route/ {print $3}'` ^-- SC2006: Use $(..) instead of legacy `..`. ^-- SC2086: Double quote to prevent globbing and word splitting. ^-- SC2016: Expressions don't expand in single quotes, use double quotes for that. In freebsd_neo.sh line 185: if [ -n $hostname ]; then ^-- SC2070: -n doesn't work with unquoted arguments. Quote or use [[ ]]. ^-- SC2086: Double quote to prevent globbing and word splitting. In freebsd_neo.sh line 186: name_stat=`echo $hostname | $grep_command "tpnet.pl"` ^-- SC2006: Use $(..) instead of legacy `..`. ^-- SC2086: Double quote to prevent globbing and word splitting. In freebsd_neo.sh line 195: echo "Hostname: " $hostname ^-- SC2086: Double quote to prevent globbing and word splitting. In freebsd_neo.sh line 197: $hostname_command $hostname ^-- SC2086: Double quote to prevent globbing and word splitting. In freebsd_neo.sh line 198: $domainname_command $hostname ^-- SC2086: Double quote to prevent globbing and word splitting. In freebsd_neo.sh line 206: local xauth_file=/home/milosz/.Xauthority ^-- SC2039: In POSIX sh, 'local' is undefined. In freebsd_neo.sh line 208: local mcookie=`dd if=/dev/urandom bs=16 count=1 2>/dev/null | hexdump -e \\"%08x\\"` ^-- SC2039: In POSIX sh, 'local' is undefined. ^-- SC2155: Declare and assign separately to avoid masking return values. ^-- SC2006: Use $(..) instead of legacy `..`. In freebsd_neo.sh line 210: $xauth_command -f $xauth_file add $hostname:0 MIT-MAGIC_COOKIE-1 $mcookie 2>/dev/null ^-- SC2086: Double quote to prevent globbing and word splitting. ^-- SC2086: Double quote to prevent globbing and word splitting. In freebsd_neo.sh line 211: $xauth_command -f $xauth_file add $hostname/unix:0 MIT-MAGIC-COOKIE-1 $mcookie 2>/dev/null ^-- SC2086: Double quote to prevent globbing and word splitting. ^-- SC2086: Double quote to prevent globbing and word splitting. In freebsd_neo.sh line 219: local ppp_loop=1 ^-- SC2039: In POSIX sh, 'local' is undefined. In freebsd_neo.sh line 220: local ppp_check="" ^-- SC2039: In POSIX sh, 'local' is undefined. In freebsd_neo.sh line 221: local ip="" ^-- SC2039: In POSIX sh, 'local' is undefined. In freebsd_neo.sh line 225: ppp_check=`neo_connection_check` ^-- SC2006: Use $(..) instead of legacy `..`. In freebsd_neo.sh line 227: echo -n "Connection: " ^-- SC2039: In POSIX sh, echo flags are undefined. In freebsd_neo.sh line 240: ppp_check=`neo_connection_check` ^-- SC2006: Use $(..) instead of legacy `..`. In freebsd_neo.sh line 241: ppp_loop=`$expr_command $ppp_loop \+ 1` ^-- SC2006: Use $(..) instead of legacy `..`. In freebsd_neo.sh line 247: ip=`neo_ip_get` ^-- SC2006: Use $(..) instead of legacy `..`. In freebsd_neo.sh line 248: echo $ip > /var/run/neo_ip ^-- SC2086: Double quote to prevent globbing and word splitting. In freebsd_neo.sh line 261: local ping_check ^-- SC2039: In POSIX sh, 'local' is undefined. In freebsd_neo.sh line 262: ping_check=`$ping_command $ping_flags ${ping_host} 2>/dev/null | $grep_command "1 packets"` 2>/dev/null ^-- SC2006: Use $(..) instead of legacy `..`. ^-- SC2086: Double quote to prevent globbing and word splitting. ^-- SC2086: Double quote to prevent globbing and word splitting. In freebsd_neo.sh line 263: echo $ping_check ^-- SC2086: Double quote to prevent globbing and word splitting. In freebsd_neo.sh line 268: local ping_check="" ^-- SC2039: In POSIX sh, 'local' is undefined. In freebsd_neo.sh line 269: local ip="" ^-- SC2039: In POSIX sh, 'local' is undefined. In freebsd_neo.sh line 270: local neo_ip ^-- SC2039: In POSIX sh, 'local' is undefined. In freebsd_neo.sh line 271: ping_check=`neo_ping_check` > /dev/null ^-- SC2006: Use $(..) instead of legacy `..`. In freebsd_neo.sh line 276: ip=`neo_ip_get` ^-- SC2006: Use $(..) instead of legacy `..`. In freebsd_neo.sh line 277: if [ -f /var/run/neo_ip ] && [ -n `cat /var/run/neo_ip` ]; then ^-- SC2070: -n doesn't work with unquoted arguments. Quote or use [[ ]]. ^-- SC2046: Quote this to prevent word splitting. ^-- SC2006: Use $(..) instead of legacy `..`. In freebsd_neo.sh line 278: neo_ip=`cat /var/run/neo_ip` ^-- SC2006: Use $(..) instead of legacy `..`. In freebsd_neo.sh line 280: echo $ip > /var/run/neo_ip ^-- SC2086: Double quote to prevent globbing and word splitting. In freebsd_neo.sh line 281: ppp_restarted=1 ^-- SC2034: ppp_restarted appears unused. Verify use (or export if used externally).
Short examples
Other interesting messages.
if [ -n $hostname ]; then ^-- SC2070: -n doesn't work with unquoted arguments. Quote or use [[ ]]. ^-- SC2086: Double quote to prevent globbing and word splitting.
cat /proc/${1}/environ | tr '\0' '\n' | tr -s '\n' ^-- SC2002: Useless cat. Consider 'cmd < file | ..' or 'cmd file | ..' instead. ^-- SC2086: Double quote to prevent globbing and word splitting.
dir=`echo $dir | tr -d '/'` ^-- SC2006: Use $(..) instead of legacy `..`. ^-- SC2086: Double quote to prevent globbing and word splitting.
trap "unlink $certificate_file" EXIT ^-- SC2064: Use single quotes, otherwise this expands now rather than when signalled.
if [ "$local_version" == "$most_recent_version" ]; then ^-- SC2039: In POSIX sh, == in place of = is undefined.
declare -i postition; ^-- SC2034: postition appears unused. Verify use (or export if used externally).
for file in `ls ${maildir}/`; do ^-- SC2045: Iterating over ls output is fragile. Use globs. ^-- SC2006: Use $(..) instead of legacy `..`.
In convert_mbox_to_maildir.sh line 22: mkdir -p ${newdir}/${file}/{cur,new} ^-- SC2086: Double quote to prevent globbing and word splitting. ^-- SC2039: In POSIX sh, brace expansion is undefined.
for group in $*; do ^-- SC2048: Use "$@" (with quotes) to prevent whitespace problems.
temp_image="$(tempfile).png" ^-- SC2186: tempfile is deprecated. Use mktemp instead.
written_data=$(expr $written_data / 1024) # terabytes ^-- SC2003: expr is antiquated. Consider rewriting this using $((..)), ${} or [[ ]]. ^-- SC2086: Double quote to prevent globbing and word splitting.
trap "unlink $certificate_file" EXIT ^-- SC2064: Use single quotes, otherwise this expands now rather than when signalled.
if [ "$?" -eq "0" ]; then ^-- SC2181: Check exit code directly with e.g. 'if mycmd;', not indirectly with $?.
curl_command=`which curl` ^-- SC2006: Use $(..) instead of legacy `..`. ^-- SC2230: which is non-standard. Use builtin 'command -v' instead.
declare -i postition; ^-- SC2034: postition appears unused. Verify use (or export if used externally).
rm -rf ${newdir}/ ^-- SC2115: Use "${var:?}" to ensure this never expands to / .
for file in `ls ${maildir}/`; do ^-- SC2045: Iterating over ls output is fragile. Use globs. ^-- SC2006: Use $(..) instead of legacy `..`.
#!/usr/bin/python ^-- SC1071: ShellCheck only supports sh/bash/dash/ksh scripts. Sorry!
if [ -n $hostname ]; then ^-- SC2070: -n doesn't work with unquoted arguments. Quote or use [[ ]]. ^-- SC2086: Double quote to prevent globbing and word splitting.
first_letter=`echo ${output_image} | cut -c1` ^-- SC2006: Use $(..) instead of legacy `..`. ^-- SC2086: Double quote to prevent globbing and word splitting.
As you can see, it is worth incorporating shell script static analysis tools into the flow.