Last active
May 26, 2026 20:24
-
-
Save krzyzanowskim/07450322713433af08798a6ab0c0ce8f to your computer and use it in GitHub Desktop.
Load .env files from $HOME down to the current directory.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # load-dotenv-up.zsh | |
| # | |
| # Load .env files from $HOME down to the current directory. | |
| # Deeper .env files override parent .env files. | |
| # On directory change, variables touched by the previous .env chain are restored | |
| # to their original values or unset if they did not exist before. | |
| # | |
| # NOTE: .env files must be valid zsh syntax. | |
| # NOTE: Regular files and FIFO/named-pipe .env files are loaded. | |
| # A FIFO .env may block the shell until something writes to it. | |
| # | |
| # Installation: | |
| # 1. Save this file as: | |
| # ~/.zsh/plugins/load-dotenv-up.zsh | |
| # | |
| # 2. Add this line to ~/.zshrc: | |
| # source ~/.zsh/plugins/load-dotenv-up.zsh | |
| # | |
| # 3. Reload zsh: | |
| # source ~/.zshrc | |
| autoload -Uz add-zsh-hook | |
| typeset -gA _dotenv_up_prev_value | |
| typeset -gA _dotenv_up_prev_was_set | |
| typeset -ga _dotenv_up_touched | |
| _dotenv_up_snapshot_env() { | |
| emulate -L zsh | |
| local target="$1" | |
| local name value | |
| case "$target" in | |
| before) before=() ;; | |
| after) after=() ;; | |
| *) return 1 ;; | |
| esac | |
| while IFS='=' read -r name value; do | |
| # Environment names should be shell identifiers. Skip unusual names to avoid | |
| # treating them as arithmetic expressions in associative-array subscripts. | |
| [[ "$name" == [A-Za-z_][A-Za-z0-9_]* ]] || continue | |
| case "$target" in | |
| before) before[$name]="$value" ;; | |
| after) after[$name]="$value" ;; | |
| esac | |
| done < <(env) | |
| } | |
| _dotenv_up_restore_previous() { | |
| emulate -L zsh | |
| local name | |
| for name in "${_dotenv_up_touched[@]}"; do | |
| if [[ "${_dotenv_up_prev_was_set[$name]}" == "1" ]]; then | |
| export "$name=${_dotenv_up_prev_value[$name]}" | |
| else | |
| unset "$name" | |
| fi | |
| done | |
| _dotenv_up_touched=() | |
| _dotenv_up_prev_value=() | |
| _dotenv_up_prev_was_set=() | |
| } | |
| _dotenv_up_load() { | |
| emulate -L zsh | |
| _dotenv_up_restore_previous | |
| local cwd="${PWD:A}" | |
| local home="${HOME:A}" | |
| # Only load .env files when current directory is inside $HOME. | |
| [[ "$cwd" == "$home" || "$cwd" == "$home"/* ]] || return 0 | |
| local -A before after | |
| _dotenv_up_snapshot_env before | |
| local dir="$cwd" | |
| local -a dirs | |
| dirs=() | |
| while true; do | |
| dirs=("$dir" "${dirs[@]}") | |
| [[ "$dir" == "$home" ]] && break | |
| dir="${dir:h}" | |
| done | |
| local envfile | |
| for dir in "${dirs[@]}"; do | |
| envfile="$dir/.env" | |
| if [[ -f "$envfile" || -p "$envfile" ]]; then | |
| setopt allexport | |
| source "$envfile" | |
| unsetopt allexport | |
| fi | |
| done | |
| _dotenv_up_snapshot_env after | |
| local name | |
| # Variables added or changed by .env files. | |
| for name in "${(@k)after}"; do | |
| if [[ -z "${before[$name]+x}" || "${before[$name]}" != "${after[$name]}" ]]; then | |
| _dotenv_up_touched+=("$name") | |
| if [[ -n "${before[$name]+x}" ]]; then | |
| _dotenv_up_prev_was_set[$name]=1 | |
| _dotenv_up_prev_value[$name]="${before[$name]}" | |
| else | |
| _dotenv_up_prev_was_set[$name]=0 | |
| fi | |
| fi | |
| done | |
| # Variables removed by .env files. | |
| for name in "${(@k)before}"; do | |
| if [[ -z "${after[$name]+x}" ]]; then | |
| _dotenv_up_touched+=("$name") | |
| _dotenv_up_prev_was_set[$name]=1 | |
| _dotenv_up_prev_value[$name]="${before[$name]}" | |
| fi | |
| done | |
| } | |
| _dotenv_up_load | |
| add-zsh-hook chpwd _dotenv_up_load |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment