Skip to content

Instantly share code, notes, and snippets.

@mrjk
Created May 15, 2024 18:51
Show Gist options
  • Save mrjk/48e6d1dca90263e0d011b829e1410705 to your computer and use it in GitHub Desktop.
Save mrjk/48e6d1dca90263e0d011b829e1410705 to your computer and use it in GitHub Desktop.
Simple shell script that generate env vars from files
#!/bin/bash
#
# Read one or more dotfiles, and load
#
# Author: mrjk
set -euo pipefail
_log ()
{
#return 0
>&2 echo "$@"
}
fn_update ()
{
local kind=$1
shift 1
case "$kind" in
env)
local name=$1
shift 1
_log "INFO: Update from env: $name"
STORED_VARS_ENV["$name"]="$@"
;;
file)
local file=$1
local name=$2
shift 2
if $DOTENV_FIRST_ONLY; then
# Ensure destination vars does not exists
if [[ -v STORED_VARS_FILES["$name"] ]]; then
_log "INFO: Skip already defined var '$name' from $file"
return 0
fi
fi
_log "INFO: Update from file '$file': $name"
STORED_VARS_FILES["$name"]="$@"
;;
*)
echo "Unsupported: $kind"
return 1
;;
esac
}
get_var_names ()
{
local file=$1
sed -En '/^[a-zA-Z0-9_]+=.*/s/([^=]*)=.*/\1/p' "$file"
}
show_env_vars ()
{
local prefix="env_"
for vname in ${VAR_NAMES[@]}; do
# Skip if known vars is not declared
[[ -v "$vname" ]] || continue
# Display loading function
local value=${!vname}
echo "fn_update env '$vname' \"$value\""
done
}
show_file_vars ()
{
local file=$1
local prefix=${2:-local_}
(
# Clear environment
unset ${VAR_NAMES[@]}
# Parse file without bash
eval "$(cat $file)"
for vname in ${VAR_NAMES[@]}; do
# Skip if known vars is not declared
if ! [[ -v "$vname" ]]; then
#_log INFO "Skip missing var: $vname in $file"
continue
else
_log DEBUG "Found var '$vname' in $file"
fi
# Display loading function
local value="${!vname}"
echo "fn_update file '$file' '$vname' \"$value\""
#if [[ -n "${value}" ]]; then
# #echo "${prefix}$vname=\"$value\""
# #echo "STORED_VARS['$vname']=\"$value\""
# #STORED_VARS["$vname"]="$value"
#fi
done
)
}
show_vals ()
{
local files=${@:-$DOTENV_ENV_FILES}
# Check missing files
local _files=
for file in $files; do
if [[ -f "$file" ]] ; then
_files="${_files} $file"
else
if ! $DOTENV_IGNORE_MISSING_FILES; then
>&2 echo "ERROR: Missing file: $file"
return 1
else
_log DEBUG "Ignore missing file: $file"
continue
fi
fi
done
files=$_files
# Build known vars list
local VAR_NAMES=()
for file in $files; do
for name in $(get_var_names "$file"); do
VAR_NAMES+=( "$name" )
done
done
_log "INFO: Var names: ${VAR_NAMES[@]}"
declare -A STORED_VARS_ENV=()
declare -A STORED_VARS_FILES=()
# Parse each files
arrays_code=$(
for file in $files; do
echo "# Parse file: $file"
show_file_vars $file local_
done
show_env_vars
)
# Process var definitions
eval "$arrays_code"
# Process var merging
declare -A FINAL_ENV=()
# Insert file variables
for name in "${!STORED_VARS_FILES[@]}"; do
#_log "INFO: var: ${name}: ${STORED_VARS_FILES[$name]}"
FINAL_ENV["$name"]=${STORED_VARS_FILES[$name]}
done
# Override environment vars
if $DOTENV_ENV_OVERRIDE; then
for name in "${!STORED_VARS_ENV[@]}"; do
#_log "INFO: env: ${name}: ${STORED_VARS_ENV[$name]}"
FINAL_ENV["$name"]=${STORED_VARS_ENV[$name]}
done
fi
# Generate output
>&2 echo 'INFO: Generated bash code:'
local prefix=''
local snames="${!FINAL_ENV[@]}"
if $DOTENV_SORTED; then
# Sort vars by name
snames=$( tr ' ' '\n' <<< "$snames" | sort )
fi
if $DOTENV_EXPORT; then
prefix="export "
fi
for name in $snames ; do
echo "${prefix}${name}=\"${FINAL_ENV[$name]}\""
done
}
show_usage ()
{
cat <<EOF
Simple tool to fetch environment variables from files
Usage:
$0 [show|run]
EOF
}
main ()
{
local cmd=$1
shift 1
# Set to true to enable alphabetical sort
DOTENV_SORTED=${DOTENV_SORTED:-true}
# Set to true to prefix output with export. Useful for sourcing shell output
DOTENV_EXPORT=${DOTENV_EXPORT:-false}
# Default env files to load. Missing files are ignored silentely
DOTENV_ENV_FILES=${DOTENV_ENV_FILES:-.env .env.secrets .env.local}
# Ignore missing files
DOTENV_IGNORE_MISSING_FILES=${DOTENV_IGNORE_MISSING_FILES:-true}
# Environment vars act as overrides
DOTENV_ENV_OVERRIDE=${DOTENV_ENV_OVERRIDE:-true}
# Set to true to only consider the first found variable
DOTENV_FIRST_ONLY=${DOTENV_FIRST_ONLY:-false}
# Process command
case "$cmd" in
show)
show_vals $@
;;
run)
eval "$(DOTENV_EXPORT=true show_vals)"
exec "$@"
;;
help|--help|-h)
show_usage
;;
*)
echo "Command not supported: $cmd"
return 1
;;
esac
}
main $@
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment