关于环境变量的不完整总结

这篇文章最后更新的时间在六个月之前,文章所叙述的内容可能已经失效,请谨慎参考!

环境变量是什么

环境变量(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替代了

在 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 里操作环境变量

  1. 查看全部环境变量
    set
    
  2. 设置环境变量
    set example=testvar
    
  3. 输出环境变量
    echo %path%
    
    • 访问环境变量和访问普通变量是一样的
    • 环境变量名大小写不敏感
  4. 删除环境变量
    set example=
    
  5. 修改环境变量
    • 可以直接覆盖原本的环境变量
    • 可以先删除再新建
  6. 给 path 添加新的路径
    set path=%path%;C:\Tools
    

cmd 里修改环境变量只影响当前的进程

在 powershell 里操作环境变量

PowerShell 提供了几种不同的方法来使用和管理环境变量

  • 使用 variable 语法
  • cmdlet
  • .NET 类

使用 variable 语法

  1. 查看全部环境变量
    ls Env:
    
  2. 设置环境变量
    $Env:example = "testvar"
    
  3. 输出环境变量
    echo $Env:example
    
    • 访问环境变量和访问普通变量是一样的
    • 环境变量名大小写不敏感
  4. 删除环境变量
    del Env:example
    
  5. 修改环境变量
    • 可以直接覆盖原本的环境变量
    • 可以先删除再新建
  6. 给 path 添加新的路径
    $Env:Path += ';C:\Tools'
    

cmdlet

  1. 查看全部环境变量
    Get-Item -Path Env:\*
    
  2. 设置环境变量
    New-Item -Path Env: -Name example -Value 'testvar'
    或
    Set-Item -Path Env:example -Value 'testvar'
    
  3. 输出环境变量
    Get-Item -Path Env:\example
    Get-Item -Path Env:\example | Select-Object -Property Value
    Get-Item -Path Env:\example | Select-Object -ExpandProperty Value
    
  4. 删除环境变量
    Remove-Item -Path Env:example
    
  5. 修改环境变量
    修改值
    Set-Item -Path Env:example -Value 'testvar2'
    重命名
    Rename-Item -Path Env:example -NewName example
    
  6. 给 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 类

  1. 查看全部环境变量
    [Environment]::GetEnvironmentVariables()
    
  2. 设置环境变量
    [Environment]::SetEnvironmentVariable('example', 'testvar')
    
  3. 输出环境变量
    [Environment]::GetEnvironmentVariable('example')
    
  4. 删除环境变量
    [Environment]::SetEnvironmentVariable('example', '')
    
  5. 修改环境变量
    • 可以直接覆盖原本的环境变量
    • 可以先删除再新建
  6. 给 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 操作环境变量

  1. 查看全部环境变量

    wmic ENVIRONMENT
    wmic ENVIRONMENT where "username='<system>'"
    
  2. 设置环境变量

    wmic ENVIRONMENT create name="JAVA_HOME",username="<system>",VariableValue="%javaPath%"
    
  3. 输出环境变量

    wmic ENVIRONMENT where "name='Path' and username='<system>'"
    
  4. 删除环境变量

    wmic ENVIRONMENT where "name='JAVA_HOME' and username='<system>'" delete
    

使用 wmic 修改环境变量时要注意作用域,如果没有指定 username ,可能会把所有用户和系统全局的环境变量一起修改。

其实 windows 里还有很多奇技淫巧可以修改环境变量

在 bash 里操作环境变量

  1. 查看全部环境变量
    printenv
    或
    export -p
    
  2. 设置环境变量
    export example=testvar
    
  3. 输出环境变量
    echo $PATH
    或
    printenv PATH
    
    • 访问环境变量和访问普通变量是一样的
    • 环境变量名大小写敏感
  4. 删除环境变量
    export -n example
    
  5. 修改环境变量
    • 可以直接覆盖原本的环境变量
    • 可以先删除再新建
  6. 给 path 添加新的路径
    export PATH=$PATH:/usr/local/nginx/sbin
    
    • 在 Linux 或 macOS 上,使用冒号 (:) 而不是分号 (;)

保存环境变量的修改

  • bash 修改的环境变量只在当前 shell 有效, shell 关闭后就会失效
  • 修改用户的环境变量
    1. 修改这个文件
      ~/.bashrc
      
    2. 修改环境变量时,大致需要这样修改,因为是脚本文件所以就是在脚本里添加 export 命令来修改环境变量
      # 添加一个新的环境变量
      export example=testvar
      # 给 path 添加新的路径
      export PATH=$PATH:/usr/local/nginx/sbin
      
    3. 修改 ~/.bashrc 后,运行这句命令就能生效
      source ~/.bashrc
      
  • 修改系统的环境变量
    1. 修改这个文件
      /etc/bashrc (redhat)
      /etc/bash.bashrc (debian)
      
    2. 又或者直接修改这个文件 /etc/environment
      • /etc/environment 是纯文本文件,不是脚本,不能用变量
      • /etc/bash.bashrc , /etc/profile 这类是 shell 脚本
    3. 修改系统的环境变量则需要重启系统才能生效
    4. 修改这个文件 /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;
}

其它语言操作环境变量也会有相应的语法,只要看一下文档就好了。

查看某个进程的环境变量

参考