为什么打开终端时~/.bash_profile文件没有被加载?

问题

我有一个Ubuntu 11.04虚拟机,想要设置Java开发环境。我按照以下步骤进行:

  1. sudo apt-get install openjdk-6-jdk
  2. 将以下条目添加到~/.bash_profile文件中:

    export JAVA_HOME=/usr/lib/jvm/java-6-openjdk
    
    export PATH=$PATH:$JAVA_HOME/bin
    
  3. 保存更改并退出

  4. 再次打开终端,输入以下命令:

    echo $JAVA_HOME   (显示为空)
    echo $PATH        (显示,但没有JAVA_HOME的值)
    
  5. 什么都没发生,就好像从未执行过导出JAVA_HOME和将其添加到PATH。

解决方案

我不得不打开 ~/.bashrc 文件,并在文件末尾添加以下条目

#Source bash_profile to set JAVA_HOME and add it to the PATH because for some reason is not being picked up
. ~/.bash_profile

问题

  1. 为什么我必须这样做?我以为在没有bash_profile、bash_login或者profile的情况下,会先执行bashrc。
  2. 在这种情况下,我的终端是一个非登录shell吗?
  3. 如果是的话,为什么在终端之后使用su并输入密码时,它没有执行我在profile中设置的导出命令?

接受的答案提到:“从~/.bashrc中引用~/.bash_profile是错误的解决方案。应该反过来;~/.bash_profile应该引用~/.bashrc。”为什么你的解决方案与此相反? - Cloud Cho
7个回答

~/.bash_profile 只有在以登录模式启动bash时才会被加载。通常情况下,这发生在您通过控制台登录(Ctrl+Alt+F1..F6),通过ssh连接或使用 sudo -isu - 以其他用户身份运行命令时。

当您图形化登录时,~/.profile 将由启动gnome-session的脚本(或您正在使用的任何桌面环境)明确加载。因此,在图形化登录时根本不会加载 ~/.bash_profile

当您打开终端时,终端以(非登录)交互模式启动bash,这意味着它会加载 ~/.bashrc

您应该将这些环境变量放在 ~/.profile 中,下次您登录时效果将显现出来。

~/.bashrc 加载 ~/.bash_profile 是错误的解决方案。应该是反过来的;~/.bash_profile 应该加载 ~/.bashrc

