How to Persist Environment Variables from Subshell to Parent Shell in KornShell (ksh)


3 views

When working with KornShell (ksh), developers often face a common scenario where environment variables set in a subshell don't persist in the parent shell. This becomes particularly problematic when:

  • Managing multiple application environments (ABC/DEF in our case)
  • Switching between different sandbox configurations
  • Maintaining separate development and production environments

The fundamental issue stems from how shells handle execution:

# This runs in a subshell (variables won't persist)
./myscript.sh

# This sources the script (variables will persist)
. ./myscript.sh

Here are several approaches to solve this problem:

1. The Preferred Method: Source the Script

While you mentioned users forget to source, this remains the most reliable method:

# Best practice is to create an alias in user profiles
alias setup_env='. /path/to/myscript.sh'

2. Script Self-Sourcing

You can make the script detect if it's being sourced:

#!/bin/ksh

# If not sourced, re-execute with source
if [[ "$(basename -- "$0")" == "$(basename -- "${BASH_SOURCE[0]}")" ]]; then
    echo "Re-executing with source..."
    exec /bin/ksh -c "source \"$0\" \"$@\""
    exit 1
fi

# Main script logic here
case $INPUT in
abc) export BIN=${ABC_BIN} ;;
def) export BIN=${DEF_BIN} ;;
*)   export BIN=${BASE_BIN} ;;
esac

3. Environment Wrapper Function

Create a wrapper function in users' profiles:

function appenv() {
    case $1 in
        abc)
            export BIN=/opt/abc/bin
            export CFG=/etc/abc
            ;;
        def) 
            export BIN=/opt/def/bin
            export CFG=/etc/def
            ;;
        *)
            echo "Usage: appenv [abc|def]"
            return 1
            ;;
    esac
    echo "Environment set for $1"
}

For your sandbox scenario, consider this enhanced version:

#!/bin/ksh

if [[ "$0" != "${.sh.file}" ]]; then
    # Script is being sourced
    main() {
        PS3="Select application: "
        select app in abc def; do
            case $app in
                abc|def)
                    PS3="Select sandbox: "
                    select sandbox in $(ls -d /home/${USER}/sandbox_${app}_* 2>/dev/null); do
                        [ -n "$sandbox" ] && break
                    done
                    
                    export BIN="/opt/${app}/bin"
                    export CFG="${sandbox}/config"
                    export APP_HOME="${sandbox}"
                    echo "Configured ${app} environment using ${sandbox}"
                    break
                    ;;
                *)
                    echo "Invalid selection"
                    ;;
            esac
        done
    }
    main
else
    echo "This script must be sourced:" >&2
    echo "  . $0" >&2
    exit 1
fi
  • Always document whether scripts need sourcing in the header
  • Use descriptive variable names (APP_BIN instead of just BIN)
  • Consider using directories like /etc/profile.d/ for global environment settings
  • Implement error checking for sandbox directory existence

Watch out for these issues:

# Problem: This will exit the parent shell if sourced
exit 0

# Solution: Use return instead
return 0 2>/dev/null || exit 0

When working with Korn shell (ksh) scripts, developers often need to set environment variables that persist in the parent shell after script execution. A common challenge arises when variables are exported within a subshell but don't propagate to the parent shell environment.

#!/bin/ksh
# Example of variables that won't persist
export MY_VAR="value"

The standard solution is to source the script using the dot operator:

. ./myscript.sh

However, as noted in the original question, users frequently forget to source scripts, leading to frustration when variables aren't available in their shell session.

Alias Solution

One effective approach is to create aliases in users' profiles:

# Add to ~/.profile or ~/.kshrc
alias setup_env='. /path/to/myscript.sh'

Users can then run:

setup_env abc

Wrapper Function

For more complex scenarios, a shell function provides better flexibility:

function env_switcher() {
    case $1 in
        abc)
            export BIN=${ABC_BIN}
            export CFG=${ABC_CFG}
            ;;
        def)
            export BIN=${DEF_BIN}
            export CFG=${DEF_CFG}
            ;;
        *)
            export BIN=${BASE_BIN}
            export CFG=${BASE_CFG}
            ;;
    esac
    
    # Additional environment setup
    export ORA_HOME=${BIN%/bin}/oracle
    export PATH=${BIN}:${PATH}
    
    echo "Environment set for $1"
}

For sandbox management, consider this enhanced version:

function create_sandbox() {
    read "sandbox?Enter sandbox name: "
    case $1 in
        abc)
            export SANDBOX_HOME="${HOME}/sandbox_abc_${sandbox}"
            mkdir -p "${SANDBOX_HOME}"
            ;;
        def)
            export SANDBOX_HOME="${HOME}/sandbox_def_${sandbox}"
            mkdir -p "${SANDBOX_HOME}"
            ;;
    esac
    
    # Set other sandbox-specific variables
    export BIN="${SANDBOX_HOME}/bin"
    export LOGS="${SANDBOX_HOME}/logs"
}
  • Always document the required sourcing method clearly
  • Implement sanity checks in your scripts
  • Consider using a central configuration management system
  • Provide clear feedback about environment changes
# Example sanity check
if [ -z "$ABC_BIN" ] || [ -z "$DEF_BIN" ]; then
    echo "Error: Required base variables not set" >&2
    return 1
fi

Improve your scripts with better user feedback:

function set_env() {
    case $1 in
        abc|def)
            # Successful case
            export BIN=$(eval echo \${${1^^}_BIN})
            echo "Environment set for $1 (BIN=$BIN)"
            return 0
            ;;
        *)
            echo "Invalid option: $1" >&2
            echo "Usage: set_env [abc|def]" >&2
            return 1
            ;;
    esac
}