Skip to content

Instantly share code, notes, and snippets.

@vroomfondel
Last active January 23, 2026 14:37
Show Gist options
  • Select an option

  • Save vroomfondel/7ef185d8d6d726fdf720ad0f4ef23619 to your computer and use it in GitHub Desktop.

Select an option

Save vroomfondel/7ef185d8d6d726fdf720ad0f4ef23619 to your computer and use it in GitHub Desktop.
envsubst implementation in pure bash
#!/bin/bash
# Pure Bash implementation of envsubst.
# Usage: ./envsubst.sh [SHELL-FORMAT]
# self-ref
# https://gist.githubusercontent.com/vroomfondel/7ef185d8d6d726fdf720ad0f4ef23619/raw/envsubst.sh
# per curl with cache-busting query-arg
# curl -L https://gist.githubusercontent.com/vroomfondel/7ef185d8d6d726fdf720ad0f4ef23619/raw/envsubst.sh?cache_bust=$(date +%s)
# per api (usage throttled)
# curl -s https://api.github.com/gists/7ef185d8d6d726fdf720ad0f4ef23619 | jq -r '.files["envsubst.sh"].content'
# If arguments were passed, extract the variable names to be replaced.
REPLACE_ONLY=""
if [ $# -gt 0 ]; then
# Extracts variables like $VAR or ${VAR} from the argument string.
REPLACE_ONLY=$(echo "$1" | grep -oE '\$([a-zA-Z_][a-zA-Z0-9_]*|\{[a-zA-Z_][a-zA-Z0-9_]*\})')
fi
# Function to process a single line.
process_line() {
local line="$1"
local result=""
# Regex for $VAR or ${VAR}.
# Matches $ followed by a valid shell variable name or {name}.
local regex='\$([a-zA-Z_][a-zA-Z0-9_]*|\{[a-zA-Z_][a-zA-Z0-9_]*\})'
local remaining="$line"
while [[ "$remaining" =~ $regex ]]; do
local full_match="${BASH_REMATCH[0]}"
local var_name_raw="${BASH_REMATCH[1]}"
# Split the remaining string: before, match, after.
local before="${remaining%%"$full_match"*}"
local after="${remaining#*"$full_match"}"
# Clean the variable name (remove curly braces).
local clean_var_name="${var_name_raw#\{}"
clean_var_name="${clean_var_name%\}}"
# Check if this variable should be replaced.
local should_replace=true
# Only replace if the variable is set in the environment.
if ! [[ -v "$clean_var_name" ]]; then
should_replace=false
fi
# If REPLACE_ONLY is set, only replace if the variable is in the list.
if [ "$should_replace" = true ] && [ -n "$REPLACE_ONLY" ]; then
if ! echo "$REPLACE_ONLY" | grep -qxFe "$full_match"; then
should_replace=false
fi
fi
if [ "$should_replace" = true ]; then
# Read value from the environment.
local var_value="${!clean_var_name}"
result="${result}${before}${var_value}"
else
result="${result}${before}${full_match}"
fi
remaining="$after"
done
result="${result}${remaining}"
printf "%s\n" "$result"
}
# Read from stdin line by line.
# IFS= preserves leading/trailing whitespace.
# -r prevents backslashes from being interpreted.
while IFS= read -r input_line || [ -n "$input_line" ]; do
process_line "$input_line"
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment