diff --git a/profile.sh b/profile.sh index ddf01a4..9dc9058 100644 --- a/profile.sh +++ b/profile.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Begin profile # ------------------------------------------------------------------------------ -# Copyright (c) 2013-2022 Geoffray Levasseur +# Copyright (c) 2013-2026 Geoffray Levasseur # Protected by the BSD3 license. Please read bellow for details. # # * Redistribution and use in source and binary forms, @@ -35,12 +35,19 @@ # * OF SUCH DAMAGE. # ------------------------------------------------------------------------------ -if [[ ! $SHELL =~ bash|zsh ]]; then - echo "That environment script is designed to be used with bash or zsh being the shell." - echo "Please consider using bash or zsh instead, or patch me ;)!" +if [[ ! $SHELL =~ bash ]]; then + echo "That environment script is designed to be used with bash being the shell." + echo "Please consider using bash to enjoy our features!" return 1 fi +# Required for associative arrays (4.0+) and namerefs (4.3+) +if ((BASH_VERSINFO[0] < 4)) || [[ ${BASH_VERSINFO[0]} -eq 4 && ${BASH_VERSINFO[1]} -lt 3 ]]; then + echo "[ Error ] This profile requires Bash 4.3 or higher." + echo "Current version: $BASH_VERSION" + return 1 2>/dev/null || exit 1 +fi + # ------------------------------------------------------------------------------ # path* : private functions for PATH variable management pathremove() @@ -79,37 +86,43 @@ parse_conf() { local config_file="$1" local current_section="" + local line key value [[ ! -f "$config_file" ]] && return 1 while IFS='=' read -r key value || [[ -n "$key" ]]; do - # Clean key and value (strip CR and whitespace) - key=$(printf '%s' "$key" | tr -d '\r' | xargs 2>/dev/null) - value=$(printf '%s' "$value" | tr -d '\r' | xargs 2>/dev/null) + # Internal trimming (removes leading/trailing whitespace & CR) + key="${key%"${key##*[![:space:]]}"}" + key="${key#"${key%%[![:space:]]*}"}" + key="${key%$'\r'}" # Strip potential Windows line endings # Skip comments and empty lines [[ -z "$key" || "$key" =~ ^[#\;] ]] && continue # Section Detection: [section_name] - if [[ "$key" =~ ^\[(.*)\]$ ]]; then + if [[ "$key" =~ ^\[([a-zA-Z0-9_]+)\]$ ]]; then current_section="${BASH_REMATCH[1]}" - # Dynamically declare the associative array for this section declare -g -A "CONF_$current_section" continue fi - # If we have a key/value pair and are inside a section - if [[ -n "$current_section" && -n "$value" ]]; then - # Strip quotes from value + # Secure Assignment (if inside a section) + if [[ -n "$current_section" ]]; then + # Clean the value + value="${value%"${value##*[![:space:]]}"}" + value="${value#"${value%%[![:space:]]*}"}" + value="${value%$'\r'}" + + # Strip quotes (handling both " and ') value="${value%\"}"; value="${value#\"}" value="${value%\'}"; value="${value#\'}" - # Store in the dynamic array: CONF_sectionname[key]=value - eval "CONF_${current_section}['$key']='$value'" + # Use a nameref for safe, eval-free assignment + local -n current_array="CONF_$current_section" + current_array["$key"]="$value" fi done < "$config_file" -} -# ------------------------------------------------------------------------------ +}# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ @@ -118,20 +131,20 @@ load_alias() { local section_name="CONF_$1" - # Check if the associative array for this section exists - if [[ "$(declare -p "$section_name" 2>/dev/null)" != "declare -A"* ]]; then - return 1 - fi + # Check if the associative array exists using declare -p + [[ "$(declare -p "$section_name" 2>/dev/null)" != "declare -A"* ]] && return 1 - # Reference the array keys - eval "local keys=\"\${!$section_name[@]}\"" + # Create a nameref to the section array + local -n current_aliases="$section_name" - for key in $keys; do - # Fetch the value for this specific key - eval "local cmd=\"\${$section_name[$key]}\"" + # Iterate safely over the keys of the associative array + for key in "${!current_aliases[@]}"; do + local cmd="${current_aliases[$key]}" - # Portability check: only alias if the command exists - local base_cmd=$(echo "$cmd" | awk '{print $1}') + # Extract the base command (first word) safely without awk + local base_cmd="${cmd%% *}" + + # Only alias if the base command is executable if command -v "$base_cmd" >/dev/null 2>&1; then alias "$key"="$cmd" fi @@ -146,16 +159,14 @@ load_conf() { local section_name="CONF_$1" - if [[ "$(declare -p "$section_name" 2>/dev/null)" != "declare -A"* ]]; then - return 1 - fi + [[ "$(declare -p "$section_name" 2>/dev/null)" != "declare -A"* ]] && return 1 - eval "local keys=\"\${!$section_name[@]}\"" + local -n current_vars="$section_name" - for key in $keys; do - eval "local val=\"\${$section_name[$key]}\"" - # Export as a standard shell variable - export "$key"="$val" + for key in "${!current_vars[@]}"; do + # Export the key/value pair as a standard shell variable + # We use 'export' directly; Bash handles the assignment safely here + export "$key"="${current_vars[$key]}" done } # ------------------------------------------------------------------------------ @@ -168,8 +179,10 @@ load_conf() # ------------------------------------------------------------------------------ # Store script's path (realpath -s resolve symlinks if profile.sh is a symlink) +# Because we're more likely to be sourced, we use BASH_SOURCE to get the path +# of the sourced file instead of $0 if [[ -z "$PROFILE_PATH" ]]; then - export MYPATH=$(dirname "$(realpath -s "$0")") + export MYPATH=$(dirname "$(realpath -s "${BASH_SOURCE[0]}")") else export MYPATH="$PROFILE_PATH" fi