#!/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 generated template which can be generated by calling this ## script directly) ## Version information VERSION_REV=11 VERSION_MAJOR=0 VERSION_MINOR=2 ## Start of generic script part QUIET=0 DRY=0 VERBOSE=0 SINGLE=0 ERNO=0 STEP_ARGS= STEP_RETURN=255 MAX_STEP=512 ALIAS= SEQ_CONFIG_NAME=".seqs" SEQ_CONFIG_HOME="$HOME/$SEQ_CONFIG_NAME" SEQ_CONFIG_FILE= 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 " --helpapi, -ha : Display help about build-in supporting functions" echo " (e.g. exe,addconf,echerr,...)" echo " --quiet, -q : Don't ask for permission to execute steps" echo " If called without starting step number, only this help is shown" echo " -qq : Same as --quiet but suppresses regular sequencer.sh output" echo " --single, -s : Execute only one step" echo " If more than one step is requested, only the first will be executed" 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 " (e.g. $0 \"2 4 12\")" echo " multiple steps need to be given as string" echo " [STEP ARGUMENTS]" echo " * : Arguments will be passed to selected steps and step infos as:" echo " \$2 ..." echo " \$1 is always the step number" } helpApi() { echo "sequencer.sh API" echo echo "The sequencer.sh build-in functions are available in all sequence functions:" echo "- step_config" echo " If optional step_config is defined in the sequence, it will be called once before any step." echo "- step_[1-${MAX_STEP}]_info" echo "- step_[1-${MAX_STEP}]_alias" echo "- step_[1-${MAX_STEP}]" echo echo "sequencer.sh global variables:" echo echo " \$QUIET" echo " 0 : default" echo " 1 (-q) : No user interaction (e.g. question to start a step)" echo " 2 (-qq) : 1 and no regular output of sequencer.sh" echo " \$DRY" echo " 0 : default" echo " 1 (-d) : Commands shall only be printed but not executed" echo " \$SEQ_CONFIG_HOME" echo " Path to user specific seq configuration directory" echo " \$SEQ_CONFIG_FILE" echo " Path to user specific seq configuration file" echo " Will be empty if unused" echo echo "sequencer.sh build-in functions:" echo echo " exe [COMMANDLINE]" echo " Execute command line without pipes or redirects (>,<,|)." echo " Supporting: dry-run (-d): only print command without execution" echo " verbose (-v): print command before execution" echo echo " exep \"[COMMANDLINE]\"" echo " See exe, but support for pipes or redirects." echo " Important:" echo " - Shell commands cd, read, ... won't work because COMMANDLINE is started in a new shell." echo " - All apostrophes need to be esacped since the command line is given as string." echo echo " initSeqConfig [OPTION] [TEMPLATE]" echo " Create a configuration file in $SEQ_CONFIG_HOME/ and source it if already existent." echo " [OPTION]" echo " -t : Source config also if created from template" echo " -e : Create empty configuration if no template is found" echo " Returns" echo " 0 : sourced configuration or" echo " (-t) : created and sourced configuration from template" echo " 1 : created configuration from template but not sourced" echo " 2 : created empty configuration" echo " 3 : No configuration created" echo echo " addConf [SOURCE TYPE] " echo " Trying to write or append text or a file () to a destination file." echo " If the CONFIGFILE exists, a backup (name_%Y%m%d-%H%M%S.bck) is saved at the same location." echo " If -s fails or -m, \"$(realpath "$MISSING_CONF")\" is created with the conflicts" echo " to be resolved by the user." echo " " echo " -c : create a new file" echo " -a : append to existing file" echo " -s : skip if CONFIGFILE exists (no backup and entry in missing conf)" echo " -m : only add content to missing conf and warn user" echo " [SOURCE TYPE]" echo " -f : is a file" echo " " echo " Text or file (-f) to create or added to " echo " " echo " Target file to be created or modified." echo echo " step " echo " Executes a single step also by alias. Useful if step numbers get reorganized." echo " dry-run is not applied in this function! The executed step is responsible." echo echo " echoerr [...]" echo " echo to stderr" echo " [...] : all parameter are forwarded to echo" echo echo " echoinfo [...]" echo " echo additional correctly indented line to step info" echo " [...] : all parrameter are forwared to echo" echo echo " endCheckEmpty [DESCRIPTION]" echo " exit 666 if variable is empty" echo " : Name used within eval" echo " [DESCRIPTION] : Additional text for error output" echo echo " saveReturn [ERRORCODE]" echo " Save ERRORCODE if it is != 0 for later use with endReturn" echo echo " getReturn" echo " Return last saved error code" echo echo " endReturn [OPTIONS] [MESSAGE]" echo " Notifys user that there was an error (previously saved by saveReturn," echo " or -o [ERRORCODE]) and asks to continue or end the sequence." echo " Always exits with evaluated error code." echo " [OPTIONS]" echo " -f : force exit without user input, if error code is not 0" echo " -o ERRORCODE : override stored error code and check ERRORCODE" echo " [MESSAGE]" echo " String which is displayed in the error output" echo } # Echo to stderr echoerr() { >&2 echo "$@"; } # Echo additional line to info correctly indented CONTEXT_HELP=0 INDENT_HELP=' : ' INDENTAPPEND_HELP=' ' INDENTAPPEND_INFO=' ' echoinfo() { if [ $CONTEXT_HELP -ne 0 ] ; then printf '%s' "$INDENTAPPEND_HELP"; echo "$@" else printf '%s' "$INDENTAPPEND_INFO"; echo "$@" fi } # endCheckEmpty [DESCRIPTION] # DESCRIPTION : Optional text for error endCheckEmpty() { eval 'local ref=$'$1 if [ -z $ref ] ; then if [ ! -z "$2" ] ; then echoerr -e " [E] $2\n Sequence stopped." else echoerr -e " [E] $1 must not be empty.\n Sequence stopped." fi exit 666 fi } existsFunction() { local NOTFOUND=0 declare -F $1 &>>/dev/null || NOTFOUND=1 return $NOTFOUND } # saveReturn # Function returns with in case step wants additional evaluation saveReturn() { if [ $1 -ne 0 ] ; then ERNO=$1 fi return $ERNO } # getReturn # Returns latest saved $ERNO getReturn() { return $ERNO } # endReturn [-f] [-o ERRORCODE] [MESSAGE] # -f : force exit with $ERNO without user input # -o : override and check given error code # MESSAGE : Custom error message # endReturn() { local arg local forceExit=0 local errorCode=$ERNO local endMessage="" for arg in "$@" ; do case "$1" in -f) forceExit=1 shift ;; -o) shift local rex='^[-]*[0-9]+$' # Check if string is a number or alias if [[ "$1" =~ $rex ]] ; then errorCode=$1 else echoerr " [W] Ignoring invalid error code: $1" fi shift ;; "") break ;; *) endMessage="$@" break ;; esac done if [[ ( $errorCode -ne 0 && $QUIET -ne 0 ) || ( $errorCode -ne 0 && $forceExit -ne 0 ) ]] ; then echo if [ "$endMessage" != "" ]; then echoerr -e " [E] $endMessage\n Sequence stopped" else echoerr -e " [E] Return value $errorCode detected.\n Sequence stopped" fi exit $errorCode fi if [ $errorCode -ne 0 ] ; then echo if [ "$endMessage" != "" ]; then echoerr -e " [W] $endMessage" else echoerr " [W] Return value $errorCode detected." fi read -p "End sequence: [y]/n? " answer case $answer in [nN]) # reset saved error code if user chooses to continue ERNO=0 echo echo " [I] Continuing sequence..." ;; *) echo echoerr " [E] Sequence stopped" exit $errorCode; ;; esac fi } # initSeqConfig [OPTION] [TEMPLATE] # Create a configuration file in the users' home. # Source it if already existent # [OPTION] # -t : Source config also if created from template # -e : Create empty configuration if no template is found # Return # 0 : Sourced configuration or # (-t) : created and sourced configuration from template # 1 : Created configuration from template but not sourced # 2 : Created empty configuration # 3 : No configuration created initSeqConfig() { local arg local sourceAlways=0 local createEmpty=0 for arg in "$@" ; do case "$1" in -t) sourceAlways=1 shift ;; -e) createEmpty=1 shift ;; esac done local configLoc="$SEQ_CONFIG_HOME/$1" local configTemplate="$2" # Create config subdir in users home if [ ! -e "$SEQ_CONFIG_HOME/" ] ; then echo -n " [I] Creating $(realpath $SEQ_CONFIG_HOME)..." exe mkdir -p "$SEQ_CONFIG_HOME" && echo "Ok" || echo "Nok" fi if [ -s "$configLoc" ] ; then echo " [I] Using configuration file: $configLoc" SEQ_CONFIG_FILE="$configLoc" . "$configLoc" return 0 fi # Config does not exist, check for template if [ -s "$configTemplate" ] ; then # Check first if there is an existing configuration at the templates position local configExists="$(dirname $configTemplate)/$1" if [ -s "$configExists" ] ; then exe mv "$configExists" "$configLoc" endReturn -o $? "Unable to use existing configuration: $configExists" echoerr " [I] Using existing configuration: $configExists" echoerr " (Moved to $SEQ_CONFIG_HOME)" . "$configLoc" return 0 fi exe cp -ar "$configTemplate" "$configLoc" endReturn -o $? "Failed to create configuration" if [ $sourceAlways -eq 0 ] ; then echoerr " [W] Seq configuration created from template but not used" echoerr " Please modify "$configLoc" first and restart sequence" return 1 else echo " [W] Using seq configuration from template $configTemplate" echo " (Copied to $SEQ_CONFIG_HOME)" SEQ_CONFIG_FILE="$configLoc" . "$configLoc" return 0 fi else echo " [W] Seq configuration template not found" fi if [ $createEmpty -ne 0 ] ; then # Create empty config file echo " [W] Created empty configuration file $configLoc" exe touch "$configLoc" return 2 fi echoerr " [E] No seq configuration created" return 3 } # addConf [FILE_MODE] # 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 arg local confMode="" local transferCmd="echo" for arg in $@ ; do case "$1" in -c) # create a new file confMode="-c" shift ;; -a) # append to existing file confMode="-a" shift ;; -s) # skip if CONFIGFILE exists confMode="-s" shift ;; -m) # only add content to missing conf and warn user confMode="-m" shift ;; -f) # choose if source is a file or text transferCmd="cat" shift ;; *) # default if [ "$confMode" == "" ] ; then echoerr " [E] Parameter 1 (-a|-c|-m|-s) missing for addConf()" exit 0; fi ;; esac done local source="$1" local dest="$2" if [ "$transferCmd" == "cat" ] && [ ! -f "$source" ] ; then echoerr " [E] Source: \"$source\" does not exist" return 1; fi if [ "$dest" == "" ] ; then echoerr " [E] Destination empty" return 1; fi if [ "$DRY" -ne 0 ] ; then echo " [I] Writing $dest ...dry-run" return 0; fi echo -n " [I] Writing $dest ..." if [ $confMode != "-m" ] ; then # try writing config directly if it doesn't exist if [ ! -f "$dest" ] ; then $transferCmd "$source" > "$dest" 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="${dest}_`date +%Y%m%d-%H%M%S`.bck" if [ ! -f "$addConfBackup" ] ; then cp -ar "$dest" "$addConfBackup" if [ $confMode == "-c" ] ; then $transferCmd "$source" > "$dest" else $transferCmd "$source" >> "$dest" fi echo -e "ok \n [I] Existing config saved to ${addConfBackup}" return 0 else echo "nok" echoerr " [W] backup exists" fi fi else echo -e "ok \n [I] no change requested" fi # add configuration to missingConf file if [ "$missingDate" = "" ] ; then missingDate=set echo -n "### " >> "$MISSING_CONF" date >> "$MISSING_CONF" fi local helpText="needs to be added manually" if [ "$confMode" == "-s" ] ; then helpText="not overwritten" fi echo "#--- \"$dest\" $helpText (Option: $confMode) ---" >> "$MISSING_CONF" $transferCmd "$source" >> "$MISSING_CONF" echo >> "$MISSING_CONF" echoerr " [W] Check $(realpath "$MISSING_CONF") for configuration conflicts ($dest)" 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 echoerr " [E] Step $1 not found" exit 1; fi # don't execute step functions which are not available if [ $NOTFOUND -ne 0 ] ; then return $NOTFOUND fi if [ $QUIET -ne 2 ] ; then echo -en "\n [STEP $1] " existsFunction step_${1}_info if [ $? -eq 0 ] ; then step_${1}_info $1 "${STEP_ARGS[@]}" else # Add newline if no info is given echo fi fi if [ $QUIET -eq 0 ] ; then read -p "Start: (y)es/[n]o/(s)kip? " answer case $answer in [yY]) step_$1 $1 "${STEP_ARGS[@]}" STEP_RETURN=$? ;; [sS]) # skip step STEP_RETURN=0 return 0 ;; *) local stepId="$1" # Display alias if exists existsFunction step_${1}_alias if [ $? -eq 0 ] ; then step_${i}_alias stepId="$ALIAS" fi echoerr " [I] Stopping sequence at step: $stepId" exit 1; ;; esac else step_$1 $1 "${STEP_ARGS[@]}" STEP_RETURN=$? fi } # checkStep # return 0 - for invalid step # return Step Number # 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 ref=0 fi else ref=$1 fi if (( $ref < 1 || $ref > $MAX_STEP )) ; then echoerr " [E] Invalid step: $ref" return 0 else existsFunction step_$ref if [ $? -eq 0 ] ; then return $ref else # step doesn't exist echoerr " [E] Invalid step: $ref" return 0 fi fi } # step # execute given step step() { local stepNo=0 local stepArgs=("$@") checkStep "$1" stepNo=$? if [ "$stepNo" == "0" ] ; then return 1 else step_$stepNo "${stepArgs[@]}" fi } # continous # (max $MAX_STEP) # execute sequence continously from given starting step continous() { local i local step=0 checkStep "$1" step=$? if [[ $step == 0 ]] ; then return 1 fi if [ $QUIET -ne 2 ]; then echo " [I] Starting sequence $(realpath $0) ..."; fi for ((i=$step; i<=${MAX_STEP}; i++)); do execute -q $i local res=$? if [ $res -ne 0 ] ; then break fi if [ $STEP_RETURN -ne 0 ] ; then break fi done return $STEP_RETURN } # selection # execute given step list # e.g.: selection -q (1, 4, 12) selection() { local i local step=0 local array=("$@") if [ ${#array[@]} -eq 0 ] ; then return 1 fi if [ $QUIET -ne 2 ]; then echo " [I] Starting sequence $(realpath $0) ..."; fi for i in ${array[@]} ; do checkStep "$i" step=$? if [ $step -eq 0 ] ; then return 1 else execute $step fi done return $STEP_RETURN } # Creating a minimal seq (step definition) template createTemplate() { if [ -f $TEMPLATE_NAME ] ; then return 1 fi echo "#!/bin/bash" > $TEMPLATE_NAME echo >> $TEMPLATE_NAME echo "toolName=mytool" >> $TEMPLATE_NAME echo >> $TEMPLATE_NAME echo "# Get script working directory" >> $TEMPLATE_NAME echo "# (when called from a different directory)" >> $TEMPLATE_NAME echo "WDIR=\"\$( cd \"\$( dirname \"\${BASH_SOURCE[0]}\" )\" >>/dev/null 2>&1 && pwd )\"" >> $TEMPLATE_NAME echo "CONFIG=0" >> $TEMPLATE_NAME echo "CONFIG_FILE_NAME=\"\${toolName}.cfg\"" >> $TEMPLATE_NAME echo "CONFIG_FILE_TEMPLATE=\"\$WDIR/\${CONFIG_FILE_NAME}.example\"" >> $TEMPLATE_NAME echo >> $TEMPLATE_NAME echo "step_config() {" >> $TEMPLATE_NAME echo " echo \"Called once before executing steps.\"" >> $TEMPLATE_NAME echo " ## e.g. to source a config file manually:" >> $TEMPLATE_NAME echo " #. \"\$CONFIG_FILE\"" >> $TEMPLATE_NAME echo " ## or to use sequencer api:" >> $TEMPLATE_NAME echo " #initSeqConfig \"\$CONFIG_FILE_NAME\" \"\$CONFIG_FILE_TEMPLATE\"" >> $TEMPLATE_NAME echo " #if [ \$? -eq 0 ] ; then" >> $TEMPLATE_NAME echo " # CONFIG=1" >> $TEMPLATE_NAME echo " #fi" >> $TEMPLATE_NAME echo "}" >> $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() { local i 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 } # displayHelp [NO TEMPLATE] # [NO TEMPLATE] # 0 (default) : Ask for template creation # 1 : Do not ask for template creation # Always display sequencer help and, if available, sequence help displayHelp() { local i local createTemplate=1 local stepsFound=0 CONTEXT_HELP=1 helpSequencer if [ ! -z $1 ] && [ $1 -eq 1 ] ; then createTemplate=0 fi # 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" if [ $createTemplate -ne 0 ] ; then 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 fi 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 '%s' "$INDENT_HELP" 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 echo fi CONTEXT_HELP=0 } # 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 -n "--" fi if [ $DRY -ne 0 ] || [ $VERBOSE -eq 1 ] ; then (set -x; : "${arr[@]}") fi if [ $DRY -eq 0 ] ; then "${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 arg 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 1 exit 0; ;; --helpapi|-ha) #show build-in functions helpApi exit 0; ;; --quiet|-q|-qq) # detect if option quiet is available if [ "$1" == "-qq" ] ; then QUIET=2 else QUIET=1 fi shift ;; --single|-s) # execute only one step and stop SINGLE=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 echoerr " [E] Unsupported sequence revision" showVersion exit 1 fi # exclude older versions if needed if [ ! -z $VERSION_SEQREV ] && [ $VERSION_SEQREV -lt 3 ] ; then echoerr " [E] Unsupported sequence revision (addConf)" showVersion exit 1 fi if [ -z $VERSION_SEQREV ] ; then echoerr -e " [W] No sequence revision found. Trying anyway...\n"; fi # End here on quiet mode and no step was given if [ $EMPTYCALL -ne 0 ] && [ $QUIET -ne 0 ] ; then exit 1; fi if [ $DRY -ne 0 ] && [ $QUIET -eq 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 # run configuration for seq only if available and if first step is valid existsFunction step_config if [ $? -eq 0 ] ; then checkStep "${START[0]}" if [ $? -ne 0 ] ; then echo " [I] Configuring sequence (step_config) ..." step_config else return 1 fi fi # check if more than one step is given and select execution mode if [ $SINGLE -ne 0 ] ; then selection "${START[0]}" elif [ "${#START[@]}" -gt "1" ]; then selection "${START[@]}" else continous $START fi } main "$@" MAINRETURN=$? if [ $QUIET -ne 2 ] ; then echo echo "${0##*/} finished" fi exit $MAINRETURN;