如果你是一个经常使用 shell 的用户,几乎肯定会在主目录下有一个 .bash_profile 或 .bashrc 脚本,通常包含各种调整,比如设置环境变量(将某个目录添加到 $PATH)、告诉 shell 做聪明的事情(如 set -o noclobber)以及为命令添加各种别名(如 alias please=sudo)。
(如果你真的很有条理,你会把所有点文件都放在某个仓库中,这样你就可以在所有工作的机器上保持设置同步。)
无论如何,我怀疑很少有人知道 .bash_profile 和 .bashrc 这样的文件实际上什么时候被执行。当我刚开始时,我只是按照别人的建议把东西放在 .bashrc 中,然后当它不工作时,就放到 .bash_profile 中。我可以在这里停下来,只描述 bash 的启动过程(尽管它很愚蠢),但有一个复杂的情况是,我在几年前切换到了 zsh(并且没有回头),但偶尔会在没有安装 zsh 的机器上使用 bash。
Shell 启动脚本的复杂性
为了优雅地处理这种情况,我需要能够在它们自己的文件中指定特定于 bash 或 zsh 的内容,然后在通用启动文件中指定任何符合 POSIX 标准的 shell(如别名和环境变量)都能理解的内容。
我对这个问题的解决方案是定义一些新的点文件文件夹,每个 shell 一个(.bash/、.zsh/ 和 .sh/),还有一个用于 shell 无关文件(.shell/):
12345678910111213141516171819.bash/ env interactive login logout.sh/ env interactive login.shell/ env interactive login logout.zsh/ env interactive login logout
不同类型的 Shell
"But!" 你说,"这些不同的文件在这里做什么?" 啊,我很高兴你问了。有两种类型的 shell:
[非]交互式 shell(你向它们输入 / shell 脚本)
[非]登录 shell(首次登录时运行的 shell / 子 shell)
所有 shell 都会首先运行 env,然后登录 shell 会运行 login,然后交互式 shell 会运行 interactive。完成后,登录 shell 会运行 logout。
在哪里放置内容
这完全取决于它什么时候需要运行。
如果它正在设置 / 修改环境变量,它应该放在 login 中
如果它是别名或终端特定的环境变量(例如,GREP_COLOR),它应该放在 interactive 中
在我的 .shell/env 文件中,我设置了 umask,还定义了一些有用的函数来修改冒号分隔的路径环境变量(如 $PATH)
即使你不采用我方案中的其他任何东西,我也建议你看看我的函数在做什么,这与像 export PATH=$PATH:/path/to/dir 这样的东西不同。
这种特定模式太常见了,如果你考虑 $PATH(或任何你的变量,如 $LD_LIBRARY_PATH)没有设置的情况,它会非常危险。然后,值将是 :/path/to/dir,这通常意味着 /path/to/dir 和当前目录,这通常既是意外行为又是安全问题。
使用我的实现(见 .shell/env_functions),你可以从任何冒号分隔的环境变量中追加、前置和删除目录,当追加或前置时,你保证该目录只会在该变量中出现一次。
Shell 启动流程详解
Bash 启动流程
Bash 的启动流程相对复杂,因为它有多种模式:
登录 shell:当 bash 以 -l 参数启动或作为登录 shell 启动时
交互式 shell:当 bash 以交互模式启动时
非交互式 shell:当 bash 执行脚本时
远程 shell:当 bash 检测到通过 ssh 或 rsh 启动时
Zsh 启动流程
Zsh 的启动流程更加简洁和一致:
全局配置文件:/etc/zshenv、/etc/zprofile、/etc/zshrc、/etc/zlogout
用户配置文件:~/.zshenv、~/.zprofile、~/.zshrc、~/.zlogout
启动文件执行顺序
12345678登录 shell:/etc/zshenv → ~/.zshenv → /etc/zprofile → ~/.zprofile → /etc/zshrc → ~/.zshrc交互式非登录 shell:/etc/zshenv → ~/.zshenv → /etc/zshrc → ~/.zshrc非交互式 shell:/etc/zshenv → ~/.zshenv
实际应用建议
1. 环境变量管理
避免使用 export PATH=$PATH:/new/path 这种模式,因为它可能导致重复和空路径问题。相反,使用更安全的方法:
123456789101112131415# 安全的路径添加函数path_append() { local var_name="$1" local new_path="$2" local current_path="${!var_name}" if [[ -z "$current_path" ]]; then export "$var_name"="$new_path" elif [[ ":$current_path:" != *":$new_path:"* ]]; then export "$var_name"="$current_path:$new_path" fi}# 使用示例path_append PATH "/usr/local/bin"
2. 条件加载
根据 shell 类型和交互性条件性地加载配置:
123456789# 只在交互式 shell 中加载if [[ -o interactive ]]; then source ~/.shell/interactivefi# 只在登录 shell 中加载if [[ -o login ]]; then source ~/.shell/loginfi
3. 跨 Shell 兼容性
为了在不同 shell 之间保持兼容性,使用 POSIX 兼容的语法:
123456789# 使用 POSIX 兼容的语法case "$SHELL" in */bash) source ~/.bash/specific ;; */zsh) source ~/.zsh/specific ;;esac
总结
理解 shell 启动脚本的执行顺序对于正确配置你的环境至关重要。通过采用模块化的方法,你可以:
保持配置的整洁:将不同类型的配置分离到不同的文件中
提高可维护性:每个文件都有明确的职责
增强可移植性:在不同机器和 shell 之间轻松迁移配置
避免常见陷阱:如路径重复、环境变量污染等问题
记住,shell 启动脚本的执行顺序可能因操作系统、shell 版本和编译选项而异。最好的方法是测试你自己的系统,并根据需要调整配置。
参考来源: Shell startup scripts - flowblok's blog