| #!/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 |