Linux 环境变量深度详解

一、环境变量基础

1.1 什么是环境变量

环境变量是进程级别的键值对配置,每个进程都有一份独立的环境变量表。子进程创建时会从父进程继承一份副本,之后互不影响。

在 Linux 中,环境变量本质就是一组字符串,格式统一为 NAME=VALUE

1.2 为什么需要环境变量

  1. 配置与代码分离:数据库地址、API 密钥不用写死在代码里
  2. 进程间信息传递:父进程给子进程传递运行上下文
  3. 系统全局约定:PATH、HOME、USER 等标准变量被所有程序遵循
  4. 多环境部署:同一套代码通过环境变量区分开发 / 测试 / 生产

二、Shell 中的环境变量操作

2.1 常用命令

bash

运行

复制代码
# 查看所有环境变量
env
printenv

# 查看单个变量
echo $PATH
printenv PATH

# 设置临时变量(仅当前 Shell 有效)
export MY_VAR="hello"

# 只定义 Shell 变量,不进入环境
LOCAL_VAR="world"

# 删除变量
unset MY_VAR

# 临时给单个命令设置变量
MY_VAR=test ./myprogram

2.2 环境变量 vs Shell 变量

很多人混淆这两个概念:

  • Shell 变量 :只在当前 Shell 内部有效,子进程继承不到(如 a=1
  • 环境变量 :会被子进程继承(如 export a=1

bash

运行

复制代码
# 定义 Shell 变量
NAME="tom"
bash          # 启动子 Shell
echo $NAME    # 空的,继承不到
exit

# 导出为环境变量
export NAME="tom"
bash
echo $NAME    # tom,可以继承

2.3 常见系统环境变量

表格

变量名 作用 示例
PATH 可执行文件搜索路径,冒号分隔 /usr/bin:/usr/local/bin:/sbin
HOME 当前用户家目录 /home/zhangsan
USER / LOGNAME 当前用户名 zhangsan
SHELL 默认登录 Shell /bin/bash
PWD 当前工作目录 /home/zhangsan/code
OLDPWD 上一次工作目录 -
LANG 语言与字符集 zh_CN.UTF-8
LD_LIBRARY_PATH 动态库搜索路径 /usr/local/lib
PS1 命令提示符格式 \u@\h:\w\$

2.4 PATH 的工作原理

当你输入 ls 而不是 /bin/ls 时,Shell 会按 PATH 中的目录顺序挨个查找:

  1. 先看 /usr/local/bin/ls 存在吗?
  2. 再看 /usr/bin/ls 存在吗?
  3. 找到第一个就执行,找不到就报 command not found

bash

运行

复制代码
# 把当前目录加入 PATH(不推荐,有安全风险)
export PATH=.:$PATH

# 把 /opt/myapp/bin 追加到 PATH
export PATH=$PATH:/opt/myapp/bin

三、环境变量的持久化配置

环境变量按作用域分为三级,优先级从高到低:

3.1 三级作用域对比

表格

级别 配置文件 生效范围 失效时机
进程级 export 命令 当前 Shell / 进程 关闭终端即失效
用户级 ~/.bashrc ~/.bash_profile ~/.profile 当前用户所有 Shell 用户注销
系统级 /etc/profile /etc/bash.bashrc /etc/environment 所有用户 重启系统

3.2 Bash 启动加载顺序

登录式 Shell(如 ssh 登录、su -):

plaintext

复制代码
/etc/profile → /etc/profile.d/*.sh → ~/.bash_profile → ~/.bashrc → /etc/bashrc

非登录式 Shell(如图形化终端、bash 命令):

plaintext

复制代码
~/.bashrc → /etc/bashrc → /etc/profile.d/*.sh

最佳实践 :个人配置写在 ~/.bashrc,系统全局配置写在 /etc/profile.d/ 下新建 .sh 文件,不要直接修改 /etc/profile

3.3 让配置立即生效

bash

运行

复制代码
source ~/.bashrc
# 或简写
. ~/.bashrc

四、C 语言操作环境变量

4.1 三种访问方式

方式 1:main 第三个参数 envp

c

运行

复制代码
#include <stdio.h>

int main(int argc, char *argv[], char *envp[]) {
    // envp 是环境变量数组,以 NULL 结尾
    // 每个元素都是 "KEY=value" 格式的字符串
    for (int i = 0; envp[i] != NULL; i++) {
        printf("%s\n", envp[i]);
    }
    return 0;
}
方式 2:全局变量 environ

c

运行

复制代码
#include <stdio.h>

extern char **environ;  // libc 定义的全局变量

int main() {
    char **p = environ;
    while (*p) {
        printf("%s\n", *p);
        p++;
    }
    return 0;
}
方式 3:getenv 函数(最常用)

c

运行

复制代码
#include <stdio.h>
#include <stdlib.h>

int main() {
    // 读取 PATH
    char *path = getenv("PATH");
    if (path) {
        printf("PATH = %s\n", path);
    }
    
    // 读取自定义变量,不存在则用默认值
    char *mode = getenv("APP_MODE");
    if (!mode) mode = "development";
    
    printf("运行模式: %s\n", mode);
    return 0;
}

4.2 修改环境变量

c

运行

复制代码
#include <stdio.h>
#include <stdlib.h>

int main() {
    // 设置环境变量
    // 第三个参数 1 = 覆盖已存在的值,0 = 不覆盖
    setenv("APP_ENV", "production", 1);
    printf("APP_ENV = %s\n", getenv("APP_ENV"));
    
    // 修改环境变量
    setenv("APP_ENV", "debug", 1);
    printf("修改后: %s\n", getenv("APP_ENV"));
    
    // 删除环境变量
    unsetenv("APP_ENV");
    printf("删除后: %s\n", getenv("APP_ENV"));  // (null)
    
    // putenv 方式(不推荐,容易内存泄漏)
    putenv("FOO=bar");
    
    return 0;
}

重要 :进程内修改环境变量只影响当前进程及其子进程,绝对不会影响父进程(也就是你的 Shell)。这是最常见的误区。

4.3 继承与 fork/exec 的关系

plaintext

复制代码
父进程(有环境变量 A=1)
    │
    ├─ fork() → 创建子进程,复制环境变量表
    │
    └─ 子进程拥有独立的环境变量副本
         │
         └─ exec() → 加载新程序,默认保留原有环境变量
              (execle 可以指定全新的环境变量)

示例:子进程继承环境变量

c

运行

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
    setenv("TEST_VAR", "hello_from_parent", 1);
    
    pid_t pid = fork();
    if (pid == 0) {
        // 子进程可以读到父进程设置的变量
        printf("子进程: TEST_VAR = %s\n", getenv("TEST_VAR"));
        
        // 子进程修改不影响父进程
        setenv("TEST_VAR", "modified_by_child", 1);
        printf("子进程修改后: %s\n", getenv("TEST_VAR"));
        return 0;
    }
    
    wait(NULL);
    printf("父进程: TEST_VAR = %s\n", getenv("TEST_VAR"));
    // 输出还是 hello_from_parent
    return 0;
}

五、/proc 文件系统查看环境变量

Linux 中每个进程的环境变量都可以通过 /proc 文件系统查看:

bash

运行

复制代码
# 查看 PID=1234 的进程环境变量
cat /proc/1234/environ | tr '\0' '\n'

/proc/PID/environ 文件中,每个环境变量以 \0(空字符)分隔,所以用 tr '\0' '\n' 换成换行方便阅读。


六、最佳实践与常见坑

  1. 不要硬编码路径 :用 getenv("HOME") 获取家目录,不要写死 /home/xxx
  2. 敏感信息放环境变量:数据库密码、API Key 不要提交到代码仓库
  3. 布尔变量判断存在性 :如 if (getenv("DEBUG")) 来开启调试模式
  4. 提供默认值:环境变量不存在时要有合理的默认行为
  5. 不要在程序里改 PATH 然后指望 Shell 生效:子进程改不了父进程
  6. 环境变量名全大写:这是 Unix 世界的约定俗成

七、环境变量知识体系图

plaintext

复制代码
                     环境变量
                         │
          ┌──────────────┼──────────────┐
          │              │              │
       基础概念       操作方式        生命周期
          │              │              │
     键值对 NAME=VALUE  Shell命令      三级作用域
     进程级独立副本     export/ unset   进程级>用户级>系统级
     fork时继承        env/printenv    /etc/profile
     exec默认保留      $VAR 引用       ~/.bashrc
          │              │              │
       标准变量       C语言API       加载顺序
          │              │              │
     PATH搜索路径     getenv读取      登录式Shell
     HOME家目录       setenv设置      非登录式Shell
     USER用户名       putenv/ unset   source立即生效
     LANG语言         environ全局      子进程不影响父进程
     LD_LIBRARY_PATH  envp参数

谢谢