Cover V06, I12
Article
Figure 1
Listing 1
Listing 2

dec97.tar


Listing 1: The filter library system

#!/bin/ksh
# $Id: filter.ksh 1.8 97/06/11 20:53:02 dejan Exp Locker: dejan $
# boring part (somebody had to do it)
# these are defaults: customize at will
TMPDIR=${TMPDIR:-/tmp}
FLT_SAFE_DFLT=1         # should files be saved before processing
FLT_SAFEDIR_DFLT=$TMPDIR/$LOGNAME          # directory for saving archives
FLT_SAFETMPL_DFLT="save%03d.tgz"           # archive name generation template
FLT_MAXSTOR_DFLT=10000           # maximum storage (in kb) allowed for archives
FLT_PATH_DFLT=".:$HOME:/usr/local/lib"     # library search path
FLT_LIB_DFLT=filter.lib                    # name of the library file(s)
FLT_LOG_DFLT=$TMPDIR/$LOGNAME/filter.log   # file where actions are logged
FLT_ERR_DFLT=$TMPDIR/$LOGNAME/filter.err   # file where errors are logged
COMPRESS=compress
DECOMPRESS=zcat
ARCHTEST="($DECOMPRESS | tar tf -)"    # archive test
EXTRACT="($DECOMPRESS | tar xf -)"     # dearchiver
SHELL=${SHELL:-/bin/sh}                # default shell
PAGER=${PAGER:-more}                   # more or less
ARGMARK="@@"                           # where argument gets placed
ARCH=`uname`
if [ $ARCH = OSF1 ]; then
ARCHIVE="tar -cf - -R"             # archiver
elif [ $ARCH = Linux ]; then
ARCHIVE="tar -cf - -T"             # archiver
fi
[ -z "$ARCHIVE" ] && {
echo "cannot set archiver" >&2
exit 1
}
TMPF=filter.$$
FILELIST=filelist.$$
# this is the program: don't change
# catch signals
trap '
rm -f $TMPF $FILELIST
exit
' EXIT
trap '
echo "killed by a signal" >&2
exit
' INT HUP TERM
#
function Help {
if [ "$1" = "env" ]; then
cat <<-EOF >&2
filter program can be further controlled with various environment
variables by either exporting them from the parent shell or passing
their values:
FLT_SAFE=1                  # should files be saved before processing
FLT_SAFEDIR=/tmp/$LOGNAME   # directory for saving archives
FLT_SAFETMPL="save%03d.tgz" # filename generation template
FLT_MAXSTOR=10000           # maximum storage (in kb) allowed for archives
FLT_PATH=".:$HOME"          # library search path
FLT_LIB=filter.lib          # name of the library file(s)
FLT_LOG=filter.log          # file were actions are logged
FLT_ERR=filter.err          # file were errors are logged

Environment variables can be changed with env command followed by
assignments. Word expansion and all substitutions are performed
priorily.
EOF
else
cat <<-EOF >&2
usage:
filter [<opts>] <filter> [<filteropts> --] <filespec>
filter [<opts>] with <cmd> [--] <filespec>
filter [<opts>] <command>
where
<opts> are options to the filter program
<filter> is a name of a filter to process files with
<filteropts> are options that filter can recognize
<filespec> is a specification of files to be processed
<cmd> is an external program or pipe or whatever
<command> may be one of the following:
help   give usage
quit   exit
find <flt>  search for <flt> in all libraries
env    print environment
log    show log
err    show err
undo   one-level undo
![<cmd>]  execute command in a shell

If run without arguments, filter prompts for commands and behaves
like a shell.

Type "help env" to get help on controlling the environment.
EOF
fi
}
#
# environment
#
function BuildEnv {
FLT_SAFE=${FLT_SAFE:=$FLT_SAFE_DFLT}
FLT_SAFEDIR=${FLT_SAFEDIR:=$FLT_SAFEDIR_DFLT}
FLT_SAFETMPL=${FLT_SAFETMPL:=$FLT_SAFETMPL_DFLT}
FLT_MAXSTOR=${FLT_MAXSTOR:=$FLT_MAXSTOR_DFLT}
FLT_PATH=${FLT_PATH:=$FLT_PATH_DFLT}
FLT_LIB=${FLT_LIB:=$FLT_LIB_DFLT}
FLT_LOG=${FLT_LOG:=$FLT_LOG_DFLT}
FLT_ERR=${FLT_ERR:=$FLT_ERR_DFLT}
FLT_SAFEMASK="`printf $FLT_SAFETMPL 0|sed 's/0/?/g'`"
FLT_MAXARCH=`echo $FLT_SAFETMPL | awk '
{sub("^[^%]+%",""); sub("d\..*$",""); n=0+$0;
for(m=1;n>0;n--) m=m*10; print m}'`
}
function ManageEnv {
if [ $# -eq 0 ]; then
cat <<-EOF >&2
FLT_SAFE: $FLT_SAFE
FLT_SAFEDIR: $FLT_SAFEDIR
FLT_SAFETMPL: $FLT_SAFETMPL
FLT_MAXSTOR: $FLT_MAXSTOR
FLT_PATH: $FLT_PATH
FLT_LIB: $FLT_LIB
FLT_LOG: $FLT_LOG
FLT_ERR: $FLT_ERR
EOF
else
for i in $@; do
# try to catch typos at least
case "`echo $i|sed -e 's/^FLT_//' -e 's/=[^ ].*$//'`" in
SAFE|SAFEDIR|SAFETMPL|MAXSTOR|PATH|LIB|LOG|ERR)
eval $i     # set up variable
;;
*) echo "bad usage: $i" >&2
;;
esac
BuildEnv    # rebuild environment
done
fi
}
function EmptyFile {
> $1 || {
echo "can not create $1 file" >&2
return 1
}
}
function ShowLog {
[ -s $FLT_LOG ] && $PAGER $FLT_LOG
}
function ShowErr {
[ -s $FLT_ERR ] && $PAGER $FLT_ERR
}
function LogFile {
printf "%s  %-40s " "`date`" "$1"
[ $2 -eq 0 ] && echo OK || echo "FAILED($2)"
}
#
# library stuff
#
function BuildLibList {
if [ `expr $1 : "\/"` = 1 ]; then
[ -f $1 -a -r $1 ] &&
echo $1
else
typeset i
for i in `echo $FLT_PATH|sed 's/:/ /g'`; do
[ -f $i/$1 -a -r $i/$1 ] &&
echo $i/$1
done
fi
}
# find an instance of a filter
function FindFilter {
typeset name code
while read name code; do
[ "$name" = "include" -a -n "$code" ] && {
FLT_LIB=$code FetchFilter $1 &&
return 0
}
[ "$name" = "$1" -a -n "$code" ] && {
echo $code
return 0
}
done
return 1
}
function FetchFilter {
BuildLibList $FLT_LIB |
while read FETCHED_LIB; do
sed 's/^#.*//' $FETCHED_LIB |
FindFilter $1 &&
return 0
done
}
function ListFilter {
FETCHED_CODE=`FetchFilter $1` &&
echo "Code: $FETCHED_CODE"
}
#
# archive handling
#
function Undo {
FLT_ARCHF="`ls $FLT_SAFEDIR/$FLT_SAFEMASK|tail -1`"
[ -z "$FLT_ARCHF" -o ! -r "$FLT_ARCHF" ] && {
echo "no archive available to undo" >&2
return 1
}
{ eval $EXTRACT < $FLT_ARCHF &&
echo "`date`  undo from $FLT_ARCHF" >> $FLT_LOG
} || echo "undo failed" >&2
}
function MkArchName {
FIRST="`printf $FLT_SAFETMPL 0`"
if [ ! -f $FLT_SAFEDIR/$FIRST ]; then
echo $FLT_SAFEDIR/$FIRST
return 0
fi
typeset -i n size
size="`du -sk $FLT_SAFEDIR|awk '{print $1}'`"
n="`ls $FLT_SAFEDIR/$FLT_SAFEMASK|tail -1|sed 's/[^0-9]//g'`"
if [ $size -gt $FLT_MAXSTOR -o $n -ge $FLT_MAXARCH ]; then
echo "there are ${size}kb in $n archives"
echo "you have to remove some of them"
return 1
fi >&2
let n=n+1;
printf "$FLT_SAFEDIR/$FLT_SAFETMPL\n" $n
return 0
}
function PlaySafe {
FLT_ARCHF=""
if [ -n "$FLT_SAFE" ]; then
if [ ! -d "$FLT_SAFEDIR" ]; then
mkdir $FLT_SAFEDIR || {
echo "can not create $FLT_SAFEDIR directory" >&2
return 1
}
fi
FLT_ARCHF="`MkArchName`" || return 1
EmptyFile $FLT_ARCHF || return 1
if ! eval "$ARCHIVE $FILELIST" | $COMPRESS > $FLT_ARCHF ||
! eval $ARCHTEST < $FLT_ARCHF >/dev/null 2>&1 ; then
rm -f $FLT_ARCHF
echo "unable to save files" >&2
return 1
fi
fi
return 0
}
#
# real business
#
function CheckArg {
[ ! -r "$1" ] && {
echo "$1 does not exist or read permission denied" >&2
return
}
[ ! -f "$1" ] && {
echo "$1 is not an ordinary file" >&2
return
}
[ ! -w "$1" ] && {
echo "$1 write permission denied" >&2
return
}
echo $1
}
function BuildList {
typeset i f
[ $# -eq 0 ] && return 1
EmptyFile $FILELIST || return 1
for f in $@; do
case $f in
-) while read i; do
CheckArg $i
done;;
*) CheckArg $f;;
esac
done > $FILELIST
[ ! -s $FILELIST ] && {
echo "no files to be processed" >&2
return 1
}
return 0
}
function ParseLine {
# divide line at first "--" or first white space
# append parameter marker if necessary
CURRENT=""; CODE=""; ARGS=""; MARK=""
for i in "$@"; do
[ -z "$MARK" -a "$i" = "--" ] && {
CODE="$CURRENT"
CURRENT=""
MARK=y
continue
}
CURRENT="$CURRENT $i"
done
ARGS="$CURRENT"
[ -z "$MARK" ] && {
CODE="$1"; shift 1; ARGS="$@"
}
}
function ProcFilter {
# process with registered filter
FETCHED_CODE=`FetchFilter $1` && {
ParseLine $@
FILTER="$FETCHED_CODE `echo $CODE|eval sed \"s?$1??\"`"
if [ -z "$ARGS" ]; then   # we'll accept stdin here
eval "`echo $FILTER | eval sed \"s?$ARGMARK??\"`"
else
BuildList $ARGS && PlaySafe && ProcessFiles
fi
}
}
function ProcExternal {
# process with external command
ParseLine $@
FILTER="$CODE"
BuildList $ARGS && PlaySafe && ProcessFiles
}
function Exchange {
if [ $# -ne 2 -o ! -f "$1" -o ! -f "$2" ] ; then
echo "bad args: $1,$2" >&2
return 1
fi
mv $1 flt_exch.$$ && mv $2 $1 && mv flt_exch.$$ $2
}
function ProcOne {
EFILTER="`echo $FILTER | eval sed \"s?$ARGMARK?$1?\"`"
if eval $EFILTER > $TMPF 2>>$FLT_ERR; then
if Exchange $TMPF $1 ; then
LogFile "$EFILTER" 0
else
LogFile "$EFILTER" $?
ERRFLAG=2
echo "panic: could not exchange files" >&2
break
fi
else
LogFile "$EFILTER" $?
ERRFLAG=1
echo "___from: $EFILTER" >> $FLT_ERR
fi
}
function ProcessFiles {
echo $FILTER | grep -qs $ARGMARK ||

FILTER="$FILTER $ARGMARK"
FILTER="`echo $FILTER | sed -e 's/^ *//' -e 's/ *$//' -e 's/  */ /'`"
EmptyFile $TMPF || return 1
typeset -i ERRFLAG=0
{
echo "`date`  begin session"
[ -n "$FLT_ARCHF" ] && echo "savefile: $FLT_ARCHF"
echo "filter: $FILTER"
while read i; do
ProcOne $i
done < $FILELIST
echo "`date`  end session"
} >> $FLT_LOG
[ $ERRFLAG -ne 0 ] && {
echo "there were errors while processing files"
echo "type 'filter err' to see errors"
return 1
} >&2
return 0
}
#
# shell
#
function Execute {
case $1 in
"") : ;;
help|\?) shift 1; Help $@;;
x|q|quit|exit) return 32;;
env) shift 1; ManageEnv $@;;
undo) Undo;;
log) ShowLog;;
err) ShowErr;;
find) ListFilter $2;;
with) shift 1; ProcExternal "$@";;
!*) shell_args="`echo $*|sed 's/^! *//'`"
shell_cmd="$SHELL"
[ "$shell_args" ] &&
shell_cmd="$shell_cmd -c \"$shell_args\""
eval $shell_cmd
;;
*) ProcFilter "$@"
;;
esac
}
function Shell {
while :; do
read line?"filter> "
[ $? -ne 0 ] && return 0
Execute $line
[ $? -eq 32 ] && return 0
done
}
#
# main
#
BuildEnv
if [ $# -eq 0 ]; then
Shell
else
Execute "$@"
fi
exit

# End of File