Linux source命令 详解

source命令用于在当前Shell环境中执行指定的脚本文件,而不是创建新的子Shell。这使得脚本中定义的变量、函数、别名等在当前Shell中立即生效,常用于加载配置文件和环境变量。

基本介绍

source命令作用

source命令的主要作用是在当前Shell环境中执行脚本:

  • 在当前Shell中执行脚本,而不是创建子Shell
  • 使脚本中定义的变量在当前Shell中生效
  • 加载函数和别名到当前Shell
  • 重新加载配置文件
  • 导入环境变量设置
  • 执行初始化脚本
与bash/sh执行的区别
执行方式 是否创建子Shell 变量是否继承
source script.sh 是(变量在当前Shell生效)
bash script.sh 否(变量在子Shell中)
./script.sh 否(需要执行权限)

基本语法

source 文件名 [参数]
# 或
. 文件名 [参数]

# 示例
source ~/.bashrc
source /etc/profile
source config.sh arg1 arg2

# 参数会传递给脚本
source script.sh arg1 arg2
# 在脚本中通过 $1, $2 等获取参数

点命令(.) - source的简写

点命令的用法
# 点命令和source完全等价
. ~/.bashrc           # 等同于 source ~/.bashrc
. /etc/profile        # 等同于 source /etc/profile

# 带参数的点命令
. script.sh arg1 arg2

# 特殊用法:在当前目录查找文件
. ./config.sh         # 执行当前目录下的config.sh

# 注意:点后面必须有空格
. file.sh    # 正确
.file.sh     # 错误(会被解释为隐藏文件)
点命令 vs source命令:
  • 功能相同:两者在Bash中功能完全相同
  • 兼容性:点命令是POSIX标准,source是Bash扩展
  • 可读性:source更易读,点命令更简洁
  • 建议:在脚本中使用点命令,在命令行使用source

常见用法

常用场景和示例
# 1. 重新加载Shell配置文件(最常用)
source ~/.bashrc      # 重新加载bash配置
source ~/.bash_profile # 重新加载bash_profile
source ~/.zshrc       # 重新加载zsh配置
source ~/.profile     # 重新加载profile

# 2. 加载环境变量文件
source /etc/environment
source ~/.env
source .env.local

