#!/bin/bash ## Sequencer script is doing nothing on its own. It is included by a squence script ## which uses the sequencer.sh to provide sequencial operations with or without ## user interaction (see seqTemplate.sh) ## Version information VERSION_REV=5 VERSION_MAJOR=0 VERSION_MINOR=0 ## Start of generic script part QUIET=0 DRY=0 VERBOSE=0 ERNO=0 STEP_ARGS= MAX_STEP=255 ALIAS= TEMPLATE_NAME=seqTemplateExample.sh MISSING_CONF=missingConf.log VERSION_STRING="${VERSION_REV}.${VERSION_MAJOR}.${VERSION_MINOR}" helpSequencer() { echo "Usage: ${0##*/} [OPTIONS] [STEP NUMBER(s) or ALIAS] [STEP ARGUMENTS]" echo echo " [OPTIONS]" echo " --dry-run, -d : Only print to console what would be done" echo " ! Attention - Sequence must support this" echo " --help, -h : Display help" echo " --quiet, -q : Don't ask for permission to execute steps" echo " If called without starting step number, only this help is shown" echo " --verbose, -v : Verbose output (use exe() function to call shell commands in seqs)" echo " ( e.g.: exe apt update )" echo " --version : Display version of sequencer and revision of sequence" echo echo " [STEP NUMBER\"(s)\" 1-${MAX_STEP} or ALIAS]" echo " No STEP or ALIAS : assume 1 as starting point" echo " Single STEP or ALIAS : starting point of sequential process" echo " Multiple STEPS or ALIAS : execute only given steps" echo " execute only one step with using special step 0" echo " ( e.g. only execute step 4: $0 \"4 0\" )" echo " multiple steps need to be given as string" echo " [STEP ARGUMENTS]" echo " * : Arguments will be passed to selected steps as:" echo " \$2 ..." echo " \$1 is always the step number" } # endCheckEmpty [VariableName] [DESCRIPTION] # DESCRIPTION : Optional text for error endCheckEmpty() { local errorText=$1 eval 'local ref=$'$1 if [ ! -z "$2" ] ; then errorText=$2 fi if [ -z $ref ] ; then echo -e " [E] $errorText must not be empty.\nAborting installation." exit 666 fi } existsFunction() { local NOTFOUND=0 declare -F $1 &>>/dev/null || NOTFOUND=1 return $NOTFOUND } saveReturn() { if [ $1 -ne 0 ] ; then ERNO=$1 fi } # endReturn [-f] # -f : force exit with $ERNO without user input endReturn() { if [[ ( $ERNO -ne 0 && $QUIET -ne 0 ) || ( $ERNO -ne 0 && ! -z $1 && $1 == "-f" ) ]] ; then echo echo -e " [E] Return value $ERNO detected.\nAborting installation." exit $ERNO fi if [ $ERNO -ne 0 ] ; then echo echo " [E] Return value $ERNO detected." read -p "End installation: [y]/n? " answer case $answer in [nN]) echo echo Continuing installation... ;; *) echo echo Installation aborted exit $ERNO; ;; esac fi } # addConf # trying to write a file # if exists, one attempt is made to create bck file of it # if all fails, a log file is created with the conflicts to be resolved by the user addConf() { local confMode="" case "$1" in -c) # create a new file confMode="-c" ;; -a) # append to existing file confMode="-a" ;; -s) # skip if CONFIGFILE exists confMode="-s" ;; -m) # only add content to missing conf and warn user confMode="-m" ;; *) # default echo "Parameter 1 (-a|-c|-m|-s) missing for addConf()" exit 0; ;; esac if [ "$DRY" -ne 0 ] ; then echo " [I] Writing $3...dry-run" return 0; fi echo -n " [I] Writing $3..." if [ $confMode != "-m" ] ; then # try writing config directly if [ ! -f "$3" ] ; then case "$confMode" in -c|-s) echo "$2" > "$3" ;; -a) echo "$2" >> "$3" ;; esac echo "ok" return 0 fi if [ $confMode == "-s" ] ; then # if skip is selected, don't try to backup but add confilict entry echo "skipping (exists)" else # try backup existing config local addConfBackup="${3}_`date +%Y%m%d-%H%M%S`.bck" if [ ! -f "$addConfBackup" ] ; then cp -ar "$3" "$addConfBackup" if [ $confMode == "-c" ] ; then echo "$2" > "$3" else echo "$2" >> "$3" fi echo -e "ok \n [W] Existing config saved to ${addConfBackup}" return 0 else echo "nok (backup exists)" fi fi else echo "nok (no change requested)" fi # add configuration to missingConf file if [ "$missingDate" = "" ] ; then missingDate=set echo -n "### " >> "$MISSING_CONF" date >> "$MISSING_CONF" fi echo "#--- $3 ---" >> "$MISSING_CONF" echo "$2" >> "$MISSING_CONF" echo >> "$MISSING_CONF" echo " [W] Check $(realpath "$MISSING_CONF") for configuration conflicts ($3)" return 1 } # execute [-q] # -q: don't stop and don't report step functions which cannot be found # execute given step_ function execute() { local NOTFOUND=0 local NOREPORT=0 if [ $1 == "-q" ] ; then NOREPORT=1 shift fi # check if step function exists declare -F step_$1 &>>/dev/null || NOTFOUND=1 if [ $NOTFOUND -eq 1 ] && [ $NOREPORT -ne 1 ] ; then echo "Step $1 not found" exit 1; fi # don't execute step functions which are not available if [ $NOTFOUND -eq 1 ] ; then return $NOTFOUND fi echo -en "\n [STEP $1] " existsFunction step_${1}_info if [ $? -eq 0 ] ; then step_${1}_info $1 else # Add newline if no info is given echo fi if [ $QUIET -ne 1 ] ; then read -p "Start: y/[n]? " answer case $answer in [yY]) step_$1 $1 "$STEP_ARGS" ;; *) echo Aborting installation at step $1 exit 1; ;; esac else step_$1 $1 "$STEP_ARGS" fi } # checkStep # return 0 - for invalid step # Check sanitiy of step number or # Check if alias exists checkStep() { local rex='^[0-9]+$' local ref="" # Check if string is a number or alias if ! [[ "$1" =~ $rex ]] ; then eval 'ref=$alias_'"$1" # Catch special character after eval if ! [[ "$ref" =~ $rex ]] ; then echo " [E] Invalid step: $1" ref=0 fi else ref=$1 fi if (( $ref < 1 || $ref > $MAX_STEP )) ; then return 0 else return $ref fi } # continous # (max $MAX_STEP) # execute installation continously from given starting step continous() { local step=0 checkStep "$1" step=$? if [[ $step == 0 ]] ; then return 1 fi for ((i=$step; i<=${MAX_STEP}; i++)); do execute -q $i local res=$? if [ $res -ne 0 ] ; then break; fi done } # selection # execute given step list # e.g.: selection -q 1 4 12 selection() { local step=0 local array=("$@") for i in ${array[@]} ; do checkStep "$i" step=$? # stop on step 0 if [ $step -eq 0 ] ; then break else execute $step fi done } # Creating a minimal step definition template createTemplate() { if [ -f $TEMPLATE_NAME ] ; then return 1 fi echo "#!/bin/bash" > $TEMPLATE_NAME echo >> $TEMPLATE_NAME echo "step_1_info() { echo \"My custom step\"; }" >> $TEMPLATE_NAME echo "step_1_alias() { ALIAS=\"begin\"; }" >> $TEMPLATE_NAME echo "step_1() {" >> $TEMPLATE_NAME echo " echo \"Doing something for step \$1 ...\"" >> $TEMPLATE_NAME echo " echo \"Command line arguments starting with argument 2: \$@\"" >> $TEMPLATE_NAME echo " # Use exe for regular command" >> $TEMPLATE_NAME echo " # Use exep \"command\" for commands containing pipes or redirects" >> $TEMPLATE_NAME echo " exe ls" >> $TEMPLATE_NAME echo " exep \"dmesg | grep usb\"" >> $TEMPLATE_NAME echo "}" >> $TEMPLATE_NAME echo >> $TEMPLATE_NAME echo "VERSION_SEQREV=${VERSION_REV}" >> $TEMPLATE_NAME echo ". $0" >> $TEMPLATE_NAME chmod +x $TEMPLATE_NAME return 0 } # Parse alias functions "step_[STEP NUBER]_alias" to create # back reference variable of schema: # alias_[ALIAS]=[STEP NUMBER] parseAlias() { for ((i=1; i<=${MAX_STEP}; i++)); do # Check for alias definition existsFunction step_${i}_alias if [ $? -ne 0 ] ; then continue fi # Function writes global ALIAS variable step_${i}_alias eval 'alias_'$ALIAS'='$i done } # Always display sequencer help and, if available, sequence help displayHelp() { local stepsFound=0 helpSequencer # check if step definition exists by looking for a step_*() function for ((i=1; i<=${MAX_STEP}; i++)); do existsFunction step_${i} if [ $? -ne 0 ] ; then continue fi stepsFound=1 done if [ $stepsFound -eq 0 ] ; then echo -e "\n It seems ${0##*/} was called directly." echo -e " Please create a sequence script first.\n" read -p " Create a template now? y/[n]? " answer case $answer in [yY]) createTemplate if [ $? -eq 0 ] ; then echo -e "\n $TEMPLATE_NAME created." else echo -e "\n $TEMPLATE_NAME exists...Nothing to do!" fi ;; *) echo -e "\n Nothing to do!" ;; esac exit 1; else echo -e "\n Step (= alias) documentation:" for ((i=1; i<=${MAX_STEP}; i++)); do # Display step reference in help if step function exists existsFunction step_${i} if [ $? -ne 0 ] ; then continue fi printf ' Step %3s ' $i # Display alias if exists existsFunction step_${i}_alias if [ $? -eq 0 ] ; then step_${i}_alias echo " = $ALIAS" printf ' : ' else echo -n " : " fi # Display step help only if info function exists existsFunction step_${i}_info if [ $? -eq 0 ] ; then step_${i}_info $i else echo " - step_${i}_info() missing" fi done fi } # showVersion showVersion() { echo "Sequencer ${VERSION_STRING}" echo -n "Seq Revision " if [ ! -z "${VERSION_SEQREV}" ] ; then echo "${VERSION_SEQREV}" else echo "-" fi } exe() { local arr=("$@") if [ $DRY -ne 0 ] ; then echo "-- ${arr[@]}" elif [ $VERBOSE -eq 1 ] ; then (set -x; "${arr[@]}") else "${arr[@]}" fi } # Handle dry run and verbose output for commands containing pipe and/or redirects # exep exep() { if [ $DRY -ne 0 ] ; then echo "-- $1" elif [ $VERBOSE -eq 1 ] ; then echo "++ $1" fi if [ $DRY -eq 0 ] ; then bash -c "$1" fi } main() { local START=0 local EMPTYCALL=1 # options check for arg in "$@" ; do case "$1" in --dry-run|-d) # shows what would be done DRY=1 shift ;; --help|-h) # show only help displayHelp exit 0; ;; --quiet|-q) # detect if option quiet is available QUIET=1 shift ;; --verbose|-v) # set verbose flag VERBOSE=1 shift ;; --version) # version request showVersion exit 0; ;; esac done if [ -z "$1" ] || [ "$1" == "" ] ; then # Empty -> show help displayHelp # Assume starting at one for interactive mode START=1 else EMPTYCALL=0 read -r -a START <<< "$1" shift STEP_ARGS="$@" fi # compatibility check of sequence if [ ! -z $VERSION_SEQREV ] && [ $VERSION_SEQREV -gt $VERSION_REV ] ; then echo " [E] Unsupported sequence revision" showVersion exit 1 fi # exclude older versions if needed if [ ! -z $VERSION_SEQREV ] && [ $VERSION_SEQREV -lt 3 ] ; then echo " [E] Unsupported sequence revision (addConf)" showVersion exit 1 fi if [ -z $VERSION_SEQREV ] ; then echo -e " [W] No sequence revision found. Trying anyway...\n"; fi # check for starting step if [ $EMPTYCALL -ne 0 ] ; then # End here on quiet mode and no step was given if [ $QUIET -eq 1 ] ; then exit 1; fi fi if [ $DRY -ne 0 ] ; then echo echo " [W] Dry run active." echo " Printed commands may not be accurate (e.g. quotation incorrect)" echo " Sequence may ignore dry run" read -p "Press enter to continue or Ctrl + c to abort" fi parseAlias # check if more than one step is given and select execution mode if [ "${#START[@]}" -gt "1" ]; then selection "${START[@]}" else continous $START fi echo echo "${0##*/} finished" } main "$@" exit 0;