Hi.
There have been discussions about static checkers for shell scripts. However, because of the flexibility of shell, there are times when the script really needs to be executed. For example:
ls=~/bin/my-ls
$ls /
Unless one processes the assignment, there is no way to tell if the command $ls
will work, or even if it exists.
However, as an experiment and proof-of-concept, I wrote a perl
script that, with many kludges, can process a number of the Bourne-shell-family statements to isolate commands that are not available. The perl is about 150 lines long.
An example shell script for input might be:
#!/usr/bin/env bash
# @(#) Example-2 Demonstrate sample statements for diagnosis.
LC_ALL=C ; LANG=C ; export LC_ALL LANG
pe() { for _i;do printf "%s" "$_i";done; printf "\n"; }
pl() { pe;pe "-----" ;pe "$*"; }
em() { pe "$*" >&2 ; }
db() { ( printf " db, ";for _i;do printf "%s" "$_i";done;printf "\n" ) >&2 ; }
db() { : ; }
C=$HOME/bin/context && [ -f $C ] && $C
FILE=${1-data1}
p=$( basename $0 ) t1="$Revision: 1.14 $" v=${t1//[!0-9.]/}
[[ $# -gt 0 ]] && [[ "$1" =~ -version ]] && { echo "$p (local) $v" ; exit 0 ; }
[[ $# -le 0 ]] || echo1 Hi
cat $FILE | cat1 | cat2 ; cat3
v1="a|b\
|c|\
d"
./scramble1
~/scramble2
$HOME/scramble3
x=`cmd1`
y=$( cmd2 )
if [ stuff ] ; then other stuff; fi
if [ stuff0 ]
then
other stuff
fi
if [[ modern stuff ]] ; then other stuff; fi
while [ repetitive exp ] ; do stuff ; done
while [[ more exps ]] ; do stuff ; done
if grep1 ; then stuff1 ; done
while gawk2 ; do stuff2 ; done
case $item in:
a) a=1 ;;
(b) case1 ;;
esac
db " End of script."
exit 0
and running the perl code across it yields:
$ ./p1 example-2
/home/drl/scramble3: not found
./scramble1: not found
basename is /usr/bin/basename
case1: not found
cat is /bin/cat
cat1: not found
cat2: not found
cat3: not found
cmd1: not found
cmd2: not found
db is a function defined in this script.
echo1: not found
exit is a special shell builtin
export is a special shell builtin
other: not found
pe is a function defined in this script.
printf is a shell builtin
stuff1: not found
stuff2: not found
/home/drl/scramble2: not found
For the adventurous, there is a far-more-complete, complex, shell parser found at: Shell::Parser - search.cpan.org
This is not a complete scanner of scripts. For example, note that single-line if/while
are not processed, but some shifting around of the perl would allow that. Constructs like until
, select
, etc. are not processed; case
within case
is not recognized.
The basic idea is to use 2 passes. In pass 1 the code is broken apart to allow ease of recognition in pass 2 by writing to a scratch file, then in pass 2 the scratch file is processed, resulting in the warnings, and notes.
I have a long script that adapts itself to different platforms. This perl code correctly identified the sw_vers
OSX command as not being found on Linux, as well as SuSE command zypper
not being found on Debian.
Good luck ... cheers, drl