7
22
2020
12

Linux 的环境变量怎么设

本文来自依云's Blog,转载请注明。

最近,Arch Linux 的 pam 包将更新到 1.4.0,然后因为一个字符的变化,不少中文用户都开始避难了:pam_env 将默认不读取用户的环境变量设置。许多中文用户使用~/.pam_environment文件来配置 fcitx 输入法所需要的那三个环境变量。更新之后,这些配置将不再生效,意味着他们可能无法输入中文了,于是大家热烈讨论现在要在哪里配置环境变量比较好。

当然了,在 pam 的配置文件里加上user_readenv=1就可以恢复原先的行为。只是,pam 的开发者这么改当然是有原因的:CVE-2010-4708。简单地说,就是在鉴权模块里设置用户指定的环境变量不安全,怕被提权。

2020年08月20日更新:Arch Linux 打包时添加了以上这个配置

当然啦,桌面用户完全可以去改系统级的环境变量设置,只要你愿意让你个人的配置信息跑到系统配置那边去的话。

正文

用户经常需要设置一些环境变量,比如输入法,比如语言和地区选项,比如自定义的 PATH,等等。然而 Linux 的环境变量又不像 Windows 那样有一个专门设置所谓「系统环境变量」的地方,而是所有环境变量全部由子进程继承父进程获得,这么一路继承下来的。于是,想要给所有想设置上的进程都设置上,就不是一件简单的事情了。

这里关注的是桌面用户,自然首先从图形界面说起。

使用 X11 的桌面环境,通常通过 display manager 来登录,比如 lightdm 和 sddm。这俩都支持 ~/.xprofile。这个文件会在启动过程中被 source,使用的 shell 是由 dm 自己确定的。lightdm 和 sddm 都是用的 /bin/sh(分别位于 /etc/lightdm/Xsession 和 /usr/share/sddm/scripts/Xsession 文件里)。可以看到,除了读取 .xprofile 外,lightdm 也会读取 .profile。sddm 甚至连 bash、zsh、tcsh、fish 的启动配置脚本都给读了。

对于 Wayland,我这儿只有 sddm,类似的,也是各种 shell 的都给你读了。(当然就不读 X11 相关的东西了。)

而对于手动 startx 的用户,~/.xinitrc 里自己写上呗。还有 VNC 啥的,也都可以自己看启动脚本找到方法。

没有图形界面的登录,通常是从 tty 或者 ssh 登录。这两者都是登录到命令行 shell。写在 shell 配置里就可以了。具体到 zsh,我是写到 ~/.zprofile,这样不管是否交互,只要是登录 shell 就执行,不登录的大概已经继承到了。另外有些情况可能写这儿也没有用,比如 cron 调用的时候。非要搞的话其实可以写到 ~/.zshenv,这个基本上所有的 zsh 都会读,只能被系统级设置或者命令行参数禁用掉。而如果是 bash 用户,.bash_profile .bash_login .profile 这仨,会读找到的第一个(sddm 也会按这个顺序找,但是 lightdm 只会找 .profile)。Arch Linux 现在默认会给新用户建立 .bash_profile,我建议 bash 用户把它改名成 .profile。这里有个图 ,大致上说明了 bash 和 zsh 不同情况下会读取的配置文件

systemd 用户实例也有一份环境变量,用于通过 systemd 启动的用户级服务,通过 systemctl --user 的子命令可以查看和更新。D-Bus 也有一份,可以通过 dbus-update-activation-environment 命令来更新。这俩通常不需要用户操心,桌面环境应该会维护好。

如果你用 tmux 的话,那么 tmux 还有一份,用于新创建的窗格。tmux 会在被连接时更新 update-environment 选项指定的环境变量。当然你也可以用 tmux setenv 子命令去更新。这个尤其值得注意,因为 tmux 可能在不同的环境中被连接,导致环境变量混乱(比如 X11 下没有 DISPLAY 变量)。