# 3. 执行初始化脚本
source /etc/profile.d/*.sh  # 加载所有profile.d脚本
source /usr/local/bin/init.sh

# 4. 加载函数库
source /usr/lib/bash/functions.sh
source ~/.bash_functions

# 5. 加载别名定义
source ~/.bash_aliases

# 6. 设置特定项目环境
source venv/bin/activate    # Python虚拟环境
source /opt/ros/noetic/setup.bash  # ROS环境

# 7. 执行配置脚本
source configure --prefix=/usr/local

# 8. 测试脚本而不创建子Shell
source test_script.sh

# 9. 从标准输入读取并执行
echo "export PATH=\$PATH:/usr/local/bin" | source /dev/stdin

# 10. 条件加载配置
if [ -f ~/.custom_env ]; then
    source ~/.custom_env
fi

环境变量管理

环境变量的导入和导出
# 1. 创建环境变量配置文件
cat > ~/.myenv << 'EOF'
# 环境变量配置文件
export JAVA_HOME=/usr/lib/jvm/java-11-openjdk
export PATH=$JAVA_HOME/bin:$PATH
export EDITOR=vim
export LANG=en_US.UTF-8

# 项目特定变量
export PROJECT_HOME=~/projects/myapp
export DATABASE_URL=postgres://localhost/mydb

# 别名
alias ll='ls -la'
alias gp='git pull'

# 函数
myfunc() {
    echo "Hello from myfunc"
}
EOF

# 2. 加载环境变量文件
source ~/.myenv

# 3. 验证变量是否生效
echo $JAVA_HOME
echo $PATH

# 4. 动态更新PATH
# 创建一个添加路径的函数
add_to_path() {
    if [[ ":$PATH:" != *":$1:"* ]]; then
        export PATH="$1:$PATH"
    fi
}

# 保存到文件
echo 'add_to_path() {
    if [[ ":$PATH:" != *":$1:"* ]]; then
        export PATH="$1:$PATH"
    fi
}' > ~/.path_functions

# 加载函数
source ~/.path_functions

# 使用函数添加路径
add_to_path /usr/local/go/bin
add_to_path ~/bin

# 5. 卸载环境变量(从当前Shell移除)
# 创建一个unset_env.sh文件
cat > unset_env.sh << 'EOF'
unset JAVA_HOME
unset PROJECT_HOME
# 从PATH中移除特定目录
export PATH=$(echo $PATH | sed 's|:/usr/local/go/bin||')
EOF

# 执行卸载
source unset_env.sh

# 6. 环境变量继承演示
# script.sh内容:
# echo "In script: VAR=$VAR"
# export VAR="script_value"

# 创建测试脚本
cat > test_env.sh << 'EOF'
#!/bin/bash
echo "In script: VAR=$VAR"
export VAR="script_value"
EOF

# 测试1:使用bash执行(不继承)
VAR="parent_value"
bash test_env.sh          # 输出: In script: VAR=
echo $VAR                 # 输出: parent_value

# 测试2:使用source执行(继承)
VAR="parent_value"
source test_env.sh        # 输出: In script: VAR=parent_value
echo $VAR                 # 输出: script_value

函数管理

函数的定义和导入
# 1. 创建函数库文件
cat > ~/.bash_functions << 'EOF'
# 系统信息函数
sysinfo() {
    echo "=== 系统信息 ==="
    echo "主机名: $(hostname)"
    echo "内核: $(uname -r)"
    echo "内存: $(free -h | awk '/^Mem:/ {print $3 "/" $2}')"
    echo "磁盘: $(df -h / | awk 'NR==2 {print $4 "/" $2 "可用"}')"
}

# Git快捷函数
gcm() {
    git commit -m "$1"
}

gacp() {
    git add .
    git commit -m "$1"
    git push
}

# 文件操作函数
mkcd() {
    mkdir -p "$1"
    cd "$1"
}

# 网络函数
myip() {
    curl -s ifconfig.me
    echo
}

# 工具函数
# 提取压缩文件
extract() {
    if [ -f "$1" ]; then
        case "$1" in
            *.tar.bz2)   tar xjf "$1"     ;;
            *.tar.gz)    tar xzf "$1"     ;;
            *.bz2)       bunzip2 "$1"     ;;
            *.rar)       unrar x "$1"     ;;
            *.gz)        gunzip "$1"      ;;
            *.tar)       tar xf "$1"      ;;
            *.tbz2)      tar xjf "$1"     ;;
            *.tgz)       tar xzf "$1"     ;;
            *.zip)       unzip "$1"       ;;
            *.Z)         uncompress "$1"  ;;
            *.7z)        7z x "$1"        ;;
            *)           echo "'$1' 无法被提取" ;;
        esac
    else
        echo "'$1' 不是一个有效的文件"
    fi
}
EOF

# 2. 加载函数库
source ~/.bash_functions

# 3. 使用函数
sysinfo
mkcd new_directory
extract archive.tar.gz

# 4. 查看已定义的函数
declare -f sysinfo      # 查看特定函数定义
declare -F              # 查看所有函数名
typeset -f              # 查看所有函数定义

# 5. 函数覆盖和重载
# 如果函数已存在,重新加载会覆盖
cat > new_functions.sh << 'EOF'
sysinfo() {
    echo "简洁版系统信息:"
    echo "主机: $(hostname)"
    echo "用户: $(whoami)"
}
EOF

source new_functions.sh
sysinfo  # 现在使用新版本

# 6. 删除函数
unset -f sysinfo  # 删除sysinfo函数
declare -F        # 确认函数已删除

# 7. 条件加载函数
if [ -f ~/.bash_functions ]; then
    source ~/.bash_functions
else
    echo "函数库文件不存在"
fi

# 8. 函数参数传递
cat > func_with_args.sh << 'EOF'
greet() {
    local name=${1:-"World"}
    local greeting=${2:-"Hello"}
    echo "$greeting, $name!"
}
EOF

source func_with_args.sh
greet                  # 输出: Hello, World!
greet "Alice"          # 输出: Hello, Alice!
greet "Bob" "Hi"       # 输出: Hi, Bob!

配置文件重载

配置文件的重载和管理
# 1. 创建配置文件管理脚本
cat > ~/reload_configs.sh << 'EOF'
#!/bin/bash
# 配置文件重载脚本

reload_bash() {
    echo "重新加载 bash 配置..."
    if [ -f ~/.bash_profile ]; then
        source ~/.bash_profile
    elif [ -f ~/.bash_login ]; then
        source ~/.bash_login
    elif [ -f ~/.profile ]; then
        source ~/.profile
    fi

    if [ -f ~/.bashrc ]; then
        source ~/.bashrc
    fi

    echo "bash 配置已重载"
}

reload_zsh() {
    echo "重新加载 zsh 配置..."
    if [ -f ~/.zshrc ]; then
        source ~/.zshrc
    fi
    echo "zsh 配置已重载"
}

reload_all() {
    case "$SHELL" in
        */bash)
            reload_bash
            ;;
        */zsh)
            reload_zsh
            ;;
        *)
            echo "未知的shell: $SHELL"
            ;;
    esac

    # 加载通用配置
    if [ -f ~/.aliases ]; then
        source ~/.aliases
    fi

    if [ -f ~/.functions ]; then
        source ~/.functions
    fi

    if [ -f ~/.env ]; then
        source ~/.env
    fi
}

