blob: 28f8c089d0edec0ea0696fdc10c7acae27304d3d [file] [log] [blame] [edit]
#!/bin/sh
#
# Copyright 2020, Data61, CSIRO (ABN 41 687 119 230)
#
# SPDX-License-Identifier: BSD-2-Clause
#
# Exit on unhandled error exit statuses; turn off filename expansion
# ("globbing"); warn on expansion of unset parameters.
set -efu
PROGNAME=${0##*/}
DIRNAME=${0%/*}
die () {
echo "$PROGNAME: fatal error: $*" >&2
exit 3
}
is_shell_script () {
FILE=$1
# Use parameter expansion tricks as a poor man's pattern matcher; if the
# expansion does not mutate the parameter, then it _didn't_ match the
# pattern.
if [ "${FILE%.bash}" != "$FILE" ] \
|| [ "${FILE%.ksh}" != "$FILE" ] \
|| [ "${FILE%.sh}" != "$FILE" ]
then
# It's claiming to be a Bourne-based script; believe it.
return 0
fi
# If it's executable, let file(1) do the hard work of figuring out what kind
# of program it is.
if [ -x "$FILE" ]
then
DESCRIPTION=$(file -b "$FILE")
case "$DESCRIPTION" in
# Catch POSIX, Korn, Bourne-Again, and Z shell scripts.
("* shell script*"|"* zsh script*")
return 0
;;
# Catch indirections through env. Mind ksh88 and ksh93.
("*env *sh script*"|"*env ksh88 script*"|"*env ksh93 script*")
return 0
;;
esac
fi
# All checks failed; report that the argument is not a shell script.
return 1
}
# Amusingly, this script won't validate itself with "command -v"; the
# "-v" flag was part of the "User Portability Utilities option" in POSIX
# Issue 6 (2004), but only promoted to full mandatory status in Issue 7
# (2017). So instead use "which" (a Debianism, but widely available)
# until Debian's checkbashisms catches up.
#
#if ! command -v checkbashisms > /dev/null
if ! which checkbashisms > /dev/null
then
die "\"checkbashisms\" command not found; is the \"devscripts\" package" \
"installed?"
fi
if ! which file > /dev/null
then
die "\"file\" command not found; is the \"file\" package installed?"
fi
if ! which python3 > /dev/null
then
die "\"python3\" command not found; is the \"python3\" package installed?"
fi
if [ -f .stylefilter ]
then
set -- $(python3 "$DIRNAME"/filter.py -f .stylefilter "$@")
fi
for FILE
do
# Is the current file a shell script? If not, skip it.
is_shell_script "$FILE" || continue
# --force checks for non-POSIX constructs even in scripts that declare bash
# as the interpreter (useful because we don't want any bash scripts).
#
# --posix forces full-POSIX checking, ignoring the couple of exceptions to
# POSIX-compliance permitted by Debian policy.
#
# --extra prints out the offending line after the diagnostic.
if ! checkbashisms --force --posix --extra "$FILE"
then
die "script \"$FILE\" has non-POSIX features"
fi
# Now use the shell's own internal parser to find more subtle problems.
# Even this is not perfect because in this mode, the shell fails to perform
# many expansions due to possible side effects. Also, the shell language is
# not decidable. :-| (See Trienen, Jennerod, "Mining Debian Maintainer
# Scripts",
# https://debconf18.debconf.org/talks/90-mining-debian-maintainer-scripts/ )
#
# Here's an example of a construct sh -n doesn't catch that fails when run
# for real:
#
# [[ a =~ a* ]] || echo a does not equal a
#
# ...and more forgivably, stupid eval tricks like this:
#
# STRING='${ARRAY_DEREF[1]}'; eval echo $STRING
if ! sh -n "$FILE"
then
die "script \"$FILE\" failed syntax check"
fi
done