关于环境变量的不完整总结
这篇文章最后更新的时间在六个月之前,文章所叙述的内容可能已经失效,请谨慎参考!
环境变量是什么
环境变量(environment variables)
- 现代操作系统都有环境变量
- 环境变量是键值对
- 环境变量的 键 值 都是字符串
- 程序可以通过系统 API 访问环境变量
- 环境变量 最早出现在 Version 7 Unix ,然后后续的操作系统都继承了这样的设计
- 环境变量是进程运行环境的一部分
- 例如,正在运行的进程可以查询 TEMP 环境变量的值以发现存储临时文件的合适位置,
- 或者查询 HOME 或 USERPROFILE 变量的值以查找运行该进程的用户所拥有的目录结构。
- 每个进程都有自己单独的环境变量
- 环境变量可以修改,但一般只能在当前进程或子进程生效
- 环境变量由父进程设置
- 默认情况下会复制父进程的环境变量
- 父进程也可以单独地设置子进程的环境变量
- 环境变量的作用
- 让进程知道当前的运行环境
- 把数据传递给子进程
- 存储临时数据
- 环境变量的作用域
- 当前进程
- 当前终端的环境变量实质上就是当前 shell 的环境变量,也是进程的作用域
- 当前用户
- 全局
- 当前进程
- 常见的环境变量
- PATH: 列出 shell 搜索 用户 输入的执行命令所在的目录。
- HOME: (类Unix系统) 和 userprofile (Microsoft Windows) 表示用户的家目录在文件系统中的位置。
- TEMP: 进程可以存储临时文件的位置。
- JAVA_HOME: jdk 目录的路径
- 伪环境变量 pseudo environment variables
- 并不真正存储在环境中,而是在请求时计算的
- 例如 cmd 里的 %CD% %DATE% , bash 里的 $RANDOM
- 伪环境变量好像只能在 shell 里使用,笔者虽然没有找到更多的资料,但实践的结果确实是这样
- powershell 里没有 cmd 里的伪环境变量,
猜测是使用cmd-let替代了
- powershell 里没有 cmd 里的伪环境变量,
在 Unix 系统通过初始化脚本启动时,环境变量通常会在此时被初始化,因此会被系统中的其它进程所继承。
在 Windows 系统中,环境变量存储在 Windows 注册表中。
- 环境变量保存在这几个位置,大多数情况下只需要修改 CurrentControlSet 就可以了
- HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
- HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Volatile Environment
- HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Session Manager\Environment
- HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Session Manager\Volatile Environment
- HKEY_LOCAL_MACHINE\SYSTEM\ControlSet002\Control\Session Manager\Environment
- HKEY_LOCAL_MACHINE\SYSTEM\ControlSet002\Control\Session Manager\Volatile Environment
- HKEY_USERS\用户的sid\Environment
- HKEY_USERS\用户的sid\Volatile Environment
- 大多数情况下关注 Environment 就可以了
- Volatile Environment 是一些和用户相关的变量,会跟随用户改变而改变,例如 userprofile
- 对于环境变量而言,注册表中有两种类型
- REG_SZ
- REG_EXPAND_SZ
- 具体区别是
- REG_SZ 类型的键值中存在的可扩展占位符 %xxx% 不会被系统解释;
- REG_EXPAND_SZ 类型的键值中存在的 %xxx% 的部分会被系统解释。
- 好像是只要用户新建或修改过的环境变量,类型都会是 REG_EXPAND_SZ
- 可以通过直接读写注册表里的值来修改环境变量
(可能会因为权限不足而失败)
在 cmd 里操作环境变量
- 查看全部环境变量
set
- 设置环境变量
set example=testvar
- 输出环境变量
echo %path%
- 访问环境变量和访问普通变量是一样的
- 环境变量名大小写不敏感
- 删除环境变量
set example=
- 修改环境变量
- 可以直接覆盖原本的环境变量
- 可以先删除再新建
- 给 path 添加新的路径
set path=%path%;C:\Tools
cmd 里修改环境变量只影响当前的进程
在 powershell 里操作环境变量
PowerShell 提供了几种不同的方法来使用和管理环境变量
- 使用 variable 语法
- cmdlet
- .NET 类
使用 variable 语法
- 查看全部环境变量
ls Env:
- 设置环境变量
$Env:example = "testvar"
- 输出环境变量
echo $Env:example
- 访问环境变量和访问普通变量是一样的
- 环境变量名大小写不敏感
- 删除环境变量
del Env:example
- 修改环境变量
- 可以直接覆盖原本的环境变量
- 可以先删除再新建
- 给 path 添加新的路径
$Env:Path += ';C:\Tools'
cmdlet
- 查看全部环境变量
Get-Item -Path Env:\*
- 设置环境变量
New-Item -Path Env: -Name example -Value 'testvar' 或 Set-Item -Path Env:example -Value 'testvar'
- 输出环境变量
Get-Item -Path Env:\example Get-Item -Path Env:\example | Select-Object -Property Value Get-Item -Path Env:\example | Select-Object -ExpandProperty Value
- 删除环境变量
Remove-Item -Path Env:example
- 修改环境变量
修改值 Set-Item -Path Env:example -Value 'testvar2' 重命名 Rename-Item -Path Env:example -NewName example
- 给 path 添加新的路径
Set-Item -Path Env:\Path -Value $Env:Path';C:\Tools' Set-Item -Path Env:\Path -Value ((Get-Item -Path Env:\Path | Select-Object -ExpandProperty Value)+';C:\Tools')
.NET 类
- 查看全部环境变量
[Environment]::GetEnvironmentVariables()
- 设置环境变量
[Environment]::SetEnvironmentVariable('example', 'testvar')
- 输出环境变量
[Environment]::GetEnvironmentVariable('example')
- 删除环境变量
[Environment]::SetEnvironmentVariable('example', '')
- 修改环境变量
- 可以直接覆盖原本的环境变量
- 可以先删除再新建
- 给 path 添加新的路径
[Environment]::SetEnvironmentVariable('path', [Environment]::GetEnvironmentVariable('path') + ';C:\Tools')
保存环境变量的修改
Environment 能修改全局的或当前用户的环境变量,其它两种方法在默认情况下都是修改当前进程的环境变量。修改全局的环境变量可能会遇到权限的问题。
[Environment]::SetEnvironmentVariable('example', 'testvar', [EnvironmentVariableTarget]::Process)
[Environment]::SetEnvironmentVariable('example', 'testvar', [EnvironmentVariableTarget]::User)
[Environment]::SetEnvironmentVariable('example', 'testvar', [EnvironmentVariableTarget]::Machine)
在 windows 修改环境变量最保险的方式还是通过这里修改
此电脑 -> 属性 -> 高级系统设置 -> 环境变量
使用 wmic 操作环境变量
查看全部环境变量
wmic ENVIRONMENT wmic ENVIRONMENT where "username='<system>'"
设置环境变量
wmic ENVIRONMENT create name="JAVA_HOME",username="<system>",VariableValue="%javaPath%"
输出环境变量
wmic ENVIRONMENT where "name='Path' and username='<system>'"
删除环境变量
wmic ENVIRONMENT where "name='JAVA_HOME' and username='<system>'" delete
使用 wmic 修改环境变量时要注意作用域,如果没有指定 username ,可能会把所有用户和系统全局的环境变量一起修改。
其实 windows 里还有很多奇技淫巧可以修改环境变量
在 bash 里操作环境变量
- 查看全部环境变量
printenv 或 export -p
- 设置环境变量
export example=testvar
- 输出环境变量
echo $PATH 或 printenv PATH
- 访问环境变量和访问普通变量是一样的
- 环境变量名大小写敏感
- 删除环境变量
export -n example
- 修改环境变量
- 可以直接覆盖原本的环境变量
- 可以先删除再新建
- 给 path 添加新的路径
export PATH=$PATH:/usr/local/nginx/sbin
- 在 Linux 或 macOS 上,使用冒号 (:) 而不是分号 (;)
保存环境变量的修改
- bash 修改的环境变量只在当前 shell 有效, shell 关闭后就会失效
- 修改用户的环境变量
- 修改这个文件
~/.bashrc
- 修改环境变量时,大致需要这样修改,因为是脚本文件所以就是在脚本里添加 export 命令来修改环境变量
# 添加一个新的环境变量 export example=testvar # 给 path 添加新的路径 export PATH=$PATH:/usr/local/nginx/sbin
- 修改 ~/.bashrc 后,运行这句命令就能生效
source ~/.bashrc
- 修改这个文件
- 修改系统的环境变量
- 修改这个文件
/etc/bashrc (redhat) /etc/bash.bashrc (debian)
- 又或者直接修改这个文件 /etc/environment
- /etc/environment 是纯文本文件,不是脚本,不能用变量
- /etc/bash.bashrc , /etc/profile 这类是 shell 脚本
- 修改系统的环境变量则需要重启系统才能生效
- 修改这个文件 /etc/profile 也可以达到类似的效果
- /etc/profile 是用户登录时执行的脚本
(但这里其实是有一点坑的,因为有些 shell 脚本执行时可能不需要登录用户)
- 修改这个文件
- liunx 环境变量的修改本质上是修改各种配置脚本
- 各种配置脚本有不一样的加载顺序 (可以参考这篇文章《终端,控制台和外壳》)
- 各种配置脚本在不同的发行版里可能会有不同的文件名
- source 命令的作用是在当前 shell 中执行脚本,不会启动新的 shell
- export 命令的作用是设置或显示环境变量, export 可新增,修改或删除环境变量,供后续执行的程序使用
在 c 语言里操作环境变量
c 里有一系列的函数来操作环境变量
char *getenv(const char *name);
int putenv(char *string);
int setenv(const char *envname, const char *envval, int overwrite);
int unsetenv(const char *name);
char *secure_getenv(const char *name);
int clearenv(void);
- 这些函数都在在头文件 stdlib.h 中声明
- 全局变量 char **environ 和 main 函数的第三个参数 char* envp[] 都保存有完整的环境变量
- 全局变量 char **environ 在头文件 unistd.h 中声明
- 只有 getenv 是 ascii c
- putenv, setenv, unsetenv 这三个函数和 environ envp 都是来自 posix
- secure_getenv 和 clearenv 来自 gun c
- 笔者其实没多少心思区分哪个函数来自哪个标准,反正用 gcc 编译时都能用。
例子
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
extern char **environ;
int main(int argc, char* argv[], char* envp[])
{
for (int i = 0; envp[i] != NULL; ++i)
{
printf("%s\n", envp[i]);
}
printf("****\n");
for (char **var = environ; *var != NULL; ++var)
{
printf("%s\n", *var);
}
printf("****\n");
printf("%s\n", getenv("PATH"));
printf("****\n");
putenv("TEST=123");
printf("%s\n", getenv("TEST"));
printf("****\n");
int overwrite = 1;
setenv("TEST", "321", overwrite);
printf("%s\n", getenv("TEST"));
printf("****\n");
unsetenv("TEST");
clearenv();
return 0;
}
其它语言操作环境变量也会有相应的语法,只要看一下文档就好了。
查看某个进程的环境变量
- linux
cat /proc/<pid>/environ cat /proc/360/environ cat /proc/25999/environ | tr '\0' '\n'
- windows
- 使用这个工具 https://docs.microsoft.com/zh-cn/sysinternals/downloads/process-explorer
- 这个 api 也可以,但好像只能在进程内部调用,不能查询到其它进程的环境变量
- 使用 windbg 中的 !peb 命令也可以查看到对应进程的环境变量
- 互联网上还有一些方法是直接读取进程的 PEB(process environment block) ,这样的方式会比较复杂
- https://www.codeproject.com/Articles/25647/Read-Environment-Strings-of-Remote-Process
- 可以参考这篇文章 《Windows 下通过 PEB 读取进程的环境变量》
参考
- https://en.wikipedia.org/wiki/Environment_variable
- https://en.cppreference.com/w/c/program/getenv
- https://pubs.opengroup.org/onlinepubs/009696899/basedefs/xbd_chap08.html
- https://man7.org/linux/man-pages/man3/clearenv.3.html
- https://docs.microsoft.com/en-us/windows/win32/procthread/environment-variables
- https://docs.microsoft.com/zh-cn/windows-server/administration/windows-commands/set_1
- https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_environment_provider?view=powershell-7.2
- https://docs.microsoft.com/zh-cn/powershell/module/microsoft.powershell.core/about/about_environment_variables?view=powershell-7.2
- https://docs.microsoft.com/zh-cn/dotnet/api/system.environment?view=net-6.0
- https://docs.microsoft.com/zh-cn/dotnet/api/system.environmentvariabletarget?view=net-6.0