# 根据参数执行
case "$1" in
    bash)
        reload_bash
        ;;
    zsh)
        reload_zsh
        ;;
    all|"")
        reload_all
        ;;
    *)
        echo "用法: $0 {bash|zsh|all}"
        ;;
esac
EOF

chmod +x ~/reload_configs.sh

# 2. 使用重载脚本
~/reload_configs.sh      # 重载所有配置
~/reload_configs.sh bash # 只重载bash配置
~/reload_configs.sh zsh  # 只重载zsh配置

# 3. 创建alias简化操作
echo "alias reload='source ~/.bashrc'" >> ~/.bashrc
echo "alias rl='reload'" >> ~/.bashrc
source ~/.bashrc

# 现在可以直接使用
reload  # 重新加载bash配置
rl      # 简写

# 4. 自动重载配置(当文件变化时)
# 使用inotifywait监控文件变化
# 安装inotify-tools: sudo apt-get install inotify-tools

cat > ~/auto_reload.sh << 'EOF'
#!/bin/bash
# 自动重载配置文件
CONFIG_FILES=(
    "$HOME/.bashrc"
    "$HOME/.bash_aliases"
    "$HOME/.bash_functions"
    "$HOME/.profile"
)

echo "开始监控配置文件变化..."

while true; do
    # 等待任何配置文件变化
    inotifywait -e modify "${CONFIG_FILES[@]}" 2>/dev/null

    # 文件变化后重新加载
    echo "检测到配置文件变化,重新加载..."
    for file in "${CONFIG_FILES[@]}"; do
        if [ -f "$file" ]; then
            echo "加载: $file"
            source "$file"
        fi
    done
    echo "配置文件已重载"
done
EOF

chmod +x ~/auto_reload.sh

# 5. 安全重载(避免循环加载)
cat > ~/.safe_reload.sh << 'EOF'
#!/bin/bash
# 安全重载配置,避免循环

# 检查是否已经在重载中
if [ -n "$RELOADING" ]; then
    echo "已经在重载过程中,跳过..."
    return 0
fi

export RELOADING=1

# 要重载的文件列表
FILES=(
    ~/.bashrc
    ~/.bash_aliases
    ~/.bash_functions
    ~/.profile
)

for file in "${FILES[@]}"; do
    if [ -f "$file" ]; then
        echo "加载: $file"
        # 使用点命令而不是source,避免某些shell的兼容性问题
        . "$file"
    fi
done

unset RELOADING
echo "安全重载完成"
EOF

# 6. 测试配置文件是否有效
cat > ~/test_config.sh << 'EOF'
#!/bin/bash
# 测试配置文件语法
TEST_FILES=("$@")

for file in "${TEST_FILES[@]}"; do
    if [ ! -f "$file" ]; then
        echo "错误: 文件不存在 - $file"
        continue
    fi

    echo "测试: $file"

    # 使用bash -n检查语法
    if bash -n "$file"; then
        echo "✓ 语法正确"

        # 尝试加载测试
        if source "$file" 2>/dev/null; then
            echo "✓ 加载成功"
        else
            echo "✗ 加载失败"
        fi
    else
        echo "✗ 语法错误"
    fi
    echo "---"
done
EOF

chmod +x ~/test_config.sh
~/test_config.sh ~/.bashrc ~/.bash_aliases

实战示例

示例1:项目环境配置管理
#!/bin/bash
# 项目环境配置管理器
# 文件名:project_env_manager.sh

PROJECTS_DIR="$HOME/projects"
ENV_DIR="$HOME/.project_envs"

# 初始化
init_manager() {
    mkdir -p "$PROJECTS_DIR"
    mkdir -p "$ENV_DIR"
    echo "项目环境管理器初始化完成"
}

# 创建项目环境
create_project_env() {
    local project_name="$1"
    local project_path="$PROJECTS_DIR/$project_name"
    local env_file="$ENV_DIR/$project_name.env"

    if [ -z "$project_name" ]; then
        echo "使用方法: create_project_env 项目名"
        return 1
    fi

    # 创建项目目录
    mkdir -p "$project_path"

    # 创建环境配置文件
    cat > "$env_file" << EOF
# 项目: $project_name
# 创建时间: $(date)
# 环境配置文件

# 项目路径
export PROJECT_NAME="$project_name"
export PROJECT_PATH="$project_path"

# 添加到PATH
export PATH="\$PROJECT_PATH/bin:\$PATH"

# 项目特定变量
export PROJECT_ENV="development"
export LOG_LEVEL="INFO"

# 别名
alias pcd="cd \$PROJECT_PATH"
alias prun="cd \$PROJECT_PATH && make run"
alias ptest="cd \$PROJECT_PATH && make test"

# 函数
pstatus() {
    echo "=== 项目状态 ==="
    echo "名称: \$PROJECT_NAME"
    echo "路径: \$PROJECT_PATH"
    echo "环境: \$PROJECT_ENV"
    echo "时间: \$(date)"
}

# 加载项目特定配置
if [ -f "\$PROJECT_PATH/.env.local" ]; then
    source "\$PROJECT_PATH/.env.local"
fi
EOF

    echo "项目 '$project_name' 环境已创建"
    echo "环境文件: $env_file"
    echo "项目目录: $project_path"
}

