什么是 .bashrc:作用、加载时机与配置最佳实践

你在终端里定义了一个别名,用得正顺手,可一打开新标签页它就消失了。在提示符下输入的 Shell 设置只在该 Shell 存在期间有效。要让它们永久生效,就必须写进一个 Bash 每次启动新会话时都会读取的文件。在大多数 Linux 系统上,这个文件就是 ~/.bashrc。

本指南将介绍 .bashrc 的位置、Bash 何时读取它、哪些内容应当写进其中,以及如何在不打开新终端的情况下应用你的改动。

.bashrc 的作用

.bashrc 是一个按用户区分的 Bash 启动脚本,并非通用的 Linux 配置文件。当 Bash 加载它时,每条命令都在当前 Shell 中运行。这正是别名、函数、提示符设置与 Shell 选项无需单独的导入步骤就能成为会话一部分的原因。其他 Shell 使用各自的文件,例如 zsh 使用 ~/.zshrc。

"rc" 这个后缀源自 RUNCOM——一个早期的 CTSS 程序,它会执行存放在文件中的命令。这个名字保留在了 .vimrc、.screenrc、.inputrc 等 Unix 配置文件中。

.bashrc 文件的位置

.bashrc 位于你的主目录中。开头的圆点使它成为隐藏文件,因此普通的 ls 不会显示它:

ls -la ~/.bashrc
-rw-r--r-- 1 dejan dejan 3771 Aug 14 09:12 /home/dejan/.bashrc

每个用户账户都有自己的副本,编辑你自己的那份不会影响系统上的其他任何人。该文件是纯文本,因此任何编辑器都可以使用:

nano ~/.bashrc

如果该文件不存在,并不表示系统出问题。Bash 只是简单地跳过它。大多数发行版都在 /etc/skel/ 中附带一个模板,并在创建每个新主目录时将其复制过去,因此你可以用以下命令恢复默认版本:

cp /etc/skel/.bashrc ~/.bashrc

你也可以从零创建该文件,以一个空文件开始。

Bash 何时读取 .bashrc

当 Bash 启动一个非登录的交互式 Shell 时,它会读取 ~/.bashrc。用日常的话来说,这意味着在你的桌面环境中打开一个新的终端窗口或标签页,或在已经打开的 Shell 中输入 bash。

有两种常见情况不符合上述描述。通过 SSH 或控制台登录会启动一个登录 Shell。Bash 会先读取 /etc/profile,然后依次查找 ~/.bash_profile、~/.bash_login 和 ~/.profile 中第一个可读的文件。运行本地脚本会启动一个非交互式 Shell,通常它不会读取上述任何文件。如果设置了 BASH_ENV,Bash 会在运行脚本前读取该变量所指向的文件。

由于大多数人都希望在登录 Shell 与非登录 Shell 中拥有一致的交互式设置,通常的做法是把这些设置放在 ~/.bashrc 中,并让登录文件去 source 它。关于完整的启动顺序以及这样设计的缘由,请参阅《.bashrc 与 .bash_profile 的区别》。

文件顶部的交互性守卫

打开 Debian 或 Ubuntu 上默认的 ~/.bashrc,第一行真正的代码是这一行:

# If not running interactively, don't do anything

case $- in

    *i*) ;;

      *) return;;

esac

变量 $- 保存着当前 Shell 的选项标志,其中只有在交互式 Shell 中才会包含 i。如果缺少 i 标志,return 会立即停止读取该文件。

这个守卫之所以存在,是因为 Bash 有一个特定的行为:当 Bash 检测到标准输入连接到了网络连接(例如 ssh user@host 'command' 的情况)时,即使 Shell 不是交互式的,它也会读取 ~/.bashrc。如果没有这个守卫,文件后面的所有内容都可能在服务器上启动 Bash 的远程命令和文件传输过程中被执行。

请将这个守卫保留在原处,并把交互式设置添加在它之后。需要由非交互式远程命令使用的设置属于例外,但放在守卫之上的任何内容都必须保持静默(不输出)。

应该把什么放进 .bashrc

任何会影响交互式 Shell 行为的设置都适合放在这里。以下是最常见的一些添加项。

别名(Aliases)

别名(alias)是较长命令的简短名称:

alias ll='ls -alF'

alias gs='git status'

alias ..='cd ..'

函数(Functions)

