#!/usr/bin/env bash ### # A helper/utility script to be imported by other scripts. # # Intended to hold very common functionality (that's surprisingly not built into bash) such as string upper/lower, # and checking current user value, and prompts of yes/no user input. # # https://git.brandon-rodriguez.com/scripts/bash/utils # Version 1.3.1 ## #region Global Utility Variables. # Color Output Variables. text_reset="\033[0m" text_black="\033[0;30m" text_red="\033[0;31m" text_green="\033[0;32m" text_orange="\033[0;33m" text_blue="\033[0;34m" text_purple="\033[0;35m" text_cyan="\033[1;36m" text_yellow="\033[1;33m" text_white="\033[1;37m" # Arg, Kwarg, and Flag Return Variables. args=() flags=() declare -A kwargs=() global_args=() global_flags=() declare -A global_kwargs=() help_flags=false # Function Return Variables. return_value="" file_name="" file_extension="" #endregion Global Utility Variables #region Script Setup Functions ### # Normalizes the location of the terminal to directory of utils.sh file. # # The point is so that the execution of a script will handle the same, regardless of terminal directory location at # script start. Ex: User can start script at either their home folder, or project root, (or any other directory) and # relative directory handling will still function the same in either case. # # Automatically called on script import. # # Return Variable(s): return_value ## function normalize_terminal () { cd "$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" } ### # Handles passed values. # # Splits into "args", "kwargs", and "flags": # * Args are any other values that don't match below two formats. # * Ex: "True", "5", and "test". # * Kwargs are key-value pairs, where the key comes first and starts with "--" or "-". # * Note that the associated value must come immediately after the key. # * Ex: "--type int", "--dir ./test", and "--name Bob". # * Flags are any variable that starts with "-" or "--", and must be defined by the calling script. # * To define flags, populate a "possible_flags" array with flag values prior to calling util script. # * These are effectively treated as booleans. True if present, false otherwise. # * Ex: "-a", "--b", "-run", and "--test". # # Ordering of provided args, kwargs, and flags should not matter, aside from kwarg keys needing a corresponding value # as the direct next processed value. # # Note that "-h" and "--help" are handled separately, and considered to both be "help flags". # If either one is provided, then a "help_flags" global variable is set to true. # The expectation is that some kind of "display help" function will run instead of normal script functionality. # # Return Variable(s): help_flags, args, kwargs, flags, global_args, global_kwargs, and global_flags ## handle_args_kwargs () { local handle_kwarg=false local kwarg_key="" set_global_args=false set_global_flags=false set_global_kwargs=false # On first run, set "global" arg/kwarg values, as backup. # Useful in case any functions ever call this for additional arg/kwarg/flag handling. # Prevents original passed values from being overriden by a function's passed values. if [[ ${#global_args[@]} == 0 && ${#global_flags[@]} == 0 && ${#global_kwargs[@]} == 0 ]] then set_global_args=true set_global_flags=true set_global_kwargs=true else args=() flags=() kwargs=() fi # Parse all args. for arg in ${@} do # Check arg type, based on substring match. if [[ "${arg}" == "-h" || "${arg}" == "--help" ]] then # Special handling for help flags. help_flags=true # Handle for flags. elif [[ ${#possible_flags[@]} > 0 && ${possible_flags[@]} =~ "${arg}" ]] then # Check for kwarg handling bool. if [[ ${handle_kwarg} == true ]] then # Expected arg to fill value for key-value pair. Got flag. Raise error. echo -e "${text_red}Expected value for kwarg key of \"${kwarg_key}\". Got a flag of \"${arg}\" instead.${text_reset}" exit 1 else # Save kwarg key and process next value. if [[ "${arg}" == "--"* ]] then # Handle for double dash flag. local new_flag=${arg#--} elif [[ "${arg}" == "-"* ]] then # Handle for single dash flag. local new_flag=${arg#-} else # Unexpected kwarg value. echo -e "${text_red}Unexpected flag type of \"${arg}\". Unable to proceed.${text_reset}" exit 1 fi flags+=( ${new_flag} ) # Optionally populate global flags. if [[ ${set_global_flags} == true ]] then global_flags+=( ${new_flag} ) fi fi # Handle for kwargs. elif [[ ("${arg}" == "-"* || "${arg}" == "--"*) && "${arg}" != "---"* ]] then # Check for kwarg handling bool. if [[ ${handle_kwarg} == true ]] then # Expected arg to fill value for key-value pair. Got kwarg key. Raise error. echo -e "${text_red}Expected value for kwarg key of \"${kwarg_key}\". Got another key of \"${arg}\" instead.${text_reset}" exit 1 else # Save kwarg key and process next value. if [[ "${arg}" == "--"* ]] then # Handle for two dash kwarg. kwarg_key=${arg#--} handle_kwarg=true elif [[ "${arg}" == "-"* ]] then # Handle for one dash kwarg. kwarg_key=${arg#-} handle_kwarg=true else # Unexpected kwarg value. echo -e "${text_red}Unexpected kwarg key type of \"${arg}\". Unable to proceed.${text_reset}" exit 1 fi fi # Handle for args. else # Check for kwarg handling bool. if [[ ${handle_kwarg} == true ]] then # Set key-value kwarg pair. kwargs[${kwarg_key}]=${arg} handle_kwarg=false # Optionally populate global kwargs. if [[ ${set_global_kwargs} == true ]] then global_kwargs[${kwarg_key}]=${arg} fi else # Add arg to list of args. args+=( ${arg} ) # Optionally populate global args. if [[ ${set_global_args} == true ]] then global_args+=( ${arg} ) fi fi fi done } #endregion Script Setup Functions #region Directory Functions ### # Gets absolute path of passed location. # # Return Variable(s): return_value ## function get_absolute_path () { # Check number of passed function args. if [[ ${#} == 1 ]] then # Expected number of args passed. Continue. # Check location type. if [[ -f ${1} ]] then # Handle for file. return_value="$(cd "$(dirname "${1}")"; pwd -P)/$(basename "${1}")" elif [[ -d ${1} ]] then # Handle for directory. # Extra logic to properly handle values of "./" and "../". local current_dir="$(pwd -P)" cd ${1} # Then call this to have consistent symlink handling as files. return_value="$(cd "$(dirname "$(pwd -P)")"; pwd -P)/$(basename "$(pwd -P)")" # Change back to original location once value is set. cd ${current_dir} else echo -e "${text_red}Passed value ( ${1} ) is not a valid file or directory.${text_reset}" exit 1 fi # Handle for too many args. elif [[ ${#} > 1 ]] then echo -e "${text_red}Too many args passed. Expected one arg, received ${#}.${text_reset}" exit 1 # Handle for too few args. else echo -e "${text_red}Too few args passed. Expected one arg, received 0.${text_reset}" exit 1 fi } ### # If no arg is provided, returns full path of current directory. # If arg is passed, then returns absolute path if was directory, or full path of parent directory if was file. # # Return Variable(s): return_value ## function get_directory_path () { # Check number of passed function args. if [[ ${#} == 0 ]] then # No args passed. Handle for current directory. get_absolute_path ./ # Handle for one arg. elif [[ ${#} == 1 ]] then # Check location type. if [[ -f ${1} ]] then # Handle for file. get_absolute_path ${1} return_value=${return_value%/*} elif [[ -d ${1} ]] then # Handle for directory. get_absolute_path ${1} else echo -e "${text_red}Passed value ( ${1} ) is not a valid file or directory.${text_reset}" exit 1 fi # Handle for too many args. else echo -e "${text_red}Too many args passed. Expected zero or one args, received ${#}.${text_reset}" exit 1 fi } ### # If no arg is provided, then returns name of current directory. # If arg is passed, then returns name of directory, or parent directory of file. # # Return Variable(s): return_value ## function get_directory_name () { # Check number of passed function args. if [[ ${#} == 0 ]] then # No args passed. Pass current directory to parent function. get_directory_path ./ return_value=${return_value##*/} # Handle for one arg. elif [[ ${#} == 1 ]] then # Pass provided value to parent function. get_directory_path ${1} return_value=${return_value##*/} # Handle for too many args. else echo -e "${text_red}Too many args passed. Expected zero or one args, received ${#}.${text_reset}" exit 1 fi } ### # Parses passed file, getting base file name and file extension. # # Return Variable(s): return_value ## function parse_file_name () { # Check number of passed function args. if [[ ${#} == 1 ]] then # Expected number of args passed. Continue. if [[ -f ${1} ]] then # Handle for file. get_absolute_path ${1} return_value=${return_value##*/} file_name="" file_extension="" _recurse_file_extension ${return_value} else echo -e "${text_red}Passed value ( ${1} ) is not a valid file.${text_reset}" exit 1 fi # Handle for too many args. elif [[ ${#} > 1 ]] then echo -e "${text_red}Too many args passed. Expected one arg, received ${#}.${text_reset}" exit 1 # Handle for too few args. else echo -e "${text_red}Too few args passed. Expected one arg, received 0.${text_reset}" exit 1 fi } ### # Recursive helper function for parse_file_name(). # Determines base file name and full file extension. # # Return Variable(s): file_name and file_extension ## function _recurse_file_extension () { local passed_value=${1} local parsed_extension=${passed_value##*.} # Check if file extension was found. Occurs when variables are not identical. if [[ ${parsed_extension} != ${passed_value} ]] then # Extension found. Iterate once more. file_name=${passed_value%.${parsed_extension}} # Handle if global var is currently empty or not. if [[ ${file_extension} == "" ]] then file_extension=".${parsed_extension}" else file_extension=".${parsed_extension}${file_extension}" fi # Call recursive function once more. _recurse_file_extension ${file_name} fi } #endregion Directory Functions #region User Check Functions ### # Checks if current username matches passed value. # In particular, is used to check if user is sudo/root user. # # Return Variable(s): None ## function check_is_user () { # Check number of passed function args. if [[ ${#} == 1 ]] then # Expected number of args passed. Continue. local username=$USER local check_user=$1 # Determine user to check for. to_lower ${check_user} if [[ "${return_value}" == "root" || "${return_value}" == "sudo" ]] then # Special logic for checking if "sudo"/"root" user. if [[ "$EUID" != 0 ]] then echo -e "${text_red}Sudo permissions required. Please run as root.${text_reset}" exit fi else # Standard logic for all other user checks. if [[ "${username}" != "${check_user}" ]] then echo -e "${text_red}User check (${check_user}) failed. Current user is ${username}.${text_reset}" exit 1 fi fi # Handle for too many args. elif [[ ${#} > 1 ]] then echo -e "${text_red}Too many args passed. Expected one arg, received ${#}.${text_reset}" exit 1 # Handle for too few args. else echo -e "${text_red}Too few args passed. Expected one arg, received 0.${text_reset}" exit 1 fi } ### # Checks if current username does not match passed value. # In particular, is used to check if user is not sudo/root user. # # Return Variable(s): None ## function check_is_not_user () { # Check number of passed function args. if [[ ${#} == 1 ]] then # Expected number of args passed. Continue. local username=$USER local check_user=$1 # Determine user to check for. to_lower ${check_user} if [[ "${return_value}" == "root" || "${return_value}" == "sudo" ]] then # Special logic for checking if "sudo"/"root" user. if [[ "$EUID" == 0 ]] then echo -e "${text_red}Standard permissions required. Please run as non-root user.${text_reset}" exit fi else # Standard logic for all other user checks. if [[ "${username}" == "${check_user}" ]] then echo -e "${text_red}Not-user check (${check_user}) failed. Current user is ${username}.${text_reset}" exit 1 fi fi # Handle for too many args. elif [[ ${#} > 1 ]] then echo -e "${text_red}Too many args passed. Expected one arg, received ${#}.${text_reset}" exit 1 # Handle for too few args. else echo -e "${text_red}Too few args passed. Expected one arg, received 0.${text_reset}" exit 1 fi } #endregion User Check Functions #region Text Manipulation Functions ### # Converts one or more passed args to uppercase characters. # # Return Variable(s): return_value ## function to_upper () { # Check number of passed function args. if [[ ${#} > 0 ]] then # At least one arg passed. Loop through each one. return_value=() for arg in ${@} do return_value+=( $(echo "${arg}" | tr '[:lower:]' '[:upper:]') ) done # No args passed. else echo -e "${text_red}Too few args passed. Expected one arg, received 0.${text_reset}" exit 1 fi } ### # Convers one or more passed args to lowercase characters. # # Return Variable(s): return_value ## function to_lower () { # Check number of passed function args. if [[ ${#} > 0 ]] then # At least one arg passed. Loop through each one. return_value=() for arg in ${@} do return_value+=( $(echo "${arg}" | tr '[:upper:]' '[:lower:]') ) done # No args passed. else echo -e "${text_red}Too few args passed. Expected one arg, received 0.${text_reset}" exit 1 fi } #endregion Text Manipulation Functions #region Display Functions ### # Prompts user for yes/no confirmation. Returns True if yes. # Accepts "yes", "ye", and "y". Input values are treated as case insensitive. # All other values are treated as "no". # # Return Variable(s): return_value ## function get_user_confirmation () { # Check number of passed function args. if [[ ${#} == 0 ]] then # Handle prompt for no args passed. echo -e "[${text_cyan}Yes${text_reset}/${text_cyan}No${text_reset}]" elif [[ ${#} == 1 ]] then # Handle prompt for one arg passed. echo -e "${1} [${text_cyan}Yes${text_reset}/${text_cyan}No${text_reset}]" else # Handle for too many args. echo -e "${text_red}Too many args passed. Expected zero or one arg, received ${#}.${text_reset}" exit 1 fi # Get user input. read -p " " return_value echo "" # Convert input to lower for easy parsing. to_lower "${return_value}" # Finally parse user input. if [[ "${return_value}" == "y" || "${return_value}" == "ye" || "${return_value}" == "yes" ]] then # User provided "yes". Return True. return_value=true else # For all other values, return False. return_value=false fi } ### # Prints out all available text colors provided by this script. # # Return Variable(s): None ## function display_text_colors () { echo "" echo "Displaying all available text colors:" echo -e " ${text_black}Black${text_reset}" echo -e " ${text_red}Red${text_reset}" echo -e " ${text_green}Green${text_reset}" echo -e " ${text_orange}Orange${text_reset}" echo -e " ${text_blue}Blue${text_reset}" echo -e " ${text_purple}Purple${text_reset}" echo -e " ${text_cyan}Cyan${text_reset}" echo -e " ${text_yellow}Yellow${text_reset}" echo -e " ${text_white}White${text_reset}" echo "" } ### # Prints out all current flags, args, and kwargs, as determined by handle_args_kwargs() function. # If function has been called multiple times, then will also print out global values. # # Return Variable(s): None ## function display_args_kwargs () { echo "" echo -e "${text_blue}Displaying processed flags, args, and kwargs.${text_reset}" echo "" # Display for flags. if [[ ${#flags[@]} > 0: ]] then echo -e " ${text_purple}Flags${text_reset} (${#flags[@]} Total):" echo " ${flags[@]}" else echo " No Flags Set." fi # Display for args. if [[ ${#args[@]} > 0 ]] then echo -e " ${text_purple}Args${text_reset} (${#args[@]} Total):" for index in ${!args[@]} do echo " ${index}: ${args[${index}]}" done else echo " No Args Set." fi # Display for kwargs. if [[ ${#kwargs[@]} > 0 ]] then echo -e " ${text_purple}Kwargs${text_reset} (${#kwargs[@]} Total):" for key in ${!kwargs[@]} do echo " ${key}: ${kwargs[${key}]}" done else echo " No Kwargs Set." fi # Only display if global values are different. if [[ ${flags[@]} != ${global_flags[@]} || ${args[@]} != ${global_args[@]} || ${kwargs[@]} != ${global_kwargs[@]} ]] then echo "" echo "" # Display for global flags. if [[ ${#global_flags[@]} > 0: ]] then echo -e " ${text_purple}Global Flags${text_reset} (${#global_flags[@]} Total):" echo " ${global_flags[@]}" else echo " No Global Flags Set." fi # Display for global args. if [[ ${#global_args[@]} > 0 ]] then echo -e " ${text_purple}Global Args${text_reset} (${#global_args[@]} Total):" for index in ${!global_args[@]} do echo " ${index}: ${global_args[${index}]}" done else echo " No Global Args Set." fi # Display for global kwargs. if [[ ${#global_kwargs[@]} > 0 ]] then echo -e " ${text_purple}Global Kwargs${text_reset} (${#global_kwargs[@]} Total):" for key in ${!global_kwargs[@]} do echo " ${key}: ${global_kwargs[${key}]}" done else echo " No Global Kwargs Set." fi fi echo "" } #endregion Display Functions # Functions to call on script import. normalize_terminal handle_args_kwargs ${@}