# 激活项目环境
activate_project() {
    local project_name="$1"
    local env_file="$ENV_DIR/$project_name.env"

    if [ -z "$project_name" ]; then
        echo "可用项目:"
        ls "$ENV_DIR"/*.env 2>/dev/null | xargs -n1 basename | sed 's/\.env$//'
        return 0
    fi

    if [ ! -f "$env_file" ]; then
        echo "错误: 项目 '$project_name' 不存在"
        return 1
    fi

    # 保存当前环境
    if [ -n "$CURRENT_PROJECT" ]; then
        deactivate_project
    fi

    # 激活新项目环境
    echo "激活项目: $project_name"
    source "$env_file"
    export CURRENT_PROJECT="$project_name"

    # 进入项目目录
    cd "$PROJECT_PATH" 2>/dev/null || true

    echo "项目环境已激活"
    pstatus
}

# 停用项目环境
deactivate_project() {
    if [ -z "$CURRENT_PROJECT" ]; then
        echo "没有激活的项目"
        return 0
    fi

    local project_name="$CURRENT_PROJECT"
    local env_file="$ENV_DIR/$project_name.env"

    # 从env文件获取要unset的变量
    if [ -f "$env_file" ]; then
        # 提取export语句中的变量名
        local vars_to_unset=$(grep '^export' "$env_file" | \
            sed 's/^export //' | \
            cut -d= -f1 | \
            tr '\n' ' ')

        # 取消设置变量
        for var in $vars_to_unset; do
            unset "$var" 2>/dev/null || true
        done
    fi

    # 从PATH中移除项目路径
    export PATH=$(echo "$PATH" | sed "s|$PROJECTS_DIR/[^:]*/bin:||g")

    unset CURRENT_PROJECT
    echo "项目 '$project_name' 环境已停用"
}