查看DotFiles 以获取更详细的说明,包括一些历史背景,解释为什么是这样的。
(顺便说一下,在使用apt安装openjdk时,软链接应该由软件包进行设置,因此您实际上不需要设置JAVA_HOME或更改PATH

符号链接是由JAVA_HOME软件包设置的,它们通过路径的附加方式来实现。这就是为什么我之前在做什么的原因。我删除了bash_profile文件,并将我的输出添加到.profile文件中。重新启动了我的虚拟机,确实可以执行echo $JAVA-HOME命令。 - Viriato
7我发现在Ubuntu 12中从侧边栏打开终端时,~/.profile文件不会加载。 - jcollum
5@jcollum 这很好。.profile文件应该只在你登录时被使用。 - geirha
2哦,打开终端不等同于登录... 我是指登录到终端。 - jcollum
我发现,将个人添加的内容放在.bash_aliases文件中,在每次打开终端时都会被加载。这样做是否是一个更好的解决方案? - Juan A. Navarro
@JuanA.Navarro 这是因为你的 ~/.bashrc 文件引用了 ~/.bash_aliases。在你需要更改环境变量的几个时候,更新 ~/.profile~/.pam_environment,然后退出并重新登录即可。 - geirha
1我知道,所以我在那里添加了我的附加代码。我想要做的是为终端配置选项(例如提示符),但是即使我在.profile中设置它们,并进行了完整的重启后,这些选项仍然没有被加载。 - Juan A. Navarro
1@JuanA.Navarro 啊,你指的是 PS1PROMPT_COMMAND 等等吗?这些不是环境变量,它们确实应该放在 ~/.bashrc 中,因为它们只对交互式shell有意义。 - geirha
1如果你使用的是(Ubuntu标准的)Gnome终端,你可以在 编辑->配置文件偏好设置 中,切换到 标题和命令 选项卡,然后勾选“作为登录shell运行命令”。这样每次打开终端时,它将会自动读取你的 .bash_profile 或 .profile 文件,就像你预期的那样。 - Lambart
6请记住,如果存在.bash_profile,bash会忽略.profile。请参阅我在这里的回答以及man bash获取更多详细信息。 - terdon
3@terdon,是的,但是当以图形方式登录时,bash不参与其中,所以直接读取.profile文件。 - geirha
@geirha,你知道当你从控制台登录时会发生状态变化的那个结构叫什么(foo-session?),以及它在哪里可以找到,与仅仅启动一个交互式shell相对应?(如果它被称为“login”,那么它在哪里可以找到?)我尝试了man -k login然后man 1 login,看到了/var/run/utmp/login session,但没有找到相应的内核或其他结构。 - n611x007
1这个流程图指出,无论shell是否是交互式的,.bash_profile.bash_login.profile都会在登录shell中被调用。这个流程图是错误的吗?还是应该修改答案? - Antonin Décimo
@AntoninDécimo 谢谢!花了将近9年的时间,有人终于注意到我的错误了。我已经编辑了答案。 - geirha
下次您登录时,效果应该明显。这就是答案!我只需要注销并重新登录,所有已打开的shell在打开之前就已经加载了~/.profile - Gabriel Staples

您可以通过运行以下命令来检查您的Bash shell是否作为登录Shell启动:
shopt login_shell

如果回复是off,那么你没有运行登录shell。
请阅读Bash手册中的调用部分,了解Bash如何(或不如何)读取不同的配置文件。
摘自man bash
当bash作为交互式登录shell被调用时,或者作为非交互式shell使用--login选项调用时,它首先从文件/etc/profile中读取并执行命令(如果该文件存在)。在读取该文件后,它会按照顺序查找~/.bash_profile~/.bash_login~/.profile,并从第一个存在且可读的文件中读取并执行命令。
另一方面,su默认也不会启动登录shell,你需要使用--login选项告诉它这样做。

15非常感谢你提供的 shotp login_shell 命令。太棒了! - Viriato

我认为值得一提的是,您可以通过编辑配置文件首选项,将gnome-terminal的默认设置更改为使用登录shell(即bash -l)。
前往“编辑”->“配置文件首选项”->“标题和命令”选项卡, 勾选“作为登录shell运行命令”的选项。

2启用此设置有哪些不利之处? - chrish
2@chris 你在很多情况下加载了比必要的代码多一点的代码。如果你的~/.bash_profile评估得非常快,这可能并不重要,而且很可能是这种情况。一个好的检查方法是排除通常代价很高的其他进程调用。 - vaab
我勾选了“将命令作为登录 shell 运行”,但现在每次打开终端时,都会出现“-bash:/home/nikhil/.bash_profile: line 1: unexpected EOF while looking for matching和“-bash:/home/nikhil/.bash_profile: line 2: syntax error: unexpected end of file”的错误提示。 - nkhl

如果您打开终端或运行su,则Shell不会作为登录Shell执行,而是作为普通交互式Shell执行。因此,它读取~ /.bashrc而不是~ /.bash_profile 。您可以使用-l选项运行su,使其将您的Shell作为登录Shell运行。
当您使用GUI时,Shell通常不会作为登录Shell运行,因此通常可以将所有内容放在~ /.bashrc中。

1这就是我所做的,而且它起作用了,但是看看底下那个人说的,他建议不要把它放在bashrc里面,而是放在配置文件中。...嘿,两种方法都可以,非常感谢。 - Viriato

TL;DR

在经典的推荐ubuntu设置中,~/.bash_profile只在特定情况下被评估。这是有道理的。

把你的东西放在~/.bashrc中,它会每次都被评估。

好的,我想了解,为什么这是有道理的?

理解正在发生的事情的关键点:

  • Linux上的所有进程都有并使用环境变量
  • 环境变量是继承
  • 因此,在所有进程的父进程上设置它们一次就足够了 (尤其是如果需要一些计算时间)。
  • 所有进程的父进程通常在您登录设备后启动 (提供您的凭据)。
  • 当您登录计算机时,可能有一些您只想做一次的事情(例如检查新邮件...)。

所以"登录"时间通常是:

  • 在控制台模式下,当您登录(使用Ctrl-Alt F1)或通过ssh登录时,由于shell是所有进程的父进程,它将加载您的~/.bash_profile
  • 在图形模式下,当您打开会话时,第一个进程(在经典Ubuntu中为gnome-session)将负责读取.profile文件。

好的,那么我应该把我的东西放在哪里?

这相当复杂,完整故事在这里。但是下面是一个对于Ubuntu用户来说非常常见的简要概述。所以考虑到:

  • 您使用bash shell,
  • 您有一个~/.bash_profile文件,并且遵循推荐~/.bash_profile中添加加载~/.bashrc的指南,以便至少有一个文件会在任何调用机制下都得到评估

这是一个快速建议,告诉您应该把东西放在哪里。

  • ~/.bashrc(在各种场合下都会被评估,只要你遵循建议)

    适用于快速评估环境变量和代码的仅用户仅bash命令行使用(例如别名)。欢迎

    它将自动加载:

    • 在图形会话中新建shell窗口/窗格。
    • 调用bash
    • screen新建窗格或选项卡(不包括tmux!)
    • 在图形控制台客户端(terminator/gnome-terminal...)中的任何bash实例,如果您没有选择 "运行命令作为登录Shell" 选项。

    并且由于之前的建议,它将在所有其他情况下加载。

  • ~/.bash_profile(仅在特定场合下被评估)

    适用于慢速评估环境变量和代码的仅用户控制台会话使用。欢迎 它将在以下情况下加载:

    • 控制台登录(Ctrl-Alt F1)
    • 通过ssh登录到此机器
    • tmux新建窗格或窗口(默认设置),(不包括screen!)
    • 显式调用bash -l
    • 在图形控制台客户端(terminator/gnome-terminal...)中的任何bash实例,只有在选择 "运行命令作为登录Shell" 选项时才加载。
  • ~/.profile(仅在图形会话中被评估)

    适用于仅用户和所有图形会话进程的慢速评估环境变量和无bashism代码。它将在您的图形界面登录时加载。


在某些情况下,当bash加载配置文件时,如果不存在.bash_profile,它将加载.profile - muru
非常感谢清晰的解释,对像我这样的新手有很大帮助。在Mac Mojave上,如果我将变量放在~/.bashrc中并执行source命令,然后使用env命令,我看不到设置的环境变量(我尝试关闭iTerm并重新打开)。但我注意到当我安装Android Studio和其他应用程序时,所有这些环境变量都设置在/.bash_profile中。所以当我添加到/.bash_profile中时,它就像魔法一样起作用。为什么会这样呢? - sofs1
1关于这个问题困扰着我:假设有人将一些化妆品垃圾/有趣的东西放入~/.bashrc(比如screenfetch / figlet等),以便在打开控制台时获得一些输出。然后,如果他们运行一个简单的一行shell脚本来执行某些简单的操作,那么它总是会触发.bashrc。因此,把所有东西都放到.bashrc中似乎是疯狂的。另外,如果你把所有东西都放到.bashrc中,那么.bash_profile还有什么意义呢?我不明白。Linux / bash通常是有道理的,但这一切似乎是一个被醉酒的人编造出来的混乱而自相矛盾的困境。 - YorSubs
具体来说: • 如果我想让代码在图形界面和交互式登录终端中运行,应将该代码放在哪里? • 如果我只想让代码在图形终端中运行,应将该代码放在哪里? • 如果我只想让代码在交互式登录终端中运行,应将该代码放在哪里?答案似乎是"嗯,Bash就是一团混乱难懂的烂摊子,把代码随便放哪里然后试着插入奇怪的连接链以尝试使事情点源其他东西"。真的很奇怪...对吧? - YorSubs
1@YorSubs 你说得没错。要理解的基本事情是,当加载BASH时会执行.bashrc。从那里开始做什么取决于你自己。我同意你的观点,认为不需要使用点源化来加载其他内容,但我相信这种做法是由开发人员试图将生成的代码与他们自定义的代码.bash_profile分离开来而开始的。 - Kellen Stuart

当你执行sudo su时,不会执行shell,而应该尝试使用sudo su -,这将默认加载~/.bash_profile作为源。

他们希望你现在把它放在~/.bash_aliases中。
在~/.bashrc中有这段代码指出了它。
# Alias definitions.
# You may want to put all your additions into a separate file like
# ~/.bash_aliases, instead of adding them here directly.
# See /usr/share/doc/bash-doc/examples in the bash-doc package.

if [ -f ~/.bash_aliases ]; then
    . ~/.bash_aliases
fi


不完全是。这是用于别名,而不是环境变量。 - Pavlo Zhukov