Skip to content

Instantly share code, notes, and snippets.

@webstrand
Last active March 9, 2026 17:46
Show Gist options
  • Select an option

  • Save webstrand/11df8f48e7146727d977263ccd84ced9 to your computer and use it in GitHub Desktop.

Select an option

Save webstrand/11df8f48e7146727d977263ccd84ced9 to your computer and use it in GitHub Desktop.
#!/bin/bash
load-environ() {
local -
set -euo pipefail
if [[ "${1:-}" == "--help" || "${1:-}" == "-h" || -z "${1:-}" ]]; then
cat <<'EOF'
load-environ — import variables from an environ(7) file
Usage:
load-environ FILE [OPS...] [-- CMD ARGS...]
If no OPS are provided, --all is implied.
OPS are processed left to right, building the set of variables to load:
--all add all safe variables
--none clear the set
--clobber add all variables, including unsafe ones (requires --)
VAR add a single variable from the file
K=V add a literal variable
-i, --include VAR add VAR (use to escape flag-shaped names: -i --none)
-x, --exclude VAR remove VAR from the set
If a CMD is provided, it is executed with the constructed
environment. Otherwise the constructed environment is exported
into the current shell.
Examples:
load-environ ./env # export everything safe
load-environ ./env PATH CC CFLAGS # export only these three
load-environ ./env -x SECRET # everything except SECRET
load-environ ./env -- make # run make under the environ
load-environ ./env --clobber -- bash # full environ, new shell
EOF
return 0
fi
local file="$1"
shift
if [[ ! -r "$file" ]]; then
echo "load-environ: cannot read '$file'" >&2
return 1
fi
# regex for vars that would break an interactive shell
local _le_blocked='^(BASH_.*|FUNCNAME|SHELLOPTS|BASHOPTS|SHLVL'
_le_blocked+='|UID|EUID|PPID|GROUPS'
_le_blocked+='|RANDOM|LINENO|SECONDS|PIPESTATUS|_'
_le_blocked+='|HOSTNAME|HOSTTYPE|MACHTYPE|OSTYPE'
_le_blocked+='|OPTIND|OPTERR|OPTARG'
_le_blocked+='|COMP_.*|DIRSTACK|REPLY'
_le_blocked+='|HOME|SHELL|USER|LOGNAME'
_le_blocked+='|IFS|PWD|OLDPWD|CDPATH'
_le_blocked+='|PS1|PS2|PS4|PROMPT_COMMAND'
_le_blocked+='|HISTFILE|HISTSIZE|HISTCONTROL|HISTIGNORE'
_le_blocked+='|IGNOREEOF|GLOBIGNORE'
_le_blocked+='|INPUTRC|FIGNORE'
_le_blocked+='|TERM|COLUMNS|LINES|COLORTERM'
_le_blocked+='|TERM_PROGRAM|TERM_PROGRAM_VERSION|VTE_VERSION'
_le_blocked+='|GPG_TTY'
_le_blocked+='|MAIL|MAILCHECK|MAILPATH)$'
# parse environ file into associative array
local -A envmap=()
local entry key
while IFS= read -r -d '' entry; do
[[ "$entry" == *=* ]] || continue
key="${entry%%=*}"
envmap["$key"]="$entry"
done < "$file"
# progressively build result map — start with all safe
local -A result=()
local must_exec=false
local past_sep=false
local default_all=true
for key in "${!envmap[@]}"; do
[[ "$key" =~ $_le_blocked ]] || result["$key"]="${envmap[$key]}"
done
while [[ $# -gt 0 ]]; do
if $past_sep; then break; fi
case "$1" in
--)
past_sep=true
shift
;;
--help|-h)
load-environ --help
return
;;
--all)
for key in "${!envmap[@]}"; do
[[ "$key" =~ $_le_blocked ]] || result["$key"]="${envmap[$key]}"
done
default_all=false
shift
;;
--clobber)
must_exec=true
for key in "${!envmap[@]}"; do
result["$key"]="${envmap[$key]}"
done
default_all=false
shift
;;
--none)
must_exec=false
result=()
default_all=false
shift
;;
--exclude|-x)
local ekey="${2:?-x requires an argument}"
unset 'result[$ekey]'
shift 2
;;
--include|-i)
if [[ $# -lt 2 ]]; then
echo "load-environ: -i requires an argument" >&2
return 1
fi
shift
;&
*)
if $default_all; then
result=()
default_all=false
fi
if [[ "$1" == *=* ]]; then
result["${1%%=*}"]="$1"
elif [[ -v "envmap[$1]" ]]; then
result["$1"]="${envmap[$1]}"
else
echo "load-environ: '$1' not found in $file" >&2
return 1
fi
shift
;;
esac
done
# build flat array
local -a vars=()
for entry in "${result[@]}"; do
vars+=("$entry")
done
if $past_sep; then
if [[ $# -eq 0 ]]; then
echo "load-environ: expected command after --" >&2
return 1
fi
env -- "${vars[@]}" "$@"
elif $must_exec; then
echo "load-environ: --clobber requires -- followed by a command" >&2
return 1
else
for entry in "${vars[@]}"; do
# shellcheck disable=SC2163 # $entry is KEY=value, not a bare name
export "$entry"
done
fi
}
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
has_sep=false
for arg in "$@"; do
[[ "$arg" == "--" ]] && has_sep=true && break
done
if ! $has_sep; then
echo "load-environ: no current shell; source this file, or provide -- CMD ARGS..." >&2
exit 1
fi
load-environ "$@"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment