reenv is a lightweight Bash utility designed to track changes to the shell environment (variables, functions, aliases, completions, and Readline key bindings/variables) between two points in time and serialize those changes as sourceable Bash code.
This enables you to capture environment modifications (e.g., made within a subshell, a build script, or an installer) and replay them in your current shell or another shell session.
- reenv
# 0. Source reenv
. /path/to/reenv.bash
# 1. Initialize baseline snapshot
reenv-base
# 2. Modify environment (variables, functions, aliases, completions, and Readline bindings)
export MY_VAR="hello"
my_func() { echo "world"; }
alias my_alias="echo hello world"
# 3. Capture changes to a file
reenv-cap > delta.sh
# ... Later, in another terminal...
# 4. Apply changes in another shell session
source delta.sh- Tracks Variables, Functions, Aliases, Completions, and Readline Bindings: Detects new, modified, and deleted items (including Readline key bindings, macros, variables, and shell command bindings).
- Preserves Export Status: Correctly tracks and reapplies export status (
export/declare -x) for variables and functions. - Customizable Filter: Easily ignore specific variables or functions using a regular expression.
reenv exposes two main commands:
reenv-base: Captures the "before" snapshot (baseline) of all defined variables, functions, aliases, completions, and Readline bindings.reenv-cap: Captures the "after" snapshot, calculates the delta from the baseline, and prints sourceable Bash code representing the difference (e.g., adding/modifying environment variables, declaring functions, unsetting removed variables, and updating Readline bindings).
- Bash 4.2 or later (required for
declare -gglobal declarations). - Python 3 (required for
reenv-sortandreenv-commimplementations).
Simply clone the repository and source the script in your Bash shell or add it to your .bashrc / .bash_profile:
source /path/to/reenv.bashreenv only tracks shell and environment variables, functions, aliases, completions, and Readline bindings. It does not capture other aspects of the shell or system state, including:
- System/Process State:
- File system changes (creation, modification, or deletion of files/directories).
- Background or foreground processes started during the session, and background job lists/job control state.
- Active file descriptors, network connections, or pipe/stream redirections.
- Shell Attributes, Settings & Limits:
- The current working directory (e.g., calling
cdchanges directory, but the directory path changes are not captured ascdcommands;PWDandOLDPWDare ignored by default). - Active shell options (e.g., flags set via
shoptorset -o). - Shell resource limits (set via
ulimit). - The file creation mask (
umask). - Signal trap handlers (
trap). - Terminal settings (
stty).
- The current working directory (e.g., calling
- Ignored Variables & Parameters:
- Positional parameters (
$1,$2, etc., and$#,$@,$*). - Special shell parameters (e.g.,
$IFS,$!,$?,$$-pid). - Read-only shell variables and internal Bash/environment variables (e.g.,
BASH_*,FUNCNAME,RANDOM,USER,SECONDS, etc., which are filtered out to prevent corruption during replay).
- Positional parameters (
To track modifications in your active terminal:
# 1. Start the baseline snapshot
reenv-base
# 2. Modify the environment
export NEW_VAR="Hello World"
my_func() { echo "This is a function"; }
alias my_alias="echo 'This is an alias'"
# 3. Print the delta as sourceable bash code
reenv-capThis will print:
#a:my_alias(alias)
alias my_alias='echo '\''This is an alias'\'''
#f:my_func()
my_func ()
{
echo "This is a function"
}
#v:NEW_VAR
declare -g -x NEW_VAR="Hello World"If you run setup scripts or installer routines inside a subshell (or within a script) and want to load their environment changes back into your main shell:
# 1. Capture baseline in the main shell
reenv-base
# 2. Perform work in a subshell, dumping the delta to a file
(
export DEBIAN_FRONTEND=noninteractive
export PATH="/opt/my-app/bin:$PATH"
# Capture modifications made inside this subshell
reenv-cap > /tmp/env_delta.sh
)
# 3. Replay changes in the parent shell
source /tmp/env_delta.shSince reenv-cap generates standard shell declarations, any aliases and exported/unexported functions will be properly re-applied:
reenv-base
# Add an exported function
my_exported_func() {
echo "Exported function!"
}
export -f my_exported_func
# Delete an existing variable
unset SOME_OLD_VAR
# Capture the delta
reenv-cap > /tmp/delta.shApplying /tmp/delta.sh in another shell session will unset SOME_OLD_VAR, define my_exported_func, and call export -f my_exported_func to ensure it is available to child processes.
reenv can be customized using the following environment variables:
By default, reenv ignores internal Bash variables and read-only shell attributes (e.g., BASH_*, FUNCNAME, RANDOM, USER, PWD, COLUMNS, etc.).
If you want to ignore additional variables or functions, define REENV_SKIP with a regular expression pattern matching their names:
# Ignore any variables starting with "TEMP_" or containing "SECRET"
export REENV_SKIP="(^TEMP_|_SECRET_)"
# Take baseline
reenv-baseBy default, reenv writes environment snapshots to temporary files. If you want to use specific files instead of the default temporary files, you can specify custom file paths using the -b and -f options.
-
In
reenv-base: Use-b FILENAMEto capture the baseline snapshot. This will save the baseline state intoFILENAME.shand the unset definitions intoFILENAME-clear.sh.reenv-base -b /tmp/my_baseline
-
In
reenv-cap:- Use
-b FILENAMEto load the baseline snapshot fromFILENAME.shandFILENAME-clear.shinstead of the default temporary files. - Use
-f FILENAMEto save the "after" state snapshot intoFILENAME.shandFILENAME-clear.sh. - Use
-o FILENAMEto write the generated environment delta directly toFILENAMEinstead of printing to standard output.
If
-ois not specified,reenv-capprints the environment delta to standard output:# Calculate delta comparing custom baseline and custom current snapshot, and redirect stdout to a file reenv-cap -b /tmp/my_baseline -f /tmp/my_current > /tmp/delta.sh
- Use
You can pass the -q option to either reenv-base or reenv-cap to suppress status messages printed to stderr:
# Capture baseline quietly
reenv-base -q
# Capture delta quietly
reenv-cap -q > delta.shreenv tracks entire environment states and variables rather than identifying changes within individual variables.
-
No Internal/Incremental Deltas (e.g.,
PATHmodifications): If a variable's value is modified (for example, appending a path toPATHusingexport PATH="$PATH:/new/path"),reenvcaptures and re-emits the entire new value ofPATH(e.g.,declare -x PATH="...:/new/path"). It does not detect the incremental change or output self-referential definitions likeexport PATH="$PATH:/new/path". -
Local Variables Applied as Global: If a variable is local to an active shell function (i.e., declared with
local),reenvcaptures its value at the time of tracking. However, when the generated delta script is replayed, all captured variables are re-applied as global variables (usingdeclare -g).
reenv comes with a comprehensive test suite in reenv.bash.test. To run the tests, execute the script directly:
./reenv.bash.testThe following shell attributes are currently listed under "What is NOT Captured" but are technically viable candidates for future support:
- Save/Restore:
- Save: Query active options using
shopt -pand standard shell flags usingset +o. - Restore: Emit changed options directly (e.g.,
shopt -s autocdorset -o history).
- Save: Query active options using
- Caveats: Some shell options might change behavior in ways that disrupt the sourced script execution itself (e.g.,
errexitornounset), so care must be taken when re-applying them.
- Save/Restore:
- Save: Query active signal traps via
trap -p. - Restore: Reset removed traps using
trap - <signal>and declare new/modified ones usingtrap -- '<command>' <signal>.
- Save: Query active signal traps via
- Caveats: Signal traps are bound to the process, so some traps might not make sense or could behave unexpectedly when replayed in a different shell session with different process IDs.
- Save/Restore:
- Save: Query using
umask -p. - Restore: Re-apply using
umask <value>if it changes.
- Save: Query using
- Caveats: None.
- Save/Restore:
- Save: Parse option flags and limits from
ulimit -a(soft) andulimit -Ha(hard). - Restore: Apply changes via
ulimit -S <flag> <value>orulimit -H <flag> <value>.
- Save: Parse option flags and limits from
- Caveats: Non-root users cannot raise hard limits (only lower them) and cannot raise soft limits beyond the current shell's hard limits. To prevent replay scripts from failing, restore commands should be guarded (e.g., appending
2>/dev/null || true).
- Save/Restore:
- Save: Query current path using
pwd. - Restore: Emit
cd '<path>'if changed.
- Save: Query current path using
- Caveats: Changing the directory upon replay might be undesirable or confusing if the user only wanted to capture environment variables/aliases and not change their current location.
This project is licensed under the MIT License.