Pages

Friday, August 12, 2011

A silly script

Scripting Stuff

I spent some time writing this script, mostly because I wanted something "real" to try parameter substitution on.

Secondary aims were "dealing" with stupidly named files efficiently.
(turns out that Simple is Best.. LOL. What a surprise)

Lessons/Tensions :
Control Potential User Input firmly.
Example :
don't expect SOME_VAR=${VAR##*/} to survive user or any human input.
(or to sanely process input generated by things like browsers file managers , user-land tools in general.
ditto anything that feed a "for" loop.

Solutions:
Either sanitise / (sanity check) / correct and risk breaking the "Rule Of Least Surprise" for the user. (and how can you know what will be surprising)
Or use a strong and simple output naming scheme.

Especially if later access is desirable.

When feeding a loop examine alternative procedural paths...
Is it really so bad to not load an array or fat variable when you can process NOW and live with all the guff in the input file names. (even if it means writing out to file)

The rest of the parameter sub stuff went fine though :)
Its exactly the same as say: mkdir foo-{one,two,three,four}
(well more or less :)


So I ended up using known good techniques that I have used for eons and relearnt why I
used them in the first place.

e.g.

find ${TARGET_DIR:-$(pwd)} -name ${SUFFIX:-jpg} -type f -print0 -exec file -iL '{}' \;

Is sort of cool and I guess efficient, "atomic/granular" enough etc ....
But (me type bad) fragile when a user can ask for the suffix to contain spaces or dots or heaven knows what. Certainly can not be trusted to feed a for loop without serious amount of even more obfuscate stuff. Its also tricky to contain the path as I wanted to at that point in the process.

(that would process sub directories for example and always returns 0)

Simpler is:
if [ ! -f "*.$SUFFIX" ]; then
this foo
else
that foo
fi

(or ls *.$SUFFIX)

Which is way more maintainable as well :)

_As Long as each returned "string" is dealt with down stream of the for THING rule._
i.e. no need to clean or contain the "Broken \file(1) name.suff"

In short:

Its better to eat humble pie now than later :)

## example script: still less than elegant but working just fine ###
## Its way too big here.. interestingly it doesn't feel big under emacs :)
## use as you will or not ...


#!/bin/bash
# -*- shell-script -*-
####################################################
# This script was written so I could test some
# bash parameter subsitution out. (and find foo (but there I failed))
# As a side effect and quite orthogonally (lol) it:
# Creates an XML file that you feed to the GNOME background manager
# That (XML) file describes a desktop background slide show.
####################################################

# housekeeping
VERSION=1.0
APP=$(basename $0 .sh)
APP_V="$APP-$VERSION"
# change this to you or something
AUTHORS="Peter Gossner"
MAINTAINERS=$AUTHOR
CONTACT_MAIL="Peter Gossner "
# copyright year(s)
CPT_YEARS="2011"

# configure Default Durations
D_STATIC=600 #CL -s -S
D_TRANSIT=5 #CL -t -T
# configure supported image suffix (and assumed types)
IMAGE_SUFXS="jpg png svg" #CL -f -F

# auto configs
# start animation time this is parsed by gnome on startup (I think)
# yep it is see: ~/.gconf/gnome/background/%gconf.xml
# and probably other places... wonders about %name.xml ....

ST_YEAR=" $(date +%Y)"
ST_MONTH=" $(date +%m)"
ST_DAY=" $(date +%d)"
ST_HOUR=" $(date +%H)"
ST_MIN=" $(date +%M)"
ST_SEC=" 00"
# I heart my date all up :)

# directory of images to work with
IMAGE_DIR="$(pwd)" #CL -d -D

# error returns
E_PERM=84
E_OPTERROR=85
E_BADDIR=86
E_INTERNAL=87


# utes
PS="$$"
INSTALL="/usr/local/bin"

main(){
# main control here
# called after file is parsed (see eof)

commandLineOptions "$@"
defineVariables
checkDir
initXmlFile
gatherSuffixList
closeXmlFile "$LoopFile"

echo -e "$APP completed.
You would feed > >
\" $IMAGE_DIR/$XML_FILE \"
> > to gnomes background tool.\nHave a nice $(date +%A)"

}

commandLineOptions(){
# gather user options
while getopts "d:D:f:F:is:S:t:T:hHvV" OPTION
do
case $OPTION in
d | D)
# directory to gather images from
IMAGE_DIR="$OPTARG"
printf "%s \n" "-$OPTION setting image directory = $OPTARG"
# make sure we have at least a slightly sane input"
if [ "$IMAGE_DIR" == "" ] || [ "$IMAGE_DIR" == "." ] ; then
IMAGE_DIR="$(pwd)"
printf "%s \n" "-$OPTION !ouch! Resetting: $OPTARG to $IMAGE_DIR"
fi
;;
f | F)
#limit images search to this list of image suffixes
IMAGE_SUFXS="$OPTARG"
echo -e "-$OPTION exclusive file suffixes = $OPTARG\n"
;;
i )
INSTALL=${OPTARG:-/usr/local/bin}
installScript
;;


s | S)
# seconds to be static for
D_STATIC="$OPTARG"
echo -e "-$OPTION holding images static for $OPTARG secs\n"
;;
t | T)
# seconds to transition between images
D_TRANSIT="$OPTARG"
echo -e "-$OPTION transition images over $OPTARG secs\n"
;;
h)
helpOptions
exit 0
;;
H)
helpMan
exit 0
;;
v | V)
echo -e "$APP version $VERSION\nLegalese:\nReleased under terms and conditions of the GPL version 3 or later.\n$APP_V by $AUTHORS $CPT_YEARS\nContact: $CONTACT_MAIL"
exit 0
;;
* )
echo -e "\t unknown option or invalid (missing) argument given \n"
usages
exit $E_OPTERR
;;
esac
shift $(( $OPTIND -1 ))
OPTIND=1
done
}


helpOptions(){
echo -e "\n$APP_V
Usage:
${0##*/}
Options:
-H more information and instructions
-h options in conventional format. (this page)
-i /directory/path/to/install/this script to. [default: $INSTALL ]
-f or -F \"quoted list of formats (only)\". [defaults: $IMAGE_SUFXS ]
-d or -D /path/to/directory/of/images/to/process. [default: ./]
-s or -S nnn (seconds to hold each image static) [default $D_STATIC]
-t or -T nnn (transition / crossfade time in seconds) [default $D_TRANSIT]
-v or -V version and legals

Examples:
(access system wide and have the images in place for that)
~$ sudo ./$APP -s 300 -t 10 -d /usr/share/backgrounds/myFavsSlideShow

(slides in home directory, default timings, jpg and png files only)
~$ ./$APP -d ~/MyOtherSlideShow -f \"jpg png\"

"

}

helpMan(){
# the manual
echo -e "
$APP_V

==========================================
\"Help and Info\"
==========================================

$APP creates an XML file that slideshows images.
You may feed this to the gnome desktop background system.
e.g. \"system\" > \"appearance\" > \"background\" > \"add\"

$APP uses full path names to the image directory.
Its just a plain old shell script that outputs plain old text.
Feel free to edit either to taste.

Instructions / Guidelines
==========================
The XML structure is that used by the example supplied
in your default GNOME install:
It is not formally defined by any XML prolog
or distant \"normative\" schema or DTD.
(I did not look too hard )
see: /usr/share/backgrounds/cosmos/ for the original

Simply:
- mkdir myCoolSlideShow (or whatever)
- cp or mv or ln -s create:
jpg png or svg images to populate that directory
- (optionally) rename or prepend some string to
place them in the order you would like.
e.g.
1-image-name.jpg
2-anotherImageName.jpg
3-yetSomeOtherSnap.png
4-whatIdidLastSummer.svg
- (optionally) ${0##*/} -i $INSTALL
- cd to \"myCoolSlideShow\" and $APP
(for defaults)
- load the xml file with the background tool
- admire your near cinematic skills ;>
- watch your system grind away at rescaling all those svg files !
the transitions seem to produce the highest load (-t)
endnote:
There is probably a GUI tool that does this easier and betterer.
Search with your installer suite of choice.
( I was just brushing up on my BASH really :)

" | more
# this was so cute I couldn't resist
helpOptions | more
exit 0
}

usages(){
echo -e "$APP -[[hHvV][tTsSfFDD \"argument\"]]\n\ttry:\n\t$APP -h (for options help)\n\t$APP -H for \"more\" information"
exit $E_OPTERROR
}

checkDir(){
# image directory access and write
if [ -z $IMAGE_DIR ] || [ "$IMAGE_DIR" == "" ]; then
bailout $E_INTERNAL "Target image directory is not set, or has been hand set to \"\""
fi

if [ ! -d $IMAGE_DIR ] ; then
bailout $E_BADDIR "No such place as : $IMAGE_DIR"
elif [ ! -x $IMAGE_DIR ] ; then
bailout $E_BADDIR "Access refused to $IMAGE_DIR"
else
# echo "OK: $IMAGE_DIR exists"
START_DIR=$(pwd)
# move into the working directory
cd $IMAGE_DIR || bailout $E_PERM "Unable to cd to $IMAGE_DIR"
FILE_PATH="$(pwd)"
fi
}

initXmlFile(){
if [ -e $XML_FILE ]; then
echo -e "Note: $XML_FILE exists. Backing it up."
cp --backup=numbered $XML_FILE $XML_FILE.prev || bailout $E_PERM
fi
# clears any existing file
echo -e "$XML_OPEN" > $XML_FILE || bailout $E_PERM

for P in $ST_{YEAR,MONTH,DAY,HOUR,MIN,SEC} ;
do
# with indent
echo -e " $P" >> $XML_FILE
done
# no indent
echo -e "
" >> $XML_FILE
}

defineVariables(){
# build up "prerun" list of variables
FILE_LIST=""
REJECT_LIST=""
IMAGE_DIR=${IMAGE_DIR?-$(pwd)}
XML_FILE=$APP-slides.xml
# ${foo##*/} works except when the user puts a trailing slash
# so twice check or just use the clean app name

# users real user name
if [ -z $SUDO_USER ]; then
RUNBY="$USER"
else
RUNBY="$USER for $SUDO_USER" || RUNBY="$USERNAME"
fi
# TODO a tidyup on output indents
#XML strings
XML_START="\n\n"
XML_CLOSE="\n
\n"
STATIC_OPEN=" \n $D_STATIC"
STATIC_CLOSE="
"
TRANSIT_OPEN=" \n $D_TRANSIT"
START=" \n$ST_YEAR\n$ST_MONTH\n$ST_DAY\n$ST_HOUR\n$ST_MIN\n$ST_SEC\n\n"

XML_OPEN="$XML_START\n"

# forcing clean return
return 0
}

gatherSuffixList(){
# this all got too silly with file so ls and globs to the rescue...
# simple safe list
ti=0 ; mi=0 ; ri=0

for S in $IMAGE_SUFXS ;
do
# the intention here is Short Circuit on no match for file suffix
any="$( ls ./*.$S )"
s_any=$?
if [ -z "$any" ] || [ "$s_any" != "0" ]; then
echo "no $S suffix files found"
continue
else
# not really required huge bloat :)
files_ok="TRUE"
fi

case $S in
jpeg | jpg | JPG )
magicMatch="image/jpeg"
;;
svg | SVG )
magicMatch="image/svg"
;;
png | PNG )
magicMatch="image/png"
;;
gif | GIF )
magicMatch="image/gif"
;;
* )
echo "Achtung : no test available for suffix \"$S\" "
bailout ${E_OPTERROR:-85} "This is probably a commandline typo \n.\t Equally it may be an unsupported file suffix."
;;
esac

for I in *.$S ; do
(( ti++ ))
checking="$(file -iL "$I")"
matching="$(echo $checking | grep $magicMatch)"


if [ "$?" == "0" ]; then
(( mi++ ))

FILE_LIST="$FILE_LIST\n"$I""
addToXMLfile "$I"
else
(( ri++ ))
echo -e "Note: rejecting $I
fails basic file type \"magic\" tests\n
$checking\n
$I should have: $magicMatch as part of its \"magic\" \n"

REJECT_LIST="$REJECT_LIST\n$checking\ndoes not match $magicMatch"
fi
done
done

echo "$RUNBY FYI: matched $mi and rejected $ri of $ti files found"
return 0
}

addToXMLfile(){

FILE="$IMAGE_DIR/$1"

# capture/store the first image for start and end (loop)
if [ "$mi" -eq "1" ]; then
LoopFile="$FILE"
echo -e "$STATIC_OPEN

$FILE$STATIC_CLOSE
$TRANSIT_OPEN
$FILE" >> $XML_FILE
else
echo -e "
$FILE\n

$STATIC_OPEN
$FILE
$STATIC_CLOSE
$TRANSIT_OPEN
$FILE" >> $XML_FILE
fi
return 0
}

closeXmlFile(){
# add the return back to the first image
# and close of the xml structure

if [ "$ti" -gt "1" ]; then
echo -e "$LoopFile


\n" >> $XML_FILE

else
# CLUNK clunk Clunkity clunk
rm -fv $XML_FILE
bailout $E_BADDIR "$APP unable to find images to work with"
fi
return
}

installScript(){
# simple installer
if [ ! -d $INSTALL ];
then
mkdir -pvm 2755 $INSTALL || bailout $E_PERM "Unable to make install directory. Try\n sudo $0 -I ?"
else
echo "Installing $APP to $INSTALL"
fi

cp -fv $0 $INSTALL/${0##*/} || bailout $E_PERM ":Try \" sudo $0 -$OPTION $INSTALL \"\n "
chmod u+x $INSTALL/${0##*/}
exit 0
}


bailout(){
# # error returns
# E_PERM=84
# E_OPTERROR=85
# E_BADDIR=86
# E_INTERNAL=87 # feet off the ground see: AU cricket

E_NO=$1
echo -e "\nNOTE: $APP process $PS exiting prematurely\n\tPerhaps the following trace may assist.\n\t[line number] [at function (aka. sub routine)] [of filename]"
# give some sort of backtrace
caller 0
caller 1
case $E_NO
in
1)
echo -e "$APP_V General Error"
;;
84)
echo -e "Permissions Error. Perhaps you need root ?"
;;
85)
echo -e "Probable command line option error"
;;
86)
echo -e "Directory access error ?"
;;
87)
echo -e "$APP_V INTERNAL error. (my bad)"
;;
*)
echo -e "Unspecified Operational Error. Possibly unforeseen (unplaned) use ?"
esac

if [ ! -z "$2" ]; then
echo -e "Error: $@"
else
echo "Error $1"
fi

exit $E_NO
}


main "$@"

# formal exit with last returned value
exit

# The flaming end
# crickey what a mess


2 comments:

  1. Way too big as an example
    And a lot of twists to avoid a nested loop :)

    ReplyDelete
  2. Bugger the blog thing has removed the xml markup (how helpful)
    Just ignore this thing ... entry ...code
    Its now all broken ...

    And I'm not wasting any more time on it .
    If you want a copy ask.

    ReplyDelete