当一个快捷方式需要接受参数或包含多条命令时,应当使用函数而非别名:

mkcd () {

  mkdir -p "$1" && cd "$1"

}

运行 mkcd projects/api 现在会创建该目录并进入其中。

交互式 PATH 追加

要让 Bash 在交互式提示符下能找到你自己目录中的可执行文件,就把它加入 $PATH:

export PATH="$HOME/.local/bin:$PATH"

把 $PATH 放在末尾会保留已有的目录。把你的目录放在前面,则意味着你自己的同名命令版本会优先于系统版本。

这个改动只对读取 .bashrc 的 Shell 生效。如果该路径还需要对 GUI 程序或其他 Shell 可见,则应在 ~/.profile 或 ~/.bash_profile 中设置。

环境变量

从交互式 Bash 启动的程序所需的那些环境变量可以放在这里。用 export 导出它们,以便子进程能够继承这些值:

export EDITOR=nano

export LESS='-R'

对于那些必须在整个登录会话(包括并非从终端启动的应用程序)中都存在的变量,应使用登录文件或桌面环境文件来设置。

不要把 API 密钥、令牌或密码放进 .bashrc。这个文件常常会被纳入 dotfiles 仓库。请将机密保存在排除于版本控制之外、并使用 chmod 600 保护的独立文件中,或者使用专门的机密管理器。

Shell 提示符

PS1 定义了提示符字符串。以下示例用颜色显示了用户名、主机名和当前目录:

PS1='\[\e[32m\]\u@\h\[\e[0m\]:\[\e[34m\]\w\[\e[0m\]\$ '

\[ 和 \] 这两个标记告诉 Bash,其中包含的转义序列不占用屏幕宽度。如果省略它们,长命令行会发生错误的换行。

Shell 选项

shopt 用于开关那些默认关闭的 Bash 行为:

shopt -s autocd        # type a directory name to cd into it

shopt -s cdspell       # fix minor typos in cd arguments

shopt -s globstar      # ** matches files across subdirectories

shopt -s histappend    # append to the history file instead of overwriting

像 HISTSIZE 和 HISTCONTROL 这样的历史记录设置也存放在这个文件中。关于如何操作历史条目本身,请参阅 history 命令指南。

不重启终端即可应用更改

Bash 在启动时会读取 ~/.bashrc,因此编辑并不会对已经在运行的 Shell 生效。在加载编辑后的文件之前,先检查其语法:

bash -n ~/.bashrc

没有输出表示 Bash 没有发现语法错误。随后你可以用 source 命令重新加载该文件,而无需关闭终端:

source ~/.bashrc

点命令(.)是 source 的 POSIX 写法,在 Bash 中作用相同:

. ~/.bashrc

这两条命令都会在当前 Shell 中执行该文件,因此别名、函数和变量会立即可用。source 不会撤销任何已有定义:如果你从文件中删除了某个别名,它在当前会话中仍然保持定义,直到你运行 unalias 或打开新的终端。

把 .bashrc 拆分成独立文件

当一个 .bashrc 增长到几百行以上时,拆分成多个片段管理会更轻松。Debian 和 Ubuntu 已经对别名采用了这种模式,其默认文件包含:

if [ -f ~/.bash_aliases ]; then

    . ~/.bash_aliases

fi

如果要进行更通用的拆分,可以为这些额外文件创建一个目录:

mkdir -p ~/.bashrc.d

然后在 ~/.bashrc 中添加一个循环,逐个 source 该目录中每个可读的 Shell 文件:

if [ -d ~/.bashrc.d ]; then

    for rc in ~/.bashrc.d/*.sh; do

        [ -r "$rc" ] && . "$rc"

    done

    unset rc

fi

文件按字母顺序被 source,因此 10-path.sh 会先于 20-aliases.sh 执行。这可以把工作相关的设置集中在一个文件里,你可以直接放入或移除它而无需修改 .bashrc 本身。bash -n ~/.bashrc 检查并不会读取被 source 的文件,因此编辑后请分别检查每一个。

Fedora 和 RHEL 在其默认配置中已经会 source ~/.bashrc.d/ 中的文件,因此在添加另一个循环之前,请先检查你现有的文件。

不该放进 .bashrc 的内容

应当避免的主要是那些会输出内容的命令。在交互性守卫之上放置一条 neofetch 调用或 echo "Welcome back" 在终端里看起来无害,但它也会在 scp、rsync 和 ssh host 'command' 会话期间运行。多余的文本会破坏协议流,导致这些工具失败。请把输出内容放在守卫之下,或将登录横幅移到登录配置文件中。

运行时间较长的命令也值得避免。.bashrc 中的一切都会在你拿到提示符之前运行,因此一次耗时半秒的网络调用或版本管理器初始化,会给每个你打开的终端都增加这份延迟。

速查参考

如需可打印的速查参考,请参阅 Bash 速查表。

设置项示例作用
别名alias ll='ls -alF'为较长命令起的简短名称
函数mkcd () { mkdir -p "$1" && cd "$1"; }可接受参数的快捷方式
交互式 PATHexport PATH="$HOME/.local/bin:$PATH"向交互式 Bash 添加可执行目录
环境变量export EDITOR=nano由 Bash 启动的程序所继承的设置
提示符PS1='\u@\h:\w\$ 'Shell 提示符的格式
Shell 选项shopt -s autocd开关某项 Bash 行为
加载文件. ~/.bash_aliases从另一个文件加载设置
语法检查bash -n ~/.bashrc在不执行文件的情况下检查它
重新加载source ~/.bashrc将改动应用到当前 Shell

故障排查

改动未生效:Bash 只在 Shell 启动时读取 ~/.bashrc。请运行 bash -n ~/.bashrc,然后在当前终端中 source ~/.bashrc,或打开一个新的终端。

设置在新的终端中生效,但通过 SSH 却不行:ssh user@host 会打开一个登录 Shell,它读取的是 ~/.bash_profile、~/.bash_login 和 ~/.profile 中第一个可用的文件,而非 ~/.bashrc。Debian 和 Ubuntu 自带的 ~/.profile 已经会 source ~/.bashrc,因此默认配置可以正常工作。在其他系统上,或在替换了登录文件之后,请在其中加入 . ~/.bashrc,使两种 Shell 都能加载相同的交互式配置。

编辑 .bashrc 后 scp 或 rsync 失败:文件中可能有某些内容在远程会话期间输出了内容。请把任何 echo、neofetch 或横幅命令移到交互性守卫之下,然后用 ssh user@host 'true' 测试,它应当完全不输出任何内容。

每个新终端都报告语法错误:Bash 会给出出错的行号。请用 bash --norc 打开一个跳过该文件的 Shell,修正该行,然后在重新加载前运行 bash -n ~/.bashrc。常见原因是缺少 fi 或引号未闭合。

文件被删除或清空:用 cp /etc/skel/.bashrc ~/.bashrc 把发行版默认文件复制回来并重新加载。

某个命令在一个终端中可用,在另一个中却不行:这两个 Shell 可能读取了不同的启动文件,或者 $PATH 的追加内容位于早期的 return 之后,或位于某个不匹配的条件块之内。请在两个 Shell 中分别运行 echo "$PATH" 进行比较。

常见问题(FAQ)

执行脚本时 .bashrc 会运行吗?通常,不会。本地脚本运行在非交互式 Shell 中,除非脚本自己 source 它或 BASH_ENV 指向它,否则不会读取 ~/.bashrc。当远程 Shell 守护进程启动非交互式命令时,Bash 也可能读取 .bashrc,但交互性守卫通常会立即阻止该文件继续执行。

我用的是 zsh。这些设置该放在哪里?请使用 ~/.zshrc,它对 zsh 起相同的作用。别名、函数和 PATH 改动原样适用,但提示符转义序列和部分 shopt 选项则不适用。

应该把 .bashrc 纳入版本控制吗?应该,只要其中不含机密。一个 dotfiles 仓库能让你轻松在新机器上完成配置。请把令牌和密码保存在 .gitignore 中列出、并由 .bashrc 来 source 的独立文件中。

为什么我的 .bashrc 在 macOS 上没有被读取?Terminal 和 iTerm2 通常会打开一个登录 Shell。如果你把它们配置为使用 Bash,Bash 会读取第一个可用的登录文件,而非 ~/.bashrc。当前的 macOS 版本默认使用 zsh,其交互式配置应放在 ~/.zshrc 中。

总结

请把 ~/.bashrc 当作每次打开交互式 Bash Shell 时都会运行的代码来对待。让它保持轻快、不要把机密放进去,并且在加载改动之前,用 bash -n ~/.bashrc 检查每一次编辑。