#!/bin/bash #----------------------------------------------------------------------------------------------------------------------------------- # # Rx3 Bash Base Library # # Copyright (C) 2017-2026 Arnaud G. GIBERT # mailto:arnaud@rx3.net # # This is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this program; If not, see # . # #----------------------------------------------------------------------------------------------------------------------------------- if [[ "${RX3_BASH_BASE}" != "" ]] then return else declare -g RX3_BASH_BASE=1 fi #----------------------------------------------------------------------------------------------------------------------------------- # Default Options #----------------------------------------------------------------------------------------------------------------------------------- set -o errexit -o pipefail -o nounset shopt -s extglob #----------------------------------------------------------------------------------------------------------------------------------- # Global Variable #----------------------------------------------------------------------------------------------------------------------------------- declare -Ag LIB_VSL_TAB=() declare -g LIB_VSL_ID_LIST="" declare -g LIB_VSL_ID_NB=0 declare -g LIB_VSL_NAME_LEN=0 declare -g LIB_VSL_VERSION_LEN=0 declare LIB_NAME="Base" declare LIB_VERSION="1.2.0" declare -g NAME="" declare -g VERSION="" declare -g DUMP="FALSE" declare -g DRY_RUN="FALSE" declare -g HELP="" declare -g VERBOSE="FALSE" declare -g LOG_FILE="" declare -g LOG_LOCK="" declare -g LOG_ECHO="" declare -g LOG_TRACE="DISABLED" #----------------------------------------------------------------------------------------------------------------------------------- # Str Quote #----------------------------------------------------------------------------------------------------------------------------------- str_quote() { local quoted=${1//\'/\'\\\'\'} printf "'%s'" "${quoted}" } #----------------------------------------------------------------------------------------------------------------------------------- # Str Escape #----------------------------------------------------------------------------------------------------------------------------------- str_escape() { echo "$*" | sed -e "s/\"/\\\\\"/g" } #----------------------------------------------------------------------------------------------------------------------------------- # Echo Line #----------------------------------------------------------------------------------------------------------------------------------- echo_line() { local string="${1}" local count="${2:-""}" echo -en "\e[2K\r" if [[ "${count}" != "" ]] then printf "%05d: %s" "${count}" echo -en "${string}" fi } #----------------------------------------------------------------------------------------------------------------------------------- # Echo Error #----------------------------------------------------------------------------------------------------------------------------------- echo_error() { echo "$@" 1>&2 } #----------------------------------------------------------------------------------------------------------------------------------- # Cmd Exec #----------------------------------------------------------------------------------------------------------------------------------- cmd_exec() { local force="FALSE" if [[ "$1" == "FORCE" ]] then force="TRUE" shift fi if [[ "${VERBOSE}" == "TRUE" ]] then local caller="${FUNCNAME[1]:-MAIN}" printf '%s: [%s] ' "${NAME}" "${caller}" >&2 printf '%q ' "$@" >&2 echo >&2 fi if [[ ( "${force}" != "TRUE" ) && ( "${DUMP}" == "TRUE" ) ]] then printf '%q ' "$@" echo else if [[ ( "${force}" == "TRUE" ) || ( "${DRY_RUN}" != "TRUE" ) ]] then "$@" fi fi } #----------------------------------------------------------------------------------------------------------------------------------- # Sh Exec #----------------------------------------------------------------------------------------------------------------------------------- sh_exec() { local force="FALSE" if [[ "$1" == "FORCE" ]] then force="TRUE" shift fi if [[ "$#" -ne 1 ]] || [[ -z "$1" ]] then echo_error "sh_exec expects exactly 1 non empty argument after FORCE optional flag" return 1 fi if [[ "${VERBOSE}" == "TRUE" ]] then local caller="${FUNCNAME[1]:-MAIN}" printf '%s: [%s] bash -c %q\n' "${NAME}" "${caller}" >&2 printf 'bash -c %q\n' "${NAME}" "${caller}" "$1" >&2 fi if [[ ( "${force}" != "TRUE" ) && ( "${DUMP}" == "TRUE" ) ]] then printf 'bash -c %q\n' "${NAME}" "${caller}" "$1" else if [[ ( "${force}" == "TRUE" ) || ( "${DRY_RUN}" != "TRUE" ) ]] then bash -o errexit -o pipefail -o nounset -O extglob -c -- "$1" bash fi fi } #----------------------------------------------------------------------------------------------------------------------------------- # Print #----------------------------------------------------------------------------------------------------------------------------------- print() { local inline="${1}" shift if [[ "${inline}" == "TRUE" ]] then str="%s"'\\n' else str="%s"'\n' fi cmd_exec printf "${str}" "$*" } #-------------------------------------------------------------------------------------------------------------------------- # Tab Assign #-------------------------------------------------------------------------------------------------------------------------- tab_assign() { declare -n ta_tab=$1 local ta_key=$2 local ta_value=$3 if [[ "${ta_value}" == "-" ]] then ta_value="" fi ta_tab[${ta_key}]="${ta_value}" } #-------------------------------------------------------------------------------------------------------------------------- # Var Assign #-------------------------------------------------------------------------------------------------------------------------- var_assign() { declare -n va_var=$1 local va_value=$2 local va_mode=$3 if [[ "${va_value}" == "-" ]] then va_value="" fi if [[ "${va_mode}" == "INC" ]] then va_var="${va_var} ${va_value}" else va_var="${va_value}" fi } #-------------------------------------------------------------------------------------------------------------------------- # VSL Add #-------------------------------------------------------------------------------------------------------------------------- vsl_add() { local name local version local file if [[ "${BASH_SOURCE[1]}" == "$0" ]] then name="${NAME}" version="${VERSION}" file="${0}" else name="${LIB_NAME}" version="${LIB_VERSION}" file="${BASH_SOURCE[1]}" fi var_assign LIB_VSL_ID_LIST "${LIB_VSL_ID_NB}" INC tab_assign LIB_VSL_TAB "${LIB_VSL_ID_NB},Name" "${name}" tab_assign LIB_VSL_TAB "${LIB_VSL_ID_NB},Version" "${version}" tab_assign LIB_VSL_TAB "${LIB_VSL_ID_NB},File" "${file}" if [[ "${#name}" -gt "${LIB_VSL_NAME_LEN}" ]] then LIB_VSL_NAME_LEN="${#name}" fi if [[ "${#version}" -gt "${LIB_VSL_VERSION_LEN}" ]] then LIB_VSL_VERSION_LEN="${#version}" fi LIB_VSL_ID_NB=$(( ${LIB_VSL_ID_NB} + 1)) } #----------------------------------------------------------------------------------------------------------------------------------- # VSL HTML Dump #----------------------------------------------------------------------------------------------------------------------------------- vsl_html_dump() { local reverse="$1" local inline="$2" local i=1 local j=1 local vsl_id local vsl_id_list local name local version local file if [[ "${reverse}" == "TRUE" ]] then vsl_id_list="$( echo "${LIB_VSL_ID_LIST}" | tr ' ' '\n' | tac)" else vsl_id_list="$( echo "${LIB_VSL_ID_LIST}" | tr ' ' '\n')" fi for vsl_id in ${vsl_id_list} do name=${LIB_VSL_TAB["${vsl_id},Name"]} version=${LIB_VSL_TAB["${vsl_id},Version"]} file=${LIB_VSL_TAB["${vsl_id},File"]} if [[ $(( $i % 2)) -eq 0 ]] then print "${inline}" ' ' else print "${inline}" " " fi print "${inline}" " ${i}${name}${version}" if [[ "${VERBOSE}" == "TRUE" ]] then print "${inline}" "${file}" fi print "${inline}" " " i=$(( $i + 1)) done } #----------------------------------------------------------------------------------------------------------------------------------- # Version Print #----------------------------------------------------------------------------------------------------------------------------------- version_print() { local full="${1-TRUE}" local name_len local version_len local vsl_id_list local vsl_id local name local version local file if [[ "${full}" == "TRUE" ]] then vsl_id_list="$( echo "${LIB_VSL_ID_LIST}" | tr ' ' '\n' | tac)" name_len="${LIB_VSL_NAME_LEN}" version_len="${LIB_VSL_VERSION_LEN}" else vsl_id_list="$(( ${LIB_VSL_ID_NB} - 1))" name_len="${#LIB_VSL_TAB["${vsl_id_list},Name"]}" version_len="${#LIB_VSL_TAB["${vsl_id_list},Version"]}" fi name_len=$(( ${name_len} + 1 )) version_len=$(( ${version_len} + 1 )) for vsl_id in ${vsl_id_list} do name=${LIB_VSL_TAB["${vsl_id},Name"]} version=${LIB_VSL_TAB["${vsl_id},Version"]} file=${LIB_VSL_TAB["${vsl_id},File"]} str="$(printf "%s%*sV %s" "${name}" "$((${name_len} - ${#name}))" " " "${version}")" if [[ "${VERBOSE}" == "TRUE" ]] then str+="$(printf "%*s%s" "$((${version_len} - ${#version}))" " " "${file}")" fi echo "${str}" done } #----------------------------------------------------------------------------------------------------------------------------------- # Help Print #----------------------------------------------------------------------------------------------------------------------------------- help_print() { version_print "" echo "${NAME} ${HELP}" } #-------------------------------------------------------------------------------------------------------------------------- # File Dir Init #-------------------------------------------------------------------------------------------------------------------------- file_dir_init() { local File="$1" local Owner="$2" local Group="$3" local dir if [ ! -f ${File} ] then if [[ "$( id -u)" != "0" ]] then ${ECHO} "Can't perform file init of: [${File}] as non root user!" else dir="$( dirname ${File})" if [ ! -d ${dir} ] then ${ECHO} "Initializing directory: [${dir}]" mkdir ${dir} chmod ug+rwx ${dir} chown ${Owner}:${Group} ${dir} fi ${ECHO} "Initializing file: [${File}]" >${File} chmod ug+rw ${File} chown ${Owner}:${Group} ${File} fi fi } #-------------------------------------------------------------------------------------------------------------------------- # File Lock #-------------------------------------------------------------------------------------------------------------------------- file_lock() { local file="$1" local mode="$2" local desc="$3" if [[ ( "${mode}" == "EXCLUSIVE" ) || ( "${mode}" == "WRITE" ) ]] then flag="-x" else flag="-s" fi if [[ "${desc}" == "" ]] then desc="9" fi eval "exec ${desc}<>\"\${file}\"" if ! flock ${flag} -w 5 ${desc} then echo_error "Failed to acquire [${mode}] lock on: [${file}]" exit 1 fi } #-------------------------------------------------------------------------------------------------------------------------- # File Unlock #-------------------------------------------------------------------------------------------------------------------------- file_unlock() { local desc="$1" if [[ "${desc}" == "" ]] then desc="9" fi eval "exec ${desc}<&-" eval "exec ${desc}>&-" } #-------------------------------------------------------------------------------------------------------------------------- # Log Set #-------------------------------------------------------------------------------------------------------------------------- log_set() { local log_file="$1" local lock_file="$2" local echo_function="$3" local log_trace="$4" LOG_FILE="${log_file}" LOG_LOCK="${lock_file}" LOG_ECHO="${echo_function}" if [[ ${log_trace} != "" ]] then LOG_TRACE="${log_trace}" fi } #-------------------------------------------------------------------------------------------------------------------------- # Log Print #-------------------------------------------------------------------------------------------------------------------------- log_print() { local log_file="$1" local lock_file="$2" local echo_function="$3" local log_type="$4" local log_prefix="$5" shift; shift; shift; shift; shift if [[ "${log_type}" != "TRA" ]] || [[ "${LOG_TRACE}" != "DISABLED" ]] then ${echo_function} "($BASHPID):" "$*" if [[ "${log_file}" != "" ]] then if [[ "${lock_file}" != "" ]] then file_lock "${lock_file}" WRITE 8 fi printf >> "${log_file}" "%s %9s %3s %16s %s\n" "$(date --rfc-3339=seconds -u)" "($BASHPID)" "${log_type}" "${log_prefix}:" "$*" if [[ "${lock_file}" != "" ]] then file_unlock 8 fi fi fi } #-------------------------------------------------------------------------------------------------------------------------- # Log Trace #-------------------------------------------------------------------------------------------------------------------------- log_trace() { log_print "${LOG_FILE}" "${LOG_LOCK}" "${LOG_ECHO}" "TRA" "$@" } #-------------------------------------------------------------------------------------------------------------------------- # Log Info #-------------------------------------------------------------------------------------------------------------------------- log_info() { log_print "${LOG_FILE}" "${LOG_LOCK}" "${LOG_ECHO}" "INF" "$@" } #-------------------------------------------------------------------------------------------------------------------------- # Log Warning #-------------------------------------------------------------------------------------------------------------------------- log_warning() { log_print "${LOG_FILE}" "${LOG_LOCK}" "${LOG_ECHO}" "WRN" "$@" } #-------------------------------------------------------------------------------------------------------------------------- # Log Error #-------------------------------------------------------------------------------------------------------------------------- log_error() { log_print "${LOG_FILE}" "${LOG_LOCK}" "${LOG_ECHO}" "ERR" "$@" } #-------------------------------------------------------------------------------------------------------------------------- # Main #-------------------------------------------------------------------------------------------------------------------------- vsl_add