function saveReturn returns with the same errno it saved refactor documentation replace installation with sequence and aborted with stopped New starting output with absolute path to seq
575 lines
13 KiB
Bash
Executable File
575 lines
13 KiB
Bash
Executable File
#!/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=6
|
|
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.\n Sequence stopped."
|
|
exit 666
|
|
fi
|
|
}
|
|
|
|
existsFunction() {
|
|
local NOTFOUND=0
|
|
declare -F $1 &>>/dev/null || NOTFOUND=1
|
|
return $NOTFOUND
|
|
}
|
|
|
|
# saveReturn <ERRNO>
|
|
# Function returns with <ERRNO> 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] [MESSAGE]
|
|
# -f : force exit with $ERNO without user input
|
|
# MESSAGE : Custom error message
|
|
#
|
|
endReturn() {
|
|
local forceExit=0
|
|
local endMessage=""
|
|
|
|
for arg in "$@" ; do
|
|
case "$1" in
|
|
-f)
|
|
forceExit=1
|
|
skipStep=0
|
|
shift
|
|
;;
|
|
"")
|
|
break
|
|
;;
|
|
*)
|
|
endMessage="$@"
|
|
break
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ ( $ERNO -ne 0 && $QUIET -ne 0 ) || ( $ERNO -ne 0 && $forceExit -ne 0 ) ]] ; then
|
|
echo
|
|
if [ "$endMessage" != "" ]; then
|
|
echo -e " [E] $endMessage\n Sequence stopped"
|
|
else
|
|
echo -e " [E] Return value $ERNO detected.\n Sequence stopped"
|
|
fi
|
|
exit $ERNO
|
|
fi
|
|
if [ $ERNO -ne 0 ] ; then
|
|
echo
|
|
if [ "$endMessage" != "" ]; then
|
|
echo -e " [W] $endMessage"
|
|
else
|
|
echo " [W] Return value $ERNO detected."
|
|
fi
|
|
read -p "End sequence: [y]/n? " answer
|
|
case $answer in
|
|
[nN])
|
|
echo
|
|
echo " [I] Continuing sequence..."
|
|
;;
|
|
*)
|
|
echo
|
|
echo " [E] Sequence stopped"
|
|
exit $ERNO;
|
|
;;
|
|
esac
|
|
fi
|
|
}
|
|
|
|
# addConf <CONF_MODE> <CONFIGTEXT> <CONFIGFILE>
|
|
# 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] <Step Number>
|
|
# -q: don't stop and don't report step functions which cannot be found
|
|
# execute given step_<Step Number> 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 " [I] Stopping sequence at step $1"
|
|
exit 1;
|
|
;;
|
|
esac
|
|
else
|
|
step_$1 $1 $STEP_ARGS
|
|
fi
|
|
}
|
|
|
|
# checkStep <Step Number or Alias>
|
|
# 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 <Starting Step Number>
|
|
# (max $MAX_STEP)
|
|
# execute sequence 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 <Step Number List (separated by space)>
|
|
# 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 <COMMAND TO RUN AS STRING>
|
|
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 ] && [ $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
|
|
|
|
echo " [I] Staring sequence $(realpath $0) ..."
|
|
|
|
# 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;
|