要注意的一点是,不同的登录过程并不是只会读取自己专属的配置文件,尽管大家都在努力,也并没有完全统一好用的配置文件。所以有些环境变量,你可能想尽量避免重复设置(比如冗长重复的 PATH 项就很讨厌)。好在这些都是 shell 脚本,不光能设置环境变量,也能做条件判断。

要查看当前进程的环境变量,可以用 env 命令。要查看别的进程的,去读它的 /proc/PID/environ 文件,或者开个 htop 然后在进程上按 e 键。要看看某个环境变量在不同进程里是什么情况,可以用我写的小工具 compare-env

Linux 软件生态的特点就是这样,没有谁规定一定要用怎样的方式做一件事情,所以大家的想法总会有些出入,就导致设置个环境变量还有这么一大群配置文件(systemd 还有一份呢,我还没读没试所以没说)。但另一方面,理解它们也是十分容易的:不用猜测,不用撞大运般地耗费时间去尝试,顺着代码摸过去,总能弄明白软件的行事逻辑。即使文档像 pam_env 那样前后矛盾,也有源代码这条路可以精确刻画软件的行为。

Category: Linux | Tags: linux 环境变量 | Read Count: 135941
櫻川 浅羽 说:
Jul 24, 2020 02:09:24 AM

支持方:即使沒有文檔,也有原始碼可以看!
反對方:即使沒有原始碼,也有文檔可以看!

laggard 说:
Jul 24, 2020 04:24:50 PM

user_readenv=1 配置要写到哪里?

Avatar_small
依云 说:
Jul 24, 2020 08:15:33 PM

/etc/pam.d/ 里 pam_env 的配置那里。man pam_env 有写的。

laggard 说:
Jul 27, 2020 12:46:43 PM

/etc/pam.d 下很多文件都用到了 pam_env.so,这么说要在所有这些位置后边都加 user_readenv=1 ?

lucidlynx 说:
Aug 12, 2020 10:39:17 PM

Windows 的环境变量也是子进程继承父进程。不是集中存的哦。

CreateProcess(Ex) 函数可以为新进程设置新的环境变量块(类似于 execve)
SetEnvironmentVariable/GetEnvironmentVariable 用于修改/获取当前进程的环境变量块(类似于 setenv/getenv)

Avatar_small
依云 说:
Aug 12, 2020 10:48:41 PM

诶?那我在那个设置 %path% 环境变量的地方更新之后,为什么好像所有进程立即收到更新了呢?

Avatar_small
依云 说:
Aug 12, 2020 10:51:24 PM

哦我查了一下,原来是设置系统环境变量的时候会有广播通知。

lucidlynx 说:
Aug 13, 2020 02:35:34 PM

是这样的。最简单的测试方法是在 cmd.exe 里使用 set name=value,这类似于 bash 的 export name=value。然后只影响子进程,可以再启动一次 cmd.exe 在里面运行 set 命令查看环境变量块,但是如果我从开始菜单里启动一个 cmd.exe 却不会看到新的环境变量。

修改系统变量时,会向所有窗口发送 WM_SETTINGCHANGE 消息,程序再更新自己的环境变量块。但没有窗口的程序就没法收到了 233。

xtricman 说:
Aug 28, 2020 02:22:40 PM

linux的登录行为太复杂了,systemd-logind负责管理session但是不负责鉴权,登录鉴权不是钦定的,而是sshd、display manager各用各的,通常他们会统一调用pam。现在pam不读用户配置的话,那需要session leader自己做…那鬼知道这个session leader是个啥。

花生 说:
Apr 01, 2022 03:30:25 PM

你好 请教一个问题,我想要一个root进程启动一个普通用户权限的gui应用(通过runuser),通过什么方式可以获得并设置和从桌面上启动的gui应用一样的环境变量?

Avatar_small
依云 说:
Apr 02, 2022 11:44:59 AM

不知道你的环境变量是哪里设置的。不过用户已经登录(或者 enable-ligner 了的话)你可以设置好 DBUS_SESSION_BUS_ADDRESS 环境变量然后调用 systemd-run --user 来让 systemd 帮你启动东西。


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter

| Theme: Aeros 2.0 by TheBuckmaker.com