# 列出所有项目
list_projects() {
    echo "=== 项目列表 ==="

    if [ ! -d "$ENV_DIR" ] || [ -z "$(ls -A "$ENV_DIR")" ]; then
        echo "暂无项目"
        return 0
    fi

    for env_file in "$ENV_DIR"/*.env; do
        local project_name=$(basename "$env_file" .env)
        local project_path="$PROJECTS_DIR/$project_name"
        local is_active=""

        if [ "$project_name" = "$CURRENT_PROJECT" ]; then
            is_active=" (激活中)"
        fi

        echo "项目: $project_name$is_active"
        echo "  环境文件: $env_file"
        echo "  项目目录: $project_path"

        if [ -d "$project_path" ]; then
            echo "  目录状态: 存在"
        else
            echo "  目录状态: 不存在"
        fi
        echo ""
    done
}

# 删除项目环境
delete_project_env() {
    local project_name="$1"

    if [ -z "$project_name" ]; then
        echo "使用方法: delete_project_env 项目名"
        return 1
    fi

    # 如果正在激活,先停用
    if [ "$project_name" = "$CURRENT_PROJECT" ]; then
        deactivate_project
    fi

    local env_file="$ENV_DIR/$project_name.env"
    local project_path="$PROJECTS_DIR/$project_name"

    # 确认删除
    echo "即将删除:"
    echo "  环境文件: $env_file"
    echo "  项目目录: $project_path"
    echo -n "确认删除?(y/N): "
    read -r confirm

    if [ "$confirm" = "y" ] || [ "$confirm" = "Y" ]; then
        rm -f "$env_file"
        echo "环境文件已删除"

        echo -n "是否同时删除项目目录?(y/N): "
        read -r confirm_dir

        if [ "$confirm_dir" = "y" ] || [ "$confirm_dir" = "Y" ]; then
            rm -rf "$project_path"
            echo "项目目录已删除"
        fi

        echo "项目 '$project_name' 已删除"
    else
        echo "取消删除"
    fi
}

# 主菜单
main_menu() {
    init_manager

    while true; do
        echo ""
        echo "=== 项目环境管理器 ==="
        echo "1. 创建新项目环境"
        echo "2. 激活项目环境"
        echo "3. 停用当前项目"
        echo "4. 列出所有项目"
        echo "5. 删除项目环境"
        echo "6. 显示当前项目"
        echo "7. 退出"
        echo -n "请选择 [1-7]: "

        read -r choice

        case $choice in
            1)
                echo -n "请输入项目名: "
                read -r project_name
                create_project_env "$project_name"
                ;;
            2)
                echo -n "请输入要激活的项目名(留空查看列表): "
                read -r project_name
                activate_project "$project_name"
                ;;
            3)
                deactivate_project
                ;;
            4)
                list_projects
                ;;
            5)
                echo -n "请输入要删除的项目名: "
                read -r project_name
                delete_project_env "$project_name"
                ;;
            6)
                if [ -n "$CURRENT_PROJECT" ]; then
                    echo "当前激活项目: $CURRENT_PROJECT"
                    pstatus 2>/dev/null || echo "无法显示项目状态"
                else
                    echo "没有激活的项目"
                fi
                ;;
            7)
                echo "退出"
                exit 0
                ;;
            *)
                echo "无效选择"
                ;;
        esac
    done
}

# 运行主菜单
main_menu
示例2:安全的环境变量加载器
#!/bin/bash
# 安全的环境变量加载器
# 文件名:secure_env_loader.sh

ENV_DIR="$HOME/.secure_envs"
KEY_FILE="$HOME/.env_key"
LOG_FILE="/var/log/env_loader.log"

# 初始化
init_loader() {
    mkdir -p "$ENV_DIR"
    touch "$LOG_FILE"

    # 生成加密密钥(如果不存在)
    if [ ! -f "$KEY_FILE" ]; then
        openssl rand -base64 32 > "$KEY_FILE"
        chmod 600 "$KEY_FILE"
        echo "已生成新的加密密钥"
    fi

    echo "安全环境加载器初始化完成"
}

# 记录日志
log_message() {
    local level="$1"
    local message="$2"
    local timestamp=$(date "+%Y-%m-%d %H:%M:%S")

    echo "[$timestamp] [$level] $message" >> "$LOG_FILE"
}

# 加密环境变量文件
encrypt_env() {
    local env_name="$1"
    local env_file="$ENV_DIR/$env_name.env"
    local enc_file="$ENV_DIR/$env_name.env.enc"

    if [ -z "$env_name" ]; then
        echo "使用方法: encrypt_env 环境名"
        return 1
    fi

    if [ ! -f "$env_file" ]; then
        echo "错误: 环境文件不存在 - $env_file"
        return 1
    fi

    # 加密文件
    openssl enc -aes-256-cbc -salt -in "$env_file" -out "$enc_file" -pass file:"$KEY_FILE"

    if [ $? -eq 0 ]; then
        # 删除明文文件
        rm -f "$env_file"
        echo "环境 '$env_name' 已加密"
        log_message "INFO" "加密环境: $env_name"
    else
        echo "加密失败"
        log_message "ERROR" "加密失败: $env_name"
    fi
}

# 解密并加载环境变量
load_env() {
    local env_name="$1"
    local enc_file="$ENV_DIR/$env_name.env.enc"
    local temp_file="/tmp/$env_name.env.$$"

    if [ -z "$env_name" ]; then
        echo "可用环境:"
        ls "$ENV_DIR"/*.env.enc 2>/dev/null | xargs -n1 basename | sed 's/\.env\.enc$//'
        return 0
    fi

    if [ ! -f "$enc_file" ]; then
        echo "错误: 加密环境文件不存在 - $enc_file"
        return 1
    fi

    # 解密到临时文件
    openssl enc -aes-256-cbc -d -in "$enc_file" -out "$temp_file" -pass file:"$KEY_FILE" 2>/dev/null

    if [ $? -ne 0 ]; then
        echo "解密失败,请检查密钥"
        log_message "ERROR" "解密失败: $env_name"
        rm -f "$temp_file"
        return 1
    fi

    # 检查文件语法
    if ! bash -n "$temp_file" 2>/dev/null; then
        echo "错误: 环境文件语法错误"
        log_message "ERROR" "语法错误: $env_name"
        rm -f "$temp_file"
        return 1
    fi

    # 验证文件内容
    local validation_result=$(validate_env_file "$temp_file")
    if [ -n "$validation_result" ]; then
        echo "验证失败: $validation_result"
        log_message "WARNING" "验证失败: $env_name - $validation_result"
        echo -n "是否继续加载?(y/N): "
        read -r confirm

        if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then
            rm -f "$temp_file"
            return 1
        fi
    fi

    # 加载环境变量
    echo "加载环境: $env_name"

    # 备份当前重要环境变量
    backup_env_variables

    # 执行加载
    if source "$temp_file"; then
        echo "环境加载成功"
        log_message "INFO" "加载成功: $env_name"
        export CURRENT_ENV="$env_name"
    else
        echo "环境加载失败"
        log_message "ERROR" "加载失败: $env_name"
        # 恢复备份的环境变量
        restore_env_variables
    fi

    # 清理临时文件
    rm -f "$temp_file"
}

# 验证环境文件
validate_env_file() {
    local file="$1"
    local errors=""

    # 检查是否有危险的命令
    if grep -q -E "(rm\s+-rf|mkfs|dd\s+if=|:(){:|:&};:|chmod\s+777)" "$file"; then
        errors="文件包含危险命令"
    fi

    # 检查是否有可疑的变量名
    if grep -q -E "^(export\s+)?(PASSWORD|SECRET|KEY|TOKEN)=" "$file"; then
        errors="${errors}包含敏感变量名; "
    fi

    # 检查是否有外部命令执行
    if grep -q -E "\$(\(|`)" "$file"; then
        errors="${errors}包含命令替换; "
    fi

    echo "$errors"
}

# 备份环境变量
backup_env_variables() {
    # 创建备份文件
    local backup_file="/tmp/env_backup.$$"

    # 备份重要变量
    {
        echo "# 环境变量备份 - $(date)"
        echo "# 用户: $(whoami)"
        echo "# 主机: $(hostname)"
        echo ""

        # 备份PATH
        echo "OLD_PATH=\"$PATH\""

        # 备份其他重要变量
        for var in PATH LD_LIBRARY_PATH PYTHONPATH JAVA_HOME HOME USER; do
            if [ -n "${!var}" ]; then
                echo "OLD_${var}=\"${!var}\""
            fi
        done
    } > "$backup_file"

    export ENV_BACKUP_FILE="$backup_file"
}

# 恢复环境变量
restore_env_variables() {
    if [ -n "$ENV_BACKUP_FILE" ] && [ -f "$ENV_BACKUP_FILE" ]; then
        echo "恢复环境变量..."
        source "$ENV_BACKUP_FILE"

        # 恢复PATH
        if [ -n "$OLD_PATH" ]; then
            export PATH="$OLD_PATH"
        fi

        # 清理备份文件
        rm -f "$ENV_BACKUP_FILE"
        unset ENV_BACKUP_FILE
        echo "环境变量已恢复"
    fi
}

# 创建新的环境文件
create_env() {
    local env_name="$1"
    local env_file="$ENV_DIR/$env_name.env"

    if [ -z "$env_name" ]; then
        echo "使用方法: create_env 环境名"
        return 1
    fi

    if [ -f "$env_file" ] || [ -f "$env_file.enc" ]; then
        echo "错误: 环境 '$env_name' 已存在"
        return 1
    fi

    # 使用编辑器创建文件
    echo "创建新环境文件: $env_file"
    echo "# 环境: $env_name" > "$env_file"
    echo "# 创建时间: $(date)" >> "$env_file"
    echo "" >> "$env_file"
    echo "# 示例:" >> "$env_file"
    echo "# export VAR_NAME=\"value\"" >> "$env_file"
    echo "# alias ll='ls -la'" >> "$env_file"
    echo "" >> "$env_file"

    # 打开编辑器
    ${EDITOR:-vi} "$env_file"

    # 询问是否加密
    echo -n "是否加密此环境文件?(Y/n): "
    read -r encrypt_choice

    if [ "$encrypt_choice" != "n" ] && [ "$encrypt_choice" != "N" ]; then
        encrypt_env "$env_name"
    fi

    log_message "INFO" "创建环境: $env_name"
}

# 显示环境内容
show_env() {
    local env_name="$1"
    local enc_file="$ENV_DIR/$env_name.env.enc"
    local temp_file="/tmp/$env_name.show.$$"

    if [ -z "$env_name" ]; then
        echo "使用方法: show_env 环境名"
        return 1
    fi

    if [ ! -f "$enc_file" ]; then
        echo "错误: 环境不存在 - $env_name"
        return 1
    fi

    # 解密并显示
    openssl enc -aes-256-cbc -d -in "$enc_file" -out "$temp_file" -pass file:"$KEY_FILE" 2>/dev/null

    if [ $? -eq 0 ]; then
        echo "=== 环境: $env_name ==="
        cat "$temp_file"
        echo "========================"
        rm -f "$temp_file"
    else
        echo "解密失败"
        rm -f "$temp_file"
        return 1
    fi
}

# 主函数
main() {
    init_loader

    echo "=== 安全环境加载器 ==="
    echo "1. 创建新环境"
    echo "2. 加载环境"
    echo "3. 加密环境"
    echo "4. 查看环境内容"
    echo "5. 查看日志"
    echo "6. 退出"
    echo -n "请选择 [1-6]: "

    read -r choice

    case $choice in
        1)
            echo -n "请输入环境名: "
            read -r env_name
            create_env "$env_name"
            ;;
        2)
            echo -n "请输入要加载的环境名(留空查看列表): "
            read -r env_name
            load_env "$env_name"
            ;;
        3)
            echo -n "请输入要加密的环境名: "
            read -r env_name
            encrypt_env "$env_name"
            ;;
        4)
            echo -n "请输入要查看的环境名: "
            read -r env_name
            show_env "$env_name"
            ;;
        5)
            echo "=== 加载日志 ==="
            tail -20 "$LOG_FILE"
            ;;
        6)
            echo "退出"
            exit 0
            ;;
        *)
            echo "无效选择"
            ;;
    esac
}

# 运行
main

故障排除

# 错误:bash: source: 文件名: 没有那个文件或目录
# 原因:文件不存在或路径错误

# 解决方案:
# 1. 检查文件是否存在
ls -la 文件名

# 2. 使用绝对路径或正确相对路径
source /home/user/.bashrc          # 绝对路径
source ~/.bashrc                   # 家目录
source ./config.sh                 # 当前目录
source ../config/config.sh         # 上级目录

# 3. 检查文件权限
ls -la .bashrc
# 需要读取权限,如果是脚本还需要执行权限

# 4. 使用find命令查找文件
find / -name ".bashrc" 2>/dev/null

# 5. 检查文件名是否正确
# 注意大小写:.bashrc 不是 .Bashrc

# 6. 对于点命令,注意空格
. ./file.sh    # 正确
. ./file.sh    # 错误,点后无空格
source ./file.sh  # 使用source更清晰

# 7. 如果是符号链接,检查链接目标
ls -la ~/.bashrc
readlink ~/.bashrc

# 8. 检查文件系统挂载
# 如果文件在远程或特殊文件系统,确保已挂载

# 9. 使用条件判断避免错误
if [ -f ~/.bashrc ]; then
    source ~/.bashrc
else
    echo "文件不存在"
fi

# 10. 使用默认文件作为备选
if [ -f ~/.custom_bashrc ]; then
    source ~/.custom_bashrc
elif [ -f ~/.bashrc ]; then
    source ~/.bashrc
fi

# 问题:source后变量未正确设置或被覆盖
# 原因:作用域问题或变量名冲突

# 解决方案:
# 1. 使用export确保变量导出
# 在配置文件中:
export PATH=$PATH:/usr/local/bin
export JAVA_HOME=/usr/lib/jvm/java-11

# 2. 检查变量是否被后续脚本覆盖
# 按顺序加载配置
source config1.sh
source config2.sh  # 可能覆盖config1.sh的变量

# 3. 使用唯一变量名避免冲突
# 使用前缀
export MYAPP_PATH=/opt/myapp
export MYAPP_CONFIG=/etc/myapp.conf

# 4. 使用local关键字避免影响全局(在函数中)
myfunc() {
    local local_var="只在函数内有效"
    export global_var="全局有效"
}

# 5. 检查变量作用域
# script.sh内容:
# var="script_value"
# 执行:
var="global_value"
source script.sh
echo $var  # 输出: script_value(被修改)

# 6. 使用只读变量保护重要变量
readonly IMPORTANT_VAR="important"
source some_script.sh  # 如果尝试修改IMPORTANT_VAR会报错

# 7. 备份重要变量
OLD_PATH=$PATH
source config.sh
# 如果出现问题,可以恢复
# PATH=$OLD_PATH

# 8. 检查变量引用是否正确
# 错误的引用方式
export PATH=PATH:/new/path  # 缺少$
# 正确的引用方式
export PATH=$PATH:/new/path

# 9. 使用env命令检查所有环境变量
env | grep PATH

# 10. 使用declare查看变量属性
declare -p PATH  # 查看PATH变量的定义

# 问题:source导致无限循环或递归调用
# 原因:配置文件互相调用或循环引用

# 解决方案:
# 1. 避免配置文件循环引用
# .bashrc 中:
if [ -f ~/.bash_aliases ]; then
    source ~/.bash_aliases  # 正确
fi

# 不要在.bash_aliases中再source .bashrc

# 2. 使用标志变量防止重复加载
if [ -z "$BASHRC_LOADED" ]; then
    export BASHRC_LOADED=1
    # 加载配置...
    source ~/.bash_aliases
fi

# 3. 检查脚本是否source自身
# script.sh中:
# source script.sh  # 错误!会导致递归

# 4. 使用绝对路径避免意外
source /home/user/.bashrc  # 而不是 source ~/.bashrc

# 5. 限制source深度
export SOURCE_DEPTH=${SOURCE_DEPTH:-0}
if [ $SOURCE_DEPTH -gt 5 ]; then
    echo "警告:source调用深度超过5层"
    return 1
fi
export SOURCE_DEPTH=$((SOURCE_DEPTH + 1))
source some_file.sh
export SOURCE_DEPTH=$((SOURCE_DEPTH - 1))

# 6. 使用trap捕获问题
cleanup() {
    echo "清理中..."
    # 清理操作
}
trap cleanup EXIT INT TERM

# 7. 检查文件是否在source自身
if [ "$BASH_SOURCE" = "$0" ]; then
    echo "脚本直接执行"
else
    echo "脚本被source"
fi

# 8. 使用安全source函数
safe_source() {
    local file="$1"
    local max_depth=10
    local current_depth=${SAFE_SOURCE_DEPTH:-0}

    if [ $current_depth -ge $max_depth ]; then
        echo "错误:source深度超过限制"
        return 1
    fi

    export SAFE_SOURCE_DEPTH=$((current_depth + 1))

    if [ -f "$file" ]; then
        source "$file"
    else
        echo "文件不存在: $file"
    fi

    export SAFE_SOURCE_DEPTH=$current_depth
}

# 使用
safe_source ~/.bashrc

# 9. 记录source调用
export SOURCE_LOG="/tmp/source_log.$$"
echo "source $1" >> "$SOURCE_LOG"
source "$1"

# 问题:source大文件导致Shell启动慢或内存占用高
# 原因:配置文件太大或包含复杂操作

# 解决方案:
# 1. 分割大配置文件
# 将.bashrc分割成多个小文件
source ~/.bash_aliases
source ~/.bash_functions
source ~/.bash_env

# 2. 延迟加载
# 使用函数包装,需要时才加载
load_completion() {
    source /etc/bash_completion
    unset -f load_completion
}

# 3. 条件加载
# 只在需要时加载
if [ -n "$PS1" ]; then
    # 交互式Shell才加载
    source ~/.bash_interactive
fi

# 4. 使用缓存
if [ ! -f ~/.bash_cache ] || [ ~/.bashrc -nt ~/.bash_cache ]; then
    # 重新生成缓存
    source ~/.bashrc > ~/.bash_cache 2>&1
else
    # 使用缓存
    source ~/.bash_cache
fi

# 5. 优化配置文件
# 避免在配置文件中执行耗时操作
# 错误:在.bashrc中
# find / -name "*.txt" 2>/dev/null  # 非常慢!

# 6. 使用后台加载
# 将耗时操作放到后台
(
    # 后台执行的初始化
    source ~/.slow_init.sh
) &

# 7. 按需加载函数
# 使用函数包装,第一次调用时加载
git_prompt() {
    # 第一次调用时加载git-prompt
    if ! type -t __git_ps1 >/dev/null; then
        source /usr/share/git-core/contrib/completion/git-prompt.sh
    fi
    __git_ps1 "$@"
}

# 8. 使用lazy source技术
lazy_source() {
    local file="$1"
    local var_name="LAZY_LOADED_$(echo "$file" | tr './' '__')"

    if [ -z "${!var_name}" ]; then
        source "$file"
        export "$var_name=1"
    fi
}

# 使用
lazy_source ~/.bash_completion

# 9. 配置文件分析工具
analyze_bashrc() {
    echo "分析 ~/.bashrc:"
    echo "总行数: $(wc -l < ~/.bashrc)"
    echo "空行数: $(grep -c '^$' ~/.bashrc)"
    echo "注释行数: $(grep -c '^#' ~/.bashrc)"
    echo "source命令数: $(grep -c '^source\|^\.' ~/.bashrc)"
    echo "最慢的命令:"
    # 这里可以添加性能分析
}

# 10. 使用profiling
# 在~/.bashrc开头添加:
# PS4='+ $(date "+%s.%N")\011 '
# exec 3>&2 2>/tmp/bashstart.$$.log
# set -x

# 在结尾添加:
# set +x
# exec 2>&3 3>&-

# 然后分析日志文件
source命令速查表
基本用法:
  • source file.sh - 执行脚本
  • . file.sh - 点命令(相同功能)
  • source ~/.bashrc - 重载配置
  • source /etc/profile - 加载系统配置
常见文件:
  • ~/.bashrc - Bash配置文件
  • ~/.bash_profile - Bash登录配置
  • ~/.profile - 用户环境配置
  • /etc/environment - 系统环境变量
重要区别:
source script.sh    # 在当前Shell执行,变量会保留
bash script.sh      # 在子Shell执行,变量不保留
./script.sh         # 在子Shell执行,需要执行权限
最佳实践
  • 使用source加载配置,使用bash执行独立脚本
  • 在配置文件中使用export确保变量被导出
  • 使用点命令(.)保持POSIX兼容性
  • 使用条件判断检查文件是否存在后再source
  • 避免在配置文件中执行耗时操作
  • 使用函数包装复杂的初始化逻辑
注意事项
  • source会在当前Shell执行所有命令,包括危险命令
  • 配置文件中的语法错误会影响整个Shell会话
  • 避免配置文件之间的循环引用
  • 使用source加载第三方脚本时要谨慎
  • 注意变量作用域,避免意外覆盖
  • 在生产环境中,先测试配置文件再source