Linux 环境变量指北

作为Linux用户一定遇到过这样的场景:在终端里配置好的环境变量,一打开图形界面就“神秘失踪”;或者在KDE下正常的程序,换到GNOME却死活读不到JAVA_HOME。这背后的原因,往往与环境变量的加载机制在不同场景下的差异有关。

Linux 系统启动流程

一、终端:Login Shell

Unix shell 是为 Unix 和类 Unix 系统提供传统用户界面的命令行解释器或 shell,通过执行用户输入的命令文本,或包含命令的文本脚本来指导计算机的运行。

兼容 Bourne Shell 的 Shell 有:Bash、Zsh 等。其他类型的 Shell 有:Fish、PowerShell、nushell 等。

环境变量提供了一种在多个程序和进程间共享配置的简单方式。 在 Windows 中,通过 GUI 的方式设置全局环境变量非常方便。 而在 Linux 中,通过 Login Shell 配置环境变量是一种常见的做法,虽然原始,但是功能更加强大。

在 linux 下,图形界面启动的终端通常是 Non-Login Shell(不通过 bash --login 启动),只会读取~/.bashrc(交互式配置),而不会触发对应Login Shell的初始化流程。 因此,了解 Login Shell 对于设置环境变量非常重要。

当你通过SSH登录服务器,或是在物理机上按下Ctrl+Alt+F5进入文本终端时,系统会启动一个 Login Shell

Login Shell 是一种调用模式,在这种模式下,Shell 读取一次性初始化文件。 例如兼容 Bourne Shell 的 Shell 会读取系统范围的 /etc/profile、用户的 ~/.profile(sh)/.bash_profile(bash)/.zprofile(zsh) 或其他 dotfiles。 在登录时, /etc/profile 会读取 /etc/profile.d/ 中任何可读的 *.sh 文件:这些脚本不需要 shebang,也不需要具有可执行权限。它们用于设置环境并定义特定于应用程序的设置。

这些文件(dotfiles)将会设置初始环境,且会被所有从 Shell 启动的进程继承。 因此,其应当只在会话开始(登陆)时读取一次。 例如,在控制台或通过 SSH 登录、使用 --login 参数通过 sudo 或 su 切换用户、手动调用 Login Shell(例如,通过 bash --login)。

不同的发行版使用的 /bin/sh 可能不同,例如 Debian 系默认使用 dash,而 Arch 系默认使用 bash。 在 ~/.bash_profile~/.zprofile 中引用 ~/.profile,并统一在 ~/.profile 中设置环境变量,可以保证在不同的情况(greetd、uwsm 只使用 sh)下都能生效。

Zsh 和 Bash 有一定的相似性,主要区别在于 Bash 将 Login shell 的启动文件与交互式(interactive)shell 分开。 正如 Debian wiki 页面所解释的那样: > 那么,为什么 .bashrc 是独立于 .bash_profile 的文件呢? 这样做主要是由于历史原因,当时的机器与今天的工作站相比非常慢。 处理 .profile.bash_profile 中的命令可能需要相当长的时间,尤其是在大量工作必须由外部命令完成的机器上。 因此,困难的初始设置命令(创建可以传递给子进程的环境变量)被置于.bash_profile。 未继承的临时设置和别名被放在 .bashrc 中,以便每个 subshell 都可以重新读取它们。

虽然说通常情况下交互式的(Interactive) Login Shell(bash -li)不怎么会被用到,但是在一些特殊情况下,比如通过 SSH 登录服务器,或者在 tty 中切换用户,或者是使用 macOS 系统,这种情况下就会用到 Interactive Login Shell。

标准的解决方法是始终.bash_profile 文件末尾包含类似于以下命令的内容:

[[ -f "${HOME}/.profile" ]] && . "${HOME}/.profile"
[[ -f "${HOME}/.bashrc" ]] && . "${HOME}/.bashrc"

zsh 下,无论是否是 Login Shell,都会加载交互式配置(~/.zshrc)。

Zsh 因其强大的功能和友好的交互体验,逐渐成为主流Shell,目前 Apple 将其作为 macOS 的默认Shell。 在 archlinux 中,/etc/zsh/zprofile 包含了 emulate sh -c 'source /etc/profile',用于设置$PATH及其他环境变量,以及应用程序相关的设置(/etc/profile.d/*.sh)。 因此使用zsh作为Login Shell也会加载/etc/profile

对于 macOS,约定是将每个新终端作为 Interactive Login Shell 启动。 macOS GUI 在登录时不运行 .profile,这显然是因为它有自己的加载全局设置的方法。 但这意味着 macOS 上的终端模拟器确实需要运行 .profile(通过告诉它启动的 shell 它是一个登录 shell),否则最终会得到一个不能使用的 shell。

不同shell的配置加载顺序(精简)详见1 2

通常情况下:

  • 终端相关配置(如别名、提示符)放用户配置,如~/.bashrc; .zshrc
  • 需要全局生效的变量(如JAVA_HOME)放~/.bash_profile; .zprofile 或者统一放在 ~/.profile
  • ~/.profile 必须与 /bin/sh 兼容,bashism 语法不支持。
  • 如果在 ~/.profile 中设置环境变量,则 ~/.bash_profile 只需加载 .profile 和 .bashrc。
  • 同时拥有 ~/.profile 和 ~/.bash_profile 没有什么意义。 如果缺少后者,bash 将很乐意使用前者,并且任何特定于 bash 的行都可以通过检查 $BASH 或 $BASH_VERSION 来保护。
  • 在图形桌面环境中启动终端,默认是非login终端,它将仅读取用户的非Login启动脚本。

二、Systemd 的配置方式

随着 Systemd 的普及,一套新的变量加载机制开始取代之前的机制。 具体来说,Systemd 取代了initd,成为系统的第一个进程(PID 等于 1),其他进程都是它的子进程。 Systemd 启动流程抛弃了 shell 脚本,它将系统对象及其依赖项统一为 systemd 单元。 当你登录时,幕后发生了两件关键事情:

  1. PAM(认证模块)

优先读取/etc/environment/etc/security/pam_env.conf~/.pam_environment 已被弃用)。

/etc/environment 通过定义KEY=VAL配置,适用于所有用户和会话

# /etc/environment
LANG=en_US.UTF-8
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"

/etc/security/pam_env.conf 同时也支持 VARIABLE [DEFAULT=value] [OVERRIDE=value] 的写法

# /etc/security/pam_env.conf

LANG              DEFAULT=en_US.UTF-8
# 特殊变量 @{HOME}, @{SHELL},默认值为当前用户的家目录和shell
# 和 HOME,SHELL 的值无关,默认情况下不会设置它们。
XDG_CONFIG_HOME DEFAULT=@{HOME}/.config
# 可以使用 `${VARIABLE}` 在其他变量的值中扩展已定义的变量
_JAVA_OPTIONS DEFAULT=-Djava.util.prefs.userRoot=${XDG_CONFIG_HOME}/java
# 虽然支持 KEY=VAL 的写法,但这种写法不支持变量扩展
PATH="/home/username/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"
# 更推荐
PATH DEFAULT=@{HOME}/bin:${PATH}
  1. systemd 环境变量environment.d(5)

虽然这里的也是通过 KEY=VAL 的形式定义环境变量,但是支持了变量扩展:

PATH=/opt/foo/bin${PATH:+:$PATH}
LD_LIBRARY_PATH=/opt/foo/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}
XDG_DATA_DIRS=/opt/foo/share:${XDG_DATA_DIRS:-/usr/local/share/:/usr/share/}
  • ~/.config/environment.d/*.conf – 用户级
  • /etc/environment.d/*.conf
  • /run/environment.d/*.conf
  • /usr/lib/environment.d/*.conf
  • /etc/environment – 向后兼容

一些桌面环境通过 SystemD 用户服务启动程序,这代表着需要通过 environment.d,或者其他特定于桌面环境的方式(KDE)(GNOME)设置环境变量。 (注:通常情况下 Display Manager 设置的环境变量会导入 session)

三、图形界面

DM(Display Manager) 显示管理器

显示管理器通常是一个图形用户界面程序,它在系统启动完成后加载,取代了传统的命令行登录方式。

其负责如下功能:

显示登录窗口(用户名/密码输入界面)
选择桌面环境/窗口管理器(会话类型)
启动Xorg/Wayland显示服务器

Shell 的启动文件(/etc/profile等)是否有效取决于DM。 不同 DM 处理 Login Shell 配置的方式有所不同:

  • SDDM(KDE 默认DM)/LightDM: 在 Wayland 和 x11 下都会完整执行 Login Shell 流程并继承环境变量,变量设置相对更加灵活。 但是目前不支持 environment.d

  • GDM(GNOME 默认DM): 在 X11 会话中,GDM 使用 Login Shell 加载配置文件,同时运行 /etc/X11/xinit/xinitrc 或类似的初始化脚本。
    而在 Wayland 会话中,GDM 不使用 Login Shell,环境变量通过 ~/.config/environment.d//usr/share/gdm/env.d/ 设置。 详见

  • greetd: 默认行为加载 /etc/profile 和 ~/.profile(通过 source_profile 选项控制)。

Xorg Session

在 Xorg 环境下,可以通过 xprofile 来加载环境变量。 xprofile(~/.xprofile/etc/xprofile) 在 Xorg 会话初始化阶段(窗口管理器启动前)自动执行命令。xprofile 被以下文件加载:

  • GDM - /etc/gdm/Xsession
  • LightDM - /etc/lightdm/Xsession
  • LXDM - /etc/lxdm/Xsession
  • SDDM - /usr/share/sddm/scripts/Xsession

Wayland Session

由于 Wayland 不启动任何 Xorg 相关文件,因此 GDM 和 KDE Plasma* 会导入 systemd 用户环境变量(~/.config/environment.d)。

虽然 SDDM/LightDM 不支持 environment.d,但是可以通过在登陆脚本中导入环境变量实现类似行为:

# use systemd-environment-d-generator(8) to generate environment, and export those variables
set -o allexport
source <(/usr/lib/systemd/user-environment-generators/30-systemd-environment-d-generator)
set +o allexport

DE(Desktop Environment) 桌面环境

KDE Plasma

KDE Plasma 可以通过 ~/.config/plasma-workspace/env/ 中的脚本设置环境变量,其通过调用 /bin/sh 实现环境变量的导出。 因此, 要确保语法符合 (/bin/sh),比如 debain 系的 /bin/shdash,不支持 source 命令。

如果使用的是像 GDM 这种在 Wayland 下不加载 Login Shell 配置的 DM,但是又想在 KDE Plasma 中能够像 SDDM 一样加载环境变量, 可以通过手动加载 profile 文件来实现类似的功能。

mkdir -p ~/.config/plasma-workspace/env
touch ~/.config/plasma-workspace/env/profile.sh
# profile.sh
DM_PATH=$(grep -oP '(?<=^ExecStart=).*' /etc/systemd/system/display-manager.service 2>/dev/null) if [ "$DM_PATH" = "/usr/bin/gdm" ]; then
. "/etc/profile"
. "$HOME/.profile"
fi

GNOME

在 Wayland 下,由于 GDM 不加载 Login Shell,因此 GNOME 会话有且仅有会在 Wayland 中使用 Login Shell 重新加载环境变量。

具体来讲,GNOME 通过 gnome-session 启动,gnome-session 实际上是一个 shell 脚本,它会在执行 gnome-session-binary 之前检查自己的第一个参数是否是 -l(Login)*。如果不是,则重新启动当前 shell 会话为登录 shell。

Linux 环境变量指北的更多相关文章

  1. Linux 环境变量_006

    ***Linux 环境变量指系统运行程序或命令的能快速找到其位置等其它功能,不用输入复杂命令.以$PATH环境变量为例子, $PATH决定了shell指定寻找命令或程序的路径,比较执行ls命令,如果没 ...

  2. Linux学习笔记之Linux环境变量总结

    0x00 概述 Linux是一个多用户多任务的操作系统,可以在Linux中为不同的用户设置不同的运行环境,具体做法是设置不同用户的环境变量. 0x01 Linux环境变量分类 按照生命周期来分,Lin ...

  3. Linux 环境变量加强

    Linux 环境变量加强 # 前言 今天,主要是之前搭建 GO 环境包的使用发现自己对 Linux 环境变量还不是很熟悉. 遇到环境变量的问题还是会有些懵逼.所以,今天写点Linux 环境变量的文章, ...

  4. Linux环境变量及其设置

    简介 环境变量是在操作系统中一个具有特定名字的对象,它包含了一个或多个应用程序将使用到的信息.Linux是一个多用户的操作系统,每个用户登录系统时都会有一个专用的运行环境,通常情况下每个用户的默认的环 ...

  5. Linux环境变量总结 转

    转自https://www.jianshu.com/p/ac2bc0ad3d74 Linux是一个多用户多任务的操作系统,可以在Linux中为不同的用户设置不同的运行环境,具体做法是设置不同用户的环境 ...

  6. 三种配置linux环境变量的方法(以java为例)

    1.先确认是否为openjdk:参考 2. 修改/etc/profile文件  如果你的计算机仅仅作为开发使用时推荐使用这种方法,因为所有用户的shell都有权使用这些环境变量,可能会给系统带来安全性 ...

  7. linux环境变量LD_LIBRARY_PATH

    LIBRARY_PATH和LD_LIBRARY_PATH是Linux下的两个环境变量,二者的含义和作用分别如下: LIBRARY_PATH环境变量用于在程序编译期间查找动态链接库时指定查找共享库的路径 ...

  8. Linux 环境变量和source命令 (转)

    可能是班门弄斧了,仅share给尚不知道的童鞋. 1.       问题的来源: 为什么我们编译Android代码时,需要输入:  source ./build/envsetup.sh  或者 . . ...

  9. Ubuntu Linux 环境变量

    2011年09月17日 Ubuntu 下设置adb环境变量 分类: 同Windows一样,Ubuntu Linux系统包含两类环境变量:系统环境变量和用户环境变量.系统环境变量对所有系统用户都有效,用 ...

  10. 设置Linux环境变量的方法与区别(Ubuntu)

      设置 Linux 环境变量可以通过 export 实现,也可以通过修改几个文件来实现,有必要弄清楚这两种方法以及这几个文件的区别.   通过文件设置 Linux 环境变量 首先是设置全局环境变量, ...

随机推荐

  1. R数据分析:国产新冠口服药比辉瑞好的文章的统计做法分享

    元旦前在人民日报中央厨房上看到一篇文章,叫做"比肩辉瑞的国产新冠药物VV116,是这样研制和临床试验的",想来就把文献原文找来读了读,写下本文分享给大家,本文主要关注文章的正文中主 ...

  2. P6474 [NOI Online #2 入门组] 荆轲刺秦王 题解

    荆轲将会臭名昭著 首先 $15$ 做法很简单,那就是直接 `cout<<-1` 考虑用 BFS 来解思路很简单,但是怎么求每个士兵的控制范围呢? 直接暴力时间复杂度是 $O(nma^2)$ ...

  3. GraphQL Part V: 字段,参数和变量

    字段 我们对字段已经有了好的起点,我们在 HelloWorldQuery 中有两个字段:hello 和 world.他们都是单值字段. 现在我们可以扩展应用来支持复杂类型.例如,我们想象一下,我们在创 ...

  4. 关于在VMware中安装的CentOS7系统中无法安装ntp

    一.问题引言 今天在虚拟机中新安装了CentOS7,在使用yum命令时,出现如下如错误: 2.点击图片中链接,即是"2"中的红框,发现该链接竟不可达 3.于是开始找度娘,但并没有发 ...

  5. Qt/C++加载不同的地图控件/地图类型/缩放标尺/缩略图/比例尺/实时路况/全景视图等

    一.前言说明 在展示地图的时候,有些常规的操作,比如调整地图的缩放级别,切换到卫星图等,希望能够在地图上直接操作实现,于是就有了一堆地图控件,可以根据自己的需求动态的添加和删除,这样就更直接更快捷,而 ...

  6. Qt开源作品25-电池电量控件

    一.前言 现在这个时代,智能手机不要太流行,满大街都是,甚至连爷爷奶奶级别的人都会用智能手机,本次要写的控件就是智能手机中的电池电量表示控件,采用纯painter绘制,其实也可以采用贴图,我估计大部分 ...

  7. 使用学生优惠创建 Azure Database for MySQL 数据库

    前言 在此之前,你需要拥有一个已通过学生认证的 Azure 账户.关于通过 Azure 学生认证,网上已有大量教程,此处不再赘述. 前些日子认证通过了 Azure 的学生认证,在部署此网站时发现 Az ...

  8. Index - 此处的诗

    虚构往事 正篇   嗯--本来发过两篇,但深愧于仓促的处理和并未完善的细节设定,隐藏了.   大概会是一个中篇的科幻故事,世界设定已经完善了(Shaya 可以作证!),但近期可能没有精力动笔. 番外 ...

  9. 引发类型为“System.Windows.Forms.AxHost+InvalidActiveXStateException”的异常 解决办法

    出现题目的异常,多是引用第三方控件引起的. 在NEW时,需要初始化该对象. AxESACTIVEXLib.AxESActiveX ax = new AxESACTIVEXLib.AxESActiveX ...

  10. uwp IProgress<T>进度通知。

    主要是利用 Pp_ProgressChanged 报告进度: private void BtnDownload_Click(object sender, RoutedEventArgs e) { va ...