forked from HPR/hpr-tools
		
	Added InternetArchive/function_lib.sh
InternetArchive/function_lib.sh: new file; subset of
    '~/bin/function_lib.sh' which is referred to in a number of scripts.
    It contains relevant functions such as 'yes_no' and 'define_colours'.
			
			
This commit is contained in:
		
							
								
								
									
										477
									
								
								InternetArchive/function_lib.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										477
									
								
								InternetArchive/function_lib.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,477 @@ | |||||||
|  | # | ||||||
|  | # Bash Functions for sourcing in scripts. | ||||||
|  | # | ||||||
|  | # This is a copy of some of the functions for HPR project use. | ||||||
|  | # | ||||||
|  | #       ${HOME}/HPR/InternetArchive/function_lib.sh | ||||||
|  | # | ||||||
|  | # Revised: 2024-06-15 18:13:28 | ||||||
|  | # | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #===  FUNCTION  ================================================================ | ||||||
|  | #        NAME:  abs_diff | ||||||
|  | # DESCRIPTION:  Compute the difference between two numbers, returning an | ||||||
|  | #               absolute value | ||||||
|  | #  PARAMETERS:  $1      first number | ||||||
|  | #               $2      second number | ||||||
|  | #     RETURNS:  The absolute value as a string | ||||||
|  | #=============================================================================== | ||||||
|  | function abs_diff { | ||||||
|  |     local p1="${1:?Usage: abs_diff n1 n2}" | ||||||
|  |     local p2="${2:?Usage: abs_diff n1 n2}" | ||||||
|  |  | ||||||
|  |     echo $((p1 >= p2 ? p1 - p2 : p2 - p1)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #===  FUNCTION  ================================================================ | ||||||
|  | #          NAME:  cleanup_temp | ||||||
|  | #   DESCRIPTION:  Cleanup temporary files in case of a keyboard interrupt | ||||||
|  | #                 (SIGINT) or a termination signal (SIGTERM) and at script | ||||||
|  | #                 exit | ||||||
|  | #    PARAMETERS:  * - names of temporary files to delete | ||||||
|  | #       RETURNS:  Nothing | ||||||
|  | #=============================================================================== | ||||||
|  | function cleanup_temp { | ||||||
|  |     for tmp in "$@"; do | ||||||
|  |         [ -e "$tmp" ] && rm --force "$tmp" | ||||||
|  |     done | ||||||
|  |     exit 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #===  FUNCTION  ================================================================ | ||||||
|  | #         NAME: _DEBUG | ||||||
|  | #  DESCRIPTION: Writes one or more messages if in DEBUG mode. Each argument is | ||||||
|  | #               seen as a message and is written on a separate line. | ||||||
|  | #               References the global variable 'DEBUG' which is expected to be | ||||||
|  | #               True if debug output is wanted. | ||||||
|  | #   PARAMETERS: List of messages | ||||||
|  | #      RETURNS: Nothing | ||||||
|  | #=============================================================================== | ||||||
|  | _DEBUG () { | ||||||
|  |     [ "$DEBUG" == 0 ] && return | ||||||
|  |     for msg in "$@"; do | ||||||
|  |         printf 'D> %s\n' "$msg" | ||||||
|  |     done | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #===  FUNCTION  ================================================================ | ||||||
|  | #         NAME: yes_no | ||||||
|  | #  DESCRIPTION: Read a Yes or No response from STDIN (only these values are | ||||||
|  | #               accepted) and return a suitable numeric value. | ||||||
|  | #   PARAMETERS: 1 - Prompt string for the read | ||||||
|  | #               2 - Default value (optional) | ||||||
|  | #      RETURNS: 0 for a response of Y or YES, 1 otherwise | ||||||
|  | #=============================================================================== | ||||||
|  | yes_no () { | ||||||
|  |     local prompt="${1:?Usage: yes_no prompt [default]}" | ||||||
|  |     local default="${2^^}" | ||||||
|  |     local ans res | ||||||
|  |  | ||||||
|  |     if [[ $prompt =~ %s ]]; then | ||||||
|  |         if [[ -n $default ]]; then | ||||||
|  |             default=${default:0:1} | ||||||
|  |             # shellcheck disable=SC2059 | ||||||
|  |             # { | ||||||
|  |             case "$default" in | ||||||
|  |                 Y) printf -v prompt "$prompt" "[Y/n]" ;; | ||||||
|  |                 N) printf -v prompt "$prompt" "[y/N]" ;; | ||||||
|  |                 *) echo "Error: ${FUNCNAME[0]} @ line ${BASH_LINENO[0]}: Default must be 'Y' or 'N'" | ||||||
|  |                    exit 1 | ||||||
|  |                    ;; | ||||||
|  |             esac | ||||||
|  |             # } | ||||||
|  |         else | ||||||
|  |             echo "Error: ${FUNCNAME[0]} @ line ${BASH_LINENO[0]}: Default required" | ||||||
|  |             exit 1 | ||||||
|  |         fi | ||||||
|  |     fi | ||||||
|  |  | ||||||
|  |     # | ||||||
|  |     # Loop until a valid input is received | ||||||
|  |     # | ||||||
|  |     while true; do | ||||||
|  |         # | ||||||
|  |         # Read and handle CTRL-D (EOF) | ||||||
|  |         # | ||||||
|  |         read -r -e -p "$prompt" ans | ||||||
|  |         res="$?" | ||||||
|  |         if [[ $res -ne 0 ]]; then | ||||||
|  |             echo "Read aborted" | ||||||
|  |             return 1 | ||||||
|  |         fi | ||||||
|  |  | ||||||
|  |         [ -z "$ans" ] && ans="$default" | ||||||
|  |  | ||||||
|  |         # | ||||||
|  |         # Look for valid replies and return appropriate values. Print an error | ||||||
|  |         # message otherwise and loop around for another go | ||||||
|  |         # | ||||||
|  |         if [[ ${ans^^} =~ ^Y(E|ES)?$ ]]; then | ||||||
|  |             return 0 | ||||||
|  |         elif [[ ${ans^^} =~ ^NO?$ ]]; then | ||||||
|  |             return 1 | ||||||
|  |         else | ||||||
|  |             echo "Invalid reply; please use 'Y' or 'N'" | ||||||
|  |         fi | ||||||
|  |     done | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #===  FUNCTION  ================================================================ | ||||||
|  | #         NAME: make_file_list | ||||||
|  | #  DESCRIPTION: Given an array of filenames select from them and make | ||||||
|  | #               an array of choices in a different order if required | ||||||
|  | #   PARAMETERS: 1 - input array name | ||||||
|  | #               2 - output array name | ||||||
|  | #      RETURNS: True (0) if a new list was generated, false (1) otherwise | ||||||
|  | #=============================================================================== | ||||||
|  | make_file_list () { | ||||||
|  |     local -n inarr=${1:?Usage make_file_list input_array out_array} | ||||||
|  |     local -n outarr=${2:?Usage make_file_list input_array out_array} | ||||||
|  |     local i msg prompt range lines choices | ||||||
|  |  | ||||||
|  |     # | ||||||
|  |     # Print the array contents | ||||||
|  |     # | ||||||
|  |     lines="${#inarr[@]}" | ||||||
|  |     for ((i = 0; i < lines; i++)); do | ||||||
|  |         printf '%-2d %s\n' "$((i+1))" "${inarr[$i]}" | ||||||
|  |     done | ||||||
|  |  | ||||||
|  |     msg="Enter a range like '1,2' or '4-7'" | ||||||
|  |     if [[ $lines -eq 1 ]]; then | ||||||
|  |         prompt="1" | ||||||
|  |     else | ||||||
|  |         prompt="1-$lines" | ||||||
|  |         msg+=" (between 1 and $lines)" | ||||||
|  |     fi | ||||||
|  |  | ||||||
|  |     # | ||||||
|  |     # Choose array elements by number where order is significant | ||||||
|  |     # | ||||||
|  |     echo "$msg" | ||||||
|  |     if read_value "Files to add to list: " range "$prompt"; then | ||||||
|  |         # | ||||||
|  |         # If there's a range then parse it and write the relevant elements to the | ||||||
|  |         # target array, otherwise exit with an error | ||||||
|  |         # | ||||||
|  |         if [[ -n "$range" ]]; then | ||||||
|  |             range_parse "$lines" "$range" choices 0 | ||||||
|  |             for i in $choices; do | ||||||
|  |                 outarr+=("${inarr[$((i-1))]}") | ||||||
|  |             done | ||||||
|  |         else | ||||||
|  |             echo "No files chosen; no list created" | ||||||
|  |             return 1 | ||||||
|  |         fi | ||||||
|  |  | ||||||
|  |         return 0 | ||||||
|  |     else | ||||||
|  |         return 1 | ||||||
|  |     fi | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #===  FUNCTION  ================================================================ | ||||||
|  | #          NAME:  range_parse | ||||||
|  | #   DESCRIPTION:  Parse a collection of ranges | ||||||
|  | #    PARAMETERS:  - maximum limit of the range | ||||||
|  | #                 - entered range expression (e.g. 1-3,7,14) | ||||||
|  | #                 - name of variable to receive the result | ||||||
|  | #       RETURNS:  Writes a list of values to the nominated variable | ||||||
|  | #=============================================================================== | ||||||
|  | function range_parse { | ||||||
|  |     local max=${1?range_parse: arg 1 missing} | ||||||
|  |     local range=${2?range:parse: arg2 missing} | ||||||
|  |     local result=${3?range:parse: arg3 missing} | ||||||
|  |     local tosort=${4:-1} | ||||||
|  |  | ||||||
|  |     local selection= | ||||||
|  |  | ||||||
|  |     # | ||||||
|  |     # Turn this off. It affects how some code below behaves | ||||||
|  |     # | ||||||
|  |     set +o nounset | ||||||
|  |  | ||||||
|  |     # | ||||||
|  |     # Remove spaces from the range | ||||||
|  |     # | ||||||
|  |     range=${range// /} | ||||||
|  |  | ||||||
|  |     # | ||||||
|  |     # Check for invalid characters | ||||||
|  |     # | ||||||
|  |     if [[ $range =~ [^0-9,-] ]]; then | ||||||
|  |         echo "Invalid range: $range" | ||||||
|  |         exit | ||||||
|  |     fi | ||||||
|  |  | ||||||
|  |     # | ||||||
|  |     # Slice up the sub-ranges separated by commas and turn all n-m expressions | ||||||
|  |     # into the intermediate values. Trim the trailing space from the | ||||||
|  |     # concatenation. | ||||||
|  |     # | ||||||
|  |     until [[ -z $range ]]; do | ||||||
|  |         if [[ $range =~ [,] ]]; then | ||||||
|  |             item=${range%%,*} | ||||||
|  |             range=${range#*,} | ||||||
|  |         else | ||||||
|  |             item=$range | ||||||
|  |             range= | ||||||
|  |         fi | ||||||
|  |         if [[ $item =~ [-] ]]; then | ||||||
|  |             item=${item//-/ } | ||||||
|  |             if [[ $item =~ ^([0-9]{1,}).([0-9]{1,})$ ]]; then | ||||||
|  |                 # shellcheck disable=SC2086 | ||||||
|  |                 item=$(seq -s' ' $item) | ||||||
|  |             else | ||||||
|  |                 echo "Invalid sequence: ${item/ /-}" | ||||||
|  |                 item= | ||||||
|  |             fi | ||||||
|  |         fi | ||||||
|  |         selection+="$item " | ||||||
|  |     done | ||||||
|  |     selection=${selection%% } | ||||||
|  |  | ||||||
|  |     # | ||||||
|  |     # Check for out of bounds problems, sort values and and make unique | ||||||
|  |     # | ||||||
|  |     if [[ ${#selection} -gt 0 ]]; then | ||||||
|  |         # | ||||||
|  |         # Validate the resulting range | ||||||
|  |         # | ||||||
|  |         declare -a sel err msg | ||||||
|  |         for i in $selection; do | ||||||
|  |             if [[ $i -lt 1 || $i -gt $max ]]; then | ||||||
|  |                 err+=( $i ) | ||||||
|  |             else | ||||||
|  |                 sel+=( $i ) | ||||||
|  |             fi | ||||||
|  |         done | ||||||
|  |  | ||||||
|  |         # | ||||||
|  |         # Report any errors | ||||||
|  |         # | ||||||
|  |         if [[ ${#err[*]} -gt 0 ]]; then | ||||||
|  |             msg=( $(for i in ${err[*]}; do echo $i; done | sort -un) ) | ||||||
|  |             if [[ ${#msg[*]} -gt 1 ]]; then | ||||||
|  |                 printf "Values out of range: %s .. %s\n" ${msg[0]} ${msg[${#msg[*]}-1]} | ||||||
|  |             else | ||||||
|  |                 printf "Value out of range: %s\n" ${msg[0]} | ||||||
|  |             fi | ||||||
|  |         fi | ||||||
|  |  | ||||||
|  |         # | ||||||
|  |         # Rebuild the selection | ||||||
|  |         # | ||||||
|  |         selection= | ||||||
|  |         if [[ ${#sel[*]} -gt 0 ]]; then | ||||||
|  |             if [[ $tosort -eq 1 ]]; then | ||||||
|  |                 selection="$(for i in ${sel[*]}; do echo $i; done | sort -un)" | ||||||
|  |             else | ||||||
|  |                 selection="$(for i in ${sel[*]}; do echo $i; done)" | ||||||
|  |             fi | ||||||
|  |         fi | ||||||
|  |     fi | ||||||
|  |  | ||||||
|  |     # | ||||||
|  |     # Return the result | ||||||
|  |     # | ||||||
|  |     eval $result=\"$selection\" | ||||||
|  |  | ||||||
|  |     return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #===  FUNCTION  ================================================================ | ||||||
|  | #         NAME: pad | ||||||
|  | #  DESCRIPTION: Pad $text to the left with $char characters to length $length | ||||||
|  | #   PARAMETERS: 1 - the text string to pad (no default) | ||||||
|  | #               2 - how long the padded string is to be (default 80) | ||||||
|  | #               3 - the character to pad with (default '-') | ||||||
|  | #      RETURNS: Nothing | ||||||
|  | #=============================================================================== | ||||||
|  | pad () { | ||||||
|  |     local text=${1?Usage: pad text [length] [character]} | ||||||
|  |     local length=${2:-80} | ||||||
|  |     local char=${3:--} | ||||||
|  |  | ||||||
|  |     local line | ||||||
|  |     printf -v line "%0*d%s\n" $((length - ${#text})) 0 "$text" | ||||||
|  |     char=${char:0:1} | ||||||
|  |     echo "${line//0/$char}" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #===  FUNCTION  ================================================================ | ||||||
|  | #         NAME: read_value | ||||||
|  | #  DESCRIPTION: Read a value from STDIN and handle errors. | ||||||
|  | #   PARAMETERS: 1 - Prompt string for the read | ||||||
|  | #               2 - Name of variable to receive the result | ||||||
|  | #               3 - Default value (optional) | ||||||
|  | #      RETURNS: 1 on error, otherwise 0 | ||||||
|  | #=============================================================================== | ||||||
|  | read_value () { | ||||||
|  |     local prompt="${1:?Usage: read_value prompt outputname [default]}" | ||||||
|  |     local outputname="${2:?Usage: read_value prompt outputname [default]}" | ||||||
|  |     local default="${3:-}" | ||||||
|  |     local var | ||||||
|  |  | ||||||
|  |     # | ||||||
|  |     # Make an option for the 'read' if there's a default | ||||||
|  |     # | ||||||
|  |     if [[ -n $default ]]; then | ||||||
|  |         default="-i '$default'" | ||||||
|  |     fi | ||||||
|  |  | ||||||
|  |     # | ||||||
|  |     # Read and handle CTRL-D (EOF). Use 'eval' to deal with the argument being | ||||||
|  |     # a variable | ||||||
|  |     # | ||||||
|  |     eval "read -r -e $default -p '$prompt' var" | ||||||
|  |     res="$?" | ||||||
|  |     if [[ $res -ne 0 ]]; then | ||||||
|  |         echo "Read aborted" | ||||||
|  |         return 1 | ||||||
|  |     fi | ||||||
|  |  | ||||||
|  |     # | ||||||
|  |     # Return the value in the nominated variable | ||||||
|  |     # | ||||||
|  |     eval "$outputname='$var'" | ||||||
|  |     return 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #===  FUNCTION  ================================================================ | ||||||
|  | #         NAME: define_colours | ||||||
|  | #  DESCRIPTION: Defines terminal colours | ||||||
|  | #   PARAMETERS: None | ||||||
|  | #      RETURNS: Nothing; justs sets the variables in the namespace | ||||||
|  | #=============================================================================== | ||||||
|  | define_colours () { | ||||||
|  |  | ||||||
|  |     # | ||||||
|  |     # Colour codes | ||||||
|  |     # | ||||||
|  |     red=$(tput setaf 1) | ||||||
|  |     export red | ||||||
|  |     green=$(tput setaf 2) | ||||||
|  |     export green | ||||||
|  |     yellow=$(tput setaf 3) | ||||||
|  |     export yellow | ||||||
|  |     blue=$(tput setaf 4) | ||||||
|  |     export blue | ||||||
|  |     purple=$(tput setaf 5) | ||||||
|  |     export purple | ||||||
|  |     reset=$(tput sgr0) | ||||||
|  |     export reset | ||||||
|  |  | ||||||
|  |     return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #===  FUNCTION  ================================================================ | ||||||
|  | #         NAME: undefine_colours | ||||||
|  | #  DESCRIPTION: Remove all the colour codes from the colour variables | ||||||
|  | #   PARAMETERS: None | ||||||
|  | #      RETURNS: Nothing; justs sets the variables in the namespace | ||||||
|  | #=============================================================================== | ||||||
|  | undefine_colours () { | ||||||
|  |  | ||||||
|  |     # | ||||||
|  |     # Colour codes being removed | ||||||
|  |     # | ||||||
|  |     for code in red green yellow blue purple reset; do | ||||||
|  |         eval "$code=''" | ||||||
|  |         eval "export $code" | ||||||
|  |     done | ||||||
|  |  | ||||||
|  |     return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #===  FUNCTION  ================================================================ | ||||||
|  | #         NAME: coloured | ||||||
|  | #  DESCRIPTION: Write a message with colour codes | ||||||
|  | #   PARAMETERS: 1 - colour name | ||||||
|  | #               2 - message | ||||||
|  | #      RETURNS: Nothing | ||||||
|  | #=============================================================================== | ||||||
|  | coloured () { | ||||||
|  |     local colour="${1:-green}" | ||||||
|  |     local message="${2:-no message}" | ||||||
|  |  | ||||||
|  |     printf '%s%s%s\n' "${!colour}" "$message" "${reset}" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #===  FUNCTION  ================================================================ | ||||||
|  | #         NAME: plural | ||||||
|  | #  DESCRIPTION: Adds an 's' to a word depending on the size of a number (based | ||||||
|  | #               on an example from O'Reilly). Also look at ngettext for a much | ||||||
|  | #               more sophisticated alternative. | ||||||
|  | #   PARAMETERS: 1 - the word to pluralise | ||||||
|  | #               2 - the number used to decide | ||||||
|  | #      RETURNS: Nothing | ||||||
|  | #=============================================================================== | ||||||
|  | plural () { | ||||||
|  |     if [[ $2 -eq 1 || $2 -eq -1 ]]; then | ||||||
|  |         echo "${1}" | ||||||
|  |     else | ||||||
|  |         echo "${1}s" | ||||||
|  |     fi | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #===  FUNCTION  ================================================================ | ||||||
|  | #         NAME: trimpath | ||||||
|  | #  DESCRIPTION: Trim a file path to its last few components for display | ||||||
|  | #   PARAMETERS: $1 - path to trim | ||||||
|  | #               $2 - number of elements to retain | ||||||
|  | #      RETURNS: Trimmed path | ||||||
|  | #=============================================================================== | ||||||
|  | trimpath () { | ||||||
|  |     local path=${1:?Usage: mpath path elements} | ||||||
|  |     local elements=${2:?Usage: mpath path elements} | ||||||
|  |     local -a arr1 arr2 | ||||||
|  |     local result | ||||||
|  |  | ||||||
|  |     # Split the path | ||||||
|  | #     oldifs="$IFS" | ||||||
|  | #     IFS='/' mapfile -d'/' -t arr1 <<<"$path" | ||||||
|  |     IFS='/' read -r -a arr1 <<<"$path" | ||||||
|  | #     IFS="$oldifs" | ||||||
|  |  | ||||||
|  |     if [[ ${#arr1[@]} -gt $elements ]]; then | ||||||
|  |         # Put the last elements in another array | ||||||
|  |         mapfile -t arr2 < <(printf '%s\n' "${arr1[@]: -$elements}") | ||||||
|  |  | ||||||
|  |         # Return the second array interleaved with '/' | ||||||
|  |         result="${arr2[*]/%//}" | ||||||
|  |         result="${result//\/ /\/}" | ||||||
|  |         echo "${result:0:-1}" | ||||||
|  |     else | ||||||
|  |         echo "$path" | ||||||
|  |     fi | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #=============================================================================== | ||||||
|  | #         NAME: stampfile | ||||||
|  | #  DESCRIPTION: Rename a file by adding a timestamp to its name. The timestamp | ||||||
|  | #               is the modification time. It is added at the end by default or | ||||||
|  | #               wherever the caller places a '%s'. Note there are not enough | ||||||
|  | #               error checks to deal with really weird requests :-) | ||||||
|  | #   PARAMETERS: $1 - name of file to rename | ||||||
|  | #               $2 - optional template string | ||||||
|  | #      RETURNS: Nothing | ||||||
|  | #=============================================================================== | ||||||
|  | function stampfile () { | ||||||
|  |     if [[ $# = 0  || $# -gt 2 ]]; then | ||||||
|  |         echo "Usage: ${FUNCNAME[0]} filename [template]" | ||||||
|  |         return | ||||||
|  |     fi | ||||||
|  |     local fname=${1} | ||||||
|  |     local tp=${2:-"${fname}_%s"} | ||||||
|  |     [ -e "$fname" ] || { echo "$fname does not exist"; return; } | ||||||
|  |     # shellcheck disable=SC2059 | ||||||
|  |     new=$(printf "$tp" "$(date -r "$fname" +%Y%m%d_%H%M%S)") | ||||||
|  |     mv "$fname" "$new" || { echo "Unable to mv $fname to $new"; return; } | ||||||
|  |     echo "Renamed $fname to $new" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | # | ||||||
|  | # vim: syntax=sh:ts=8:sw=4:et:tw=78:fo=tcrqn21 | ||||||
		Reference in New Issue
	
	Block a user