当前位置: 首页 > news >正文

【Linux系统】命令行参数和环境变量

1. 环境变量基本概念

  • 定义:环境变量(Environment Variables)是操作系统中用于指定系统运行环境参数的变量,通常以键值对(变量名+内容)形式存在。它们为程序提供运行所需的配置信息,如文件路径、系统行为设置等 。

    • 示例:在C/C++编译时,链接器通过环境变量(如LIBRARY_PATH)自动定位动态/静态库的位置,无需手动指定路径即可成功生成可执行程序 。

2. 命令行参数

在环境变量展开介绍前,我们先来认识一下命令行参数,那命令行参数又是什么呢?

命令行参数则是用户在启动程序时传递给程序的参数,这些参数在程序运行时可用。它们通常是特定于程序的,用于指定程序运行时的一些选项或参数。

例如我们常写的main函数,他其实是可以有参数的

int main(int argc, char *argv[]) {// ...
}
  • int argc:参数个数,包括程序名。

  • char *argv[]:参数值数组,argv[0] 是程序名,后续元素是用户传递的参数。

argv是一个以NULL结尾的指针数组,结构如下:

char *argv[] = { "./a.out", "arg1", "arg2", NULL }; // 示例
  • argv[0]:程序名称(如"./a.out")。
  • argv[1..argc-1]:用户输入的参数。
  • argv[argc] = NULL:标识参数数组结束 。

示例代码解释

以下是一个简单的示例,展示如何使用命令行参数:

#include <stdio.h>int main(int argc, char *argv[]) {for (int i = 0; i < argc; i++) {printf("argv[%d]: %s\n", i, argv[i]);}return 0;
}

代码解析

  • argc 是命令行参数的个数。

  • argv 是一个指向字符数组的指针数组,每个元素对应一个命令行参数。

  • 通过循环遍历 argv 数组,可以打印出所有的命令行参数。

运行示例

假设将上述代码保存为 code.c,编译并运行:

ltx@hcss-ecs-d90d:~/lesson4$ ls -l
total 8
-rw-rw-r-- 1 ltx ltx 766 Jul 15 15:18 code.c
-rw-rw-r-- 1 ltx ltx  58 Jul 15 15:21 Makefile
ltx@hcss-ecs-d90d:~/lesson4$ make
gcc -o code code.c
ltx@hcss-ecs-d90d:~/lesson4$ ./code
argv[0]: ./code
ltx@hcss-ecs-d90d:~/lesson4$ ./code a
argv[0]: ./code
argv[1]: a
ltx@hcss-ecs-d90d:~/lesson4$ ./code a b
argv[0]: ./code
argv[1]: a
argv[2]: b
ltx@hcss-ecs-d90d:~/lesson4$ ./code a b c
argv[0]: ./code
argv[1]: a
argv[2]: b
argv[3]: c
ltx@hcss-ecs-d90d:~/lesson4$ ./code a b c d
argv[0]: ./code
argv[1]: a
argv[2]: b
argv[3]: c
argv[4]: d

执行make命令,根据Makefile中的规则编译代码。这里调用了gcc编译器,将code.c文件编译成一个可执行文件code

从运行结果可以看出,程序正确地接收并输出了命令行参数。argv[0]始终是程序本身的名称,后面的argv[1]argv[n]依次是提供的命令行参数。参数的个数由argc表示,在每次运行中,argc的值等于输出的argv索引最大值加1。

其实我们的进程在启动时默认就有一个命令行参数表

  • 传递机制:父进程(如Shell)通过exec()系统调用将参数表复制到子进程的栈空间,子进程的main()函数通过argc(参数数量)和argv(参数表指针)访问 。

 实际应用场景

那命令行参数有什么用呢?

我们再来看一段代码:

#include <stdio.h>
#include <string.h>// main有参数吗?有int main(int argc, char *argv[])
{if(argc != 2){printf("Usage: %s [-a|-b|-c]\n", argv[0]);return 1;}const char *arg = argv[1];if(strcmp(arg, "-a")==0)printf("这是功能1\n");else if(strcmp(arg, "-b")==0)printf("这是功能2\n");else if(strcmp(arg, "-c")==0)printf("这是功能3\n");elseprintf("Usage: %s [-a|-b|-c]\n", argv[0]);return 0;
}

运行结果:

ltx@hcss-ecs-d90d:~/lesson4$ make
gcc -o code code.c
ltx@hcss-ecs-d90d:~/lesson4$ ./code
Usage: ./code [-a|-b|-c]
ltx@hcss-ecs-d90d:~/lesson4$ ./code -a
这是功能1
ltx@hcss-ecs-d90d:~/lesson4$ ./code -b
这是功能2
ltx@hcss-ecs-d90d:~/lesson4$ ./code -c
这是功能3
ltx@hcss-ecs-d90d:~/lesson4$ ./code -abc
Usage: ./code [-a|-b|-c]
  1. 错误处理if(argc != 2)确保用户必须提供一个选项。如果参数数量不符,打印用法并退出(返回非零值表示错误)。
  2. 选项匹配strcmp比较argv[1]与预定义字符串(如"-a"),匹配成功则执行对应功能。这种设计允许程序通过不同选项激活不同子功能,体现了“单一程序,多种行为”的灵活模式。
  3. 鲁棒性设计else分支处理无效选项(如用户输入-d),防止程序崩溃。

此代码的核心在于命令行参数作为程序行为的控制开关,通过参数值动态决定执行路径。这是Linux系统工具(如lsgrep)的基础实现原理。

main函数命令行参数的功能与原理

命令行参数是操作系统与程序间传递配置信息的核心机制,尤其在Linux环境中,它实现了程序的模块化和可配置性。以下是其功能与原理的详细分析。

1. 核心功能
  • 行为控制:允许同一程序通过不同参数执行不同功能(如示例中的-a-b)。这是Linux指令(如ls -l vs ls -a)多样性的基础。
  • 输入传递:向程序传递运行时数据(如文件名、数值阈值)。例如,grep "pattern" file.txt中,"pattern""file.txt"通过argv传递。
  • 错误预防:通过argc检查参数数量,避免无效输入导致的未定义行为(如示例中的if(argc != 2))。
  • 自文档化argv[0]用于打印用法(如printf("Usage: %s ...", argv[0])),提升用户体验。
2. 工作原理
  • Shell解析阶段
    • 用户输入命令(如./program -a)后,Shell(如Bash)按空格分割字符串,生成argv数组:["", "-a", NULL]
    • Shell通过exec()系统调用(如execv)启动程序,并将argv数组复制到进程内存栈中。
  • 程序执行阶段
    • 操作系统加载程序后,自动将argcargv传递给main函数。
    • 程序解析argv(如使用strcmp或库函数如getopt),执行对应逻辑。
  • 内存结构
    • argvenviron(环境变量表)在进程地址空间中相邻存储,均由父进程(Shell)

ls是Linux核心命令,用于列出目录内容。其选项(如-l-a)通过main函数的命令行参数实现,本质与我们的示例代码相同。

所有Linux命令的选项本质都是通过mainargv实现

为什么平时写代码时main函数没有参数?

  1. 简单性:大多数简单的程序不需要命令行参数。它们可能从标准输入读取数据,或者处理硬编码的文件名等。在这种情况下,使用无参数的main函数更为简洁和直接。

  2. 代码可读性:省略不必要的参数可以使代码更清晰,减少混乱。如果程序不需要处理命令行参数,那么包含argcargv只会增加不必要的复杂性。

  3. 默认行为:C标准允许main函数的这两种常见形式(带参数和不带参数)。编译器通常会支持这两种形式,所以开发者可以选择最适合自己需求的形式。

  4. 习惯和惯例:在教学和示例代码中,通常使用最简单的形式来避免分散学习者的注意力。这可能导致许多人在初学时习惯于使用无参数的main函数。

总之,是否使用带参数的main函数取决于程序的具体需求。如果程序不需要命令行参数,使用无参数的main函数是完全合理且常见的做法。


3. 一个例子,一个环境变量

3.1 示例

ltx@hcss-ecs-d90d:~/lesson4$ ls
code  code.c  Makefile
ltx@hcss-ecs-d90d:~/lesson4$ pwd
/home/ltx/lesson4
ltx@hcss-ecs-d90d:~/lesson4$ ./code
Usage: ./code [-a|-b|-c]

思考:为什么我们的代码编译成可执行程序后需要加路径 ./ (当前目录下),而系统的指令不需要带路径呢?

我们来试一下,如果我们的可执行程序不带路径会怎么样呢?

ltx@hcss-ecs-d90d:~/lesson4$ code
Command 'code' not found, but can be installed with:
snap install code
Please ask your administrator.

我们发现,系统说他找不到这个指令

那如果我们在使用系统的指令的时候带上路径会有什么不一样吗?

ltx@hcss-ecs-d90d:~/lesson4$ which ls
/usr/bin/ls
ltx@hcss-ecs-d90d:~/lesson4$ /usr/bin/ls
code  code.c  Makefile
ltx@hcss-ecs-d90d:~/lesson4$ ls
code  code.c  Makefile

可以看到,系统的指令带不带路径都能找到,我们的程序不带路径就找不到,这是为什么呢?

在Linux系统中,当你运行一个可执行程序时,系统会根据环境变量PATH中定义的目录来查找该程序。而我们的可执行程序code不在PATH中定义的目录里,所以在运行时需要指定其相对路径./code。否则,系统会在PATH指定的目录中查找,找不到就会提示命令未找到。

3.2 环境变量PATH

环境变量PATH是一个包含多个目录路径的变量,用于告诉操作系统在哪些目录中查找可执行文件。例如,当你在命令行中输入ls时,系统会在PATH指定的目录中依次查找ls命令。

运行以下命令可以查看当前的PATH环境变量:

echo $PATH

输出示例:

ltx@hcss-ecs-d90d:~/lesson4$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

为什么系统命令不需要带路径?

系统命令通常安装在PATH中定义的目录里,如/usr/bin/bin等。因此,当你运行ls时,系统会在PATH中的目录查找并找到/usr/bin/ls

为什么我们的程序不带路径就找不到?

我们的程序code位于当前目录./下,而./通常不在PATH环境变量中。因此,当你运行code时,系统会在PATH指定的目录中查找,但找不到,所以提示命令未找到。

为什么默认不包含当前目录?

  • 安全风险:若 PATH 包含当前目录(.),攻击者可在公共目录放置恶意程序(如伪造 ls),用户进入该目录后输入 ls 可能触发恶意程序 。
  • 命名冲突:当前目录下的程序名可能与系统指令冲突(如自定义 test 程序覆盖系统 test 命令)。

如何让我们的程序不带路径就能运行?

有以下几种方法:

方法命令示例优点缺点
复制到系统路径sudo cp code /usr/local/bin永久生效,所有用户可用需 root 权限,污染系统路径 
修改 PATH 环境变量export PATH=$PATH:/home/ltx/lesson4无需权限,快速生效仅当前终端会话有效 
永久生效配置在 \~/.bashrc 添加:
export PATH=$PATH:/home/ltx/lesson4
用户级持久化需重启终端或执行 source \~/.bashrc 

示例:

ltx@hcss-ecs-d90d:~/lesson4$ export PATH=$PATH:/home/ltx/lesson4
ltx@hcss-ecs-d90d:~/lesson4$ code
Usage: code [-a|-b|-c]
ltx@hcss-ecs-d90d:~/lesson4$ code -a
这是功能1

为什么系统命令可以带路径运行?

系统命令的路径通常在PATH中,因此无论是否带路径,系统都能找到它们。例如,/usr/bin/lsls都可以运行,因为/usr/binPATH中。

总结

  • 系统通过环境变量PATH来查找命令。

  • 我们的程序code不在PATH中,因此需要带路径运行。

  • 可以通过修改PATH或移动程序到PATH中的目录来让程序不带路径运行。

3.3 环境变量的存储机制深度解析

环境变量的本质是操作系统与应用程序间的动态配置桥梁,其存储设计融合了内存管理、持久化策略和访问效率的平衡。


一、存储层级:从内存到磁盘的协同架构

环境变量的存储分为运行时内存存储持久化磁盘存储两级,通过操作系统内核协同运作。

存储层级物理载体数据结构生命周期典型操作
运行时存储进程内存空间指针数组(char **environ进程存活期间getenv()setenv()
持久化存储磁盘配置文件(如.bashrc文本键值对永久生效source \~/.bashrc
  1. 运行时内存存储机制

    • 数据结构:以 NULL 结尾的连续指针数组(char *envp[]),每个指针指向 KEY=VALUE 格式的字符串。

      // 内存布局示例
      char *envp[] = { "PATH=/usr/bin", "HOME=/home/ltx", NULL  // 结束标记
      };
      
    • 存储位置:位于进程用户空间栈的顶部,通过 task_struct->mm->env_start 定位(Linux内核)。

    • 访问效率:O(n) 遍历复杂度,但通过哈希表缓存(如Bash的全局环境变量表)优化高频访问。

  2. 持久化磁盘存储机制

    • 存储形式:纯文本键值对(如 export PATH=$PATH:/new/path)。

    • 加载过程

    • 嵌入式系统特例:U-Boot将环境变量存储在独立Flash扇区,包含CRC校验头和标志位:

      struct env_data {uint32_t crc;          // 校验码uint8_t flags;         // 状态标志char data[4096];       // 键值对数据
      };
      

二、数据结构:键值对的组织与优化策略

  1. 基本结构

    • 键值编码KEY=VALUE 格式,禁止空格(如 PATH=/usr/bin:/local/bin)。
    • 内存布局:连续字符串数组,通过 extern char **environ 全局变量访问。
  2. 性能优化技术

    • Copy-on-Write:子进程继承父进程环境变量表时共享内存页,修改时复制新页。
    • 哈希索引:Shell维护哈希表(如Bash的 env_hash),将 O(n) 查找降至 O(1)
    • 字符串池化:重复值复用同一内存地址(如多进程共享 PATH 值)。

三、进程继承:环境变量的传递与隔离

  1. 继承机制

    • fork() 时复制:子进程获得父进程环境变量表的只读副本,通过指针共享原始数据。
    • exec() 时重建:新程序加载时,内核将环境变量表指针压入新进程栈顶,初始化 environ
  2. 作用域隔离

    setenv("TEMPVAR", "123");  // 修改当前进程环境
    pid_t pid = fork();
    if (pid == 0) {// 子进程 TEMPVAR 仍为原值setenv("TEMPVAR", "456");  // 仅修改子进程副本
    }
    

4. 常见环境变量

以下是一些常见环境变量的详解:

1. PATH

  • 作用:指定系统查找可执行文件的路径。

  • 示例/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

  • 说明:当在终端输入命令时,系统会在这些路径下查找对应的可执行文件。

2. HOME

  • 作用:指示当前用户的主目录。

  • 示例/home/username

  • 说明:用户的主目录通常用于存储个人文件和配置文件。

3. SHELL

  • 作用:指定当前用户使用的 shell 类型。

  • 示例/bin/bash

  • 说明:表明用户使用的是哪种 shell(如 Bash、Zsh 等)。

4. USER

  • 作用:当前登录的用户名。

  • 示例username

  • 说明:用于标识当前用户。

5. LOGNAME

  • 作用:当前登录的用户名。

  • 示例username

  • 说明:与 USER 类似,通常用于记录登录名称。

6. PWD

  • 作用:当前工作目录。

  • 示例/home/username/documents

  • 说明:显示用户当前所在的目录。

7. oldPWD

  • 作用:上一次工作目录。

  • 示例/home/username/downloads

  • 说明:用于记录用户之前所在的目录,方便用户返回。

8. LANG

  • 作用:指定系统的语言和区域设置。

  • 示例zh_CN.UTF-8

  • 说明:影响系统显示的语言和字符编码。

9. TZ

  • 作用:设置时区。

  • 示例Asia/Shanghai

  • 说明:影响系统显示的时间。

10. DISPLAY

  • 作用:指定显示图形界面的显示器。

  • 示例:0

  • 说明:用于 X Window 系统,标识图形界面显示的设备。

11. EDITOR

  • 作用:指定默认的文本编辑器。

  • 示例vimnano

  • 说明:用于在命令行中编辑文件。

12. PAGER

  • 作用:指定默认的分页程序。

  • 示例lessmore

  • 说明:用于分页显示文件内容。

13. LD_LIBRARY_PATH

  • 作用:指定动态链接库的搜索路径。

  • 示例/usr/local/lib:/opt/myapp/lib

  • 说明:系统会在这些路径下查找所需的动态链接库。

14. CLASSPATH

  • 作用:指定 Java 类文件的搜索路径。

  • 示例/.:/usr/lib/java:.

  • 说明:用于 Java 开发,指定 Java 虚拟机查找类文件的路径。

15. HISTFILE

  • 作用:指定命令历史记录文件的位置。

  • 示例~/.bash_history

  • 说明:用于记录用户在 shell 中执行过的命令。

16. HISTSIZE

  • 作用:设置命令历史记录的大小。

  • 示例1000

  • 说明:定义可以保存的命令历史记录的数量。

17. MAIL

  • 作用:指定用户的邮件文件位置。

  • 示例/var/mail/username

  • 说明:用于通知用户有新邮件。

18. TERM

  • 作用:指定终端类型。

  • 示例xtermlinux

  • 说明:影响终端模拟器的行为和显示效果。

19. SSH_AGENT_PID

  • 作用:指定 ssh-agent 进程的 PID。

  • 示例1234

  • 说明:用于 SSH 密钥管理。

20. SSH_AUTH_SOCK

  • 作用:指定 ssh-agent 的套接字文件位置。

  • 示例/tmp/ssh-abc123/agent.1234

  • 说明:用于 SSH 密钥认证。

21. VIRTUAL_ENV

  • 作用:指定当前激活的 Python 虚拟环境路径。

  • 示例/home/username/envs/myenv

  • 说明:用于 Python 开发,隔离项目依赖。

22. XDG_CONFIG_HOME

  • 作用:指定用户配置文件的目录。

  • 示例~/.config

  • 说明:用于存储应用程序的配置文件。

23. XDG_DATA_HOME

  • 作用:指定用户数据文件的目录。

  • 示例~/.local/share

  • 说明:用于存储应用程序的数据文件。

24. XDG_CACHE_HOME

  • 作用:指定用户缓存文件的目录。

  • 示例~/.cache

  • 说明:用于存储应用程序的缓存文件。

25. XDG_RUNTIME_DIR

  • 作用:指定用户运行时文件的目录。

  • 示例/run/user/1000

  • 说明:用于存储用户运行时的临时文件。

26. XDG_DESKTOP_DIR

  • 作用:指定用户的桌面目录。

  • 示例~/.desktop

  • 说明:用于存储桌面文件和快捷方式。

27. XDG_DOCUMENTS_DIR

  • 作用:指定用户的文档目录。

  • 示例~/documents

  • 说明:用于存储用户的文档文件。

28. XDG_DOWNLOAD_DIR

  • 作用:指定用户的下载目录。

  • 示例~/downloads

  • 说明:用于存储下载的文件。

29. XDG_MUSIC_DIR

  • 作用:指定用户的音乐目录。

  • 示例~/music

  • 说明:用于存储音乐文件。

30. XDG_PICTURES_DIR

  • 作用:指定用户的图片目录。

  • 示例~/pictures

  • 说明:用于存储图片文件。

31. XDG_VIDEOS_DIR

  • 作用:指定用户的视频目录。

  • 示例~/videos

  • 说明:用于存储视频文件。

总结

环境变量在操作系统和应用程序中起着重要的作用。它们帮助配置运行时环境,指定资源位置,并影响程序的行为。了解这些常见环境变量的用途可以帮助用户更好地管理和使用系统资源,提高工作效率。


5. 环境变量操作命令

  1. echo 命令

    • 用途:显示某个环境变量的值。

    • 示例:echo $PATH。这个命令会输出当前系统中环境变量 PATH 的值,它包含了系统查找可执行文件的多个目录路径,这些路径之间用冒号分隔。

  2. export 命令

    • 用途:设置一个新的环境变量或修改现有环境变量的值,并将其导出到 shell 环境中,使其对当前 shell 会话及其子进程可见。

    • 示例:export MY_VAR="my_value"。这会创建一个新的环境变量 MY_VAR,并将其值设置为 "my_value"。如果该变量已经存在,这个命令会修改其值。

  3. env 命令

    • 用途:显示所有当前环境变量。

    • 示例:直接在终端输入 env,然后回车,系统会列出当前 shell 环境中所有的环境变量及其对应的值,包括系统默认的环境变量和用户自定义的环境变量。

  4. unset 命令

    • 用途:清除环境变量,使其不再存在于当前 shell 环境中。

    • 示例:unset MY_VAR。这个命令会清除之前设置的 MY_VAR 环境变量,之后在当前 shell 会话中再使用 echo $MY_VAR 就不会输出该变量的值了。

  5. set 命令

    • 用途:显示本地定义的 shell 变量和环境变量。

    • 示例:在终端输入 set,它会列出当前 shell 环境中所有的变量,包括环境变量和 shell 的局部变量。这些变量可能包括用户定义的变量、shell 内置变量以及环境变量等。

示例:

ltx@hcss-ecs-d90d:~/lesson4$ echo $PATH #输出环境变量PATH的值
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
ltx@hcss-ecs-d90d:~/lesson4$ export MY_VAR="my_value" #设置一个新的环境变量
ltx@hcss-ecs-d90d:~/lesson4$ env #显示所有当前环境变量
SHELL=/bin/bash
HISTSIZE=1000
HISTTIMEFORMAT=%F %T ltx 
PWD=/home/ltx/lesson4
LOGNAME=ltx
XDG_SESSION_TYPE=tty
MOTD_SHOWN=pam
HOME=/home/ltx
LANG=en_US.UTF-8
MY_VAR=my_value  #我们刚才设置的
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.webp=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:
SSH_CONNECTION=59.62.147.11 3278 192.168.0.214 22
LESSCLOSE=/usr/bin/lesspipe %s %s
XDG_SESSION_CLASS=user
TERM=xterm
LESSOPEN=| /usr/bin/lesspipe %s
USER=ltx
DISPLAY=localhost:10.0
SHLVL=1
XDG_SESSION_ID=343
XDG_RUNTIME_DIR=/run/user/1000
SSH_CLIENT=59.62.147.11 3278 22
XDG_DATA_DIRS=/usr/local/share:/usr/share:/var/lib/snapd/desktop
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
SSH_TTY=/dev/pts/0
OLDPWD=/home/ltx
_=/usr/bin/env
ltx@hcss-ecs-d90d:~/lesson4$ unset MY_VAR  #清除之前设置的 MY_VAR 环境变量
ltx@hcss-ecs-d90d:~/lesson4$ env #再次显示所有当前环境变量,发现刚才设置的MY_VAR环境变量清除了
SHELL=/bin/bash
HISTSIZE=1000
HISTTIMEFORMAT=%F %T ltx 
PWD=/home/ltx/lesson4
LOGNAME=ltx
XDG_SESSION_TYPE=tty
MOTD_SHOWN=pam
HOME=/home/ltx
LANG=en_US.UTF-8
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.webp=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:
SSH_CONNECTION=59.62.147.11 3278 192.168.0.214 22
LESSCLOSE=/usr/bin/lesspipe %s %s
XDG_SESSION_CLASS=user
TERM=xterm
LESSOPEN=| /usr/bin/lesspipe %s
USER=ltx
DISPLAY=localhost:10.0
SHLVL=1
XDG_SESSION_ID=343
XDG_RUNTIME_DIR=/run/user/1000
SSH_CLIENT=59.62.147.11 3278 22
XDG_DATA_DIRS=/usr/local/share:/usr/share:/var/lib/snapd/desktop
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
SSH_TTY=/dev/pts/0
OLDPWD=/home/ltx
_=/usr/bin/env

6. 环境变量的组织方式

一、数据结构:键值对与内存布局

  • 核心数据结构

    环境变量通过 KEY=VALUE格式的字符串数组 组织,以NULL指针作为结束标志:

    // C语言中环境表的存储结构
    char *envp[] = { "HOME=/home/ltx", "PATH=/usr/bin:/bin", "LANG=en_US.UTF-8", NULL  // 结束标识
    };
    
    • 内存布局特点
      • 连续内存块存储字符串指针
      • 每个指针指向KEY=VALUE格式的独立字符串
      • 数组末尾以NULL指针标识边界

  • 系统级实现差异

    系统实现方式访问接口
    Linuxchar **environ全局变量extern char**environ
    Windows_environ (CRT库)_getenv()
    嵌入式系统独立Flash扇区(带CRC校验)U-Boot的env save命令

二、进程级管理:继承与作用域控制

1. 进程启动时的传递机制
通过execve()系统调用实现父子进程传递:

// Linux内核系统调用
int execve(const char *filename, char *const argv[],  // 参数表char *const envp[]   // 环境变量表
);
  • 传递流程

2. 作用域控制原理

变量类型作用域生命周期底层机制
环境变量进程及所有子进程进程会话期间通过fork()继承environ指针
Shell变量仅当前Shell进程Shell会话期间存储于Shell进程堆内存

关键区别:环境变量通过export命令将Shell变量写入environ数组

3. 系统级环境变量

系统级环境变量是在系统范围内对所有用户和进程都有效的变量。这些变量通常在系统启动时由初始化脚本设置,存储在/etc/environment/etc/profile/etc/profile.d/等文件中。例如:

  • PATH:指定系统查找可执行文件的路径。

  • LANG:指定系统的语言和区域设置。

4. 用户级环境变量

用户级环境变量是针对特定用户的变量,只对当前用户有效。这些变量通常在用户的 shell 配置文件中设置,如~/.bashrc~/.bash_profile~/.zshrc等。例如:

  • EDITOR:指定用户默认的文本编辑器。

  • SSH_AGENT_PID:指定 ssh-agent 进程的 PID。

5. 进程级环境变量

进程级环境变量是进程在运行时继承自父进程的变量。当一个进程创建子进程时,子进程会继承父进程的环境变量。子进程可以修改这些变量,但这些修改只对子进程及其后续创建的子进程有效,不会影响父进程或其他进程。例如:

  • 在终端中运行export MY_VAR="my_value"后,当前终端及其子进程都会继承这个变量。

6. 作用域

环境变量的作用域决定了它们的可见性和影响范围:

  • 全局作用域:系统级环境变量,对所有用户和进程有效。

  • 用户作用域:用户级环境变量,只对当前用户及其进程有效。

  • 进程作用域:进程级环境变量,只对当前进程及其子进程有效。

三、通过代码如何获取环境变量

在这之前我们要在再来了解一个命令行参数,前文中我们知道了main函数可以有参数,那最多有几个参数呢?

其实main函数最多有三个命令行参数,第三个参数就是envp

1. 参数定义与类型

envp(environment pointers)

  • 类型char *envp[]char **envp

  • 功能:接收环境变量表(Environment Variables Table),存储当前进程运行所需的环境配置信息。

  • 示例

    int main(int argc, char *argv[], char *envp[]) {for (int i = 0; envp[i] != NULL; i++) {printf("envp[%d]: %s\n", i, envp[i]);}return 0;
    }

运行结果:

ltx@hcss-ecs-d90d:~/lesson4$ make
gcc -o code code.c
ltx@hcss-ecs-d90d:~/lesson4$ ./code
envp[0]: SHELL=/bin/bash
envp[1]: HISTSIZE=1000
envp[2]: HISTTIMEFORMAT=%F %T ltx 
envp[3]: PWD=/home/ltx/lesson4
envp[4]: LOGNAME=ltx
envp[5]: XDG_SESSION_TYPE=tty
envp[6]: MOTD_SHOWN=pam
envp[7]: HOME=/home/ltx
envp[8]: LANG=en_US.UTF-8
envp[9]: LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.webp=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:
envp[10]: SSH_CONNECTION=59.62.147.11 3278 192.168.0.214 22
envp[11]: LESSCLOSE=/usr/bin/lesspipe %s %s
envp[12]: XDG_SESSION_CLASS=user
envp[13]: TERM=xterm
envp[14]: LESSOPEN=| /usr/bin/lesspipe %s
envp[15]: USER=ltx
envp[16]: DISPLAY=localhost:10.0
envp[17]: SHLVL=1
envp[18]: XDG_SESSION_ID=343
envp[19]: XDG_RUNTIME_DIR=/run/user/1000
envp[20]: SSH_CLIENT=59.62.147.11 3278 22
envp[21]: XDG_DATA_DIRS=/usr/local/share:/usr/share:/var/lib/snapd/desktop
envp[22]: PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
envp[23]: DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
envp[24]: SSH_TTY=/dev/pts/0
envp[25]: OLDPWD=/home/ltx
envp[26]: _=./code

2. 与全局变量environ的关系

  • 等价性envp与全局变量extern char **environ指向同一内存地址,两种访问方式等价:

    // 方式1:通过main参数
    int main(int argc, char *argv[], char *envp[]) {for (int i=0; envp[i]!=NULL; i++) printf("%s\n", envp[i]);
    }// 方式2:通过全局变量
    #include <stdio.h>
    extern char **environ;
    int main() {for (int i=0; environ[i]!=NULL; i++) printf("%s\n", environ[i]);
    }
    
  • 实现差异environ由C运行时库(如glibc)定义,envp由操作系统内核通过execve()系统调用传递

四、技术原理:操作系统如何传递环境变量

1. 进程创建时的传递机制

步骤说明底层实现
1. Shell预处理解析用户命令,合并继承的环境变量与临时设置(如VAR=value commandBash通过env_hash表管理变量
2. 调用execve()Shell通过系统调用加载程序,传递环境表:
int execve(path, argv, envp)
Linux内核复制envp到新进程栈顶
3. 程序启动初始化C运行时库(crt0)从栈中读取envp,初始化全局变量environcrt0.o代码片段:environ = __envp;
4. 传递给main()main声明包含envp参数,则将其地址压栈x86_64调用约定:rdi=argc, rsi=argv, rdx=envp (什么是Python,它的用途是什么?-腾讯云开发者社区-腾讯云)

2. 环境变量表的继承规则

  • 默认继承:子进程自动继承父进程环境变量表(如从Shell启动的程序继承PATHHOME等)。
  • 动态修改:父进程可通过exec族函数指定新环境表(如execle("/bin/ls", "ls", NULL, new_envp)),覆盖默认继承

五、拓展

程序入口点 _start:从操作系统到用户代码的桥梁

一、_start 的本质与定位
  1. 程序入口点的真实身份

    • 传统认知中,main 函数被视为程序的起点,但实际执行流程中,_start 才是操作系统加载程序后执行的第一条指令。这一机制由操作系统与编译器共同约定,确保程序初始化逻辑的统一性
    • 技术定义_start 是一个由汇编实现的符号(Symbol),通常由链接器(如 GNU ld)通过默认链接脚本指定。其内存地址被写入可执行文件的ELF头部e_entry字段),操作系统内核加载程序时直接跳转至该地址
  2. 与 main 函数的层级关系

    关键结论main 仅是用户代码的语法入口,而 _start 是操作系统层面的实际入口点

    _start 与 main 的关系

    • _startmain 的前置调用_start 先于 main 执行,完成必要的初始化工作后才调用 main

    • main_start 的一部分:从逻辑上讲,main 是程序逻辑的起点,而 _start 是程序执行的真正起点。


二、_start 的底层实现机制
  1. 初始化操作的核心步骤
    _start 函数需完成硬件环境的基础配置,具体包括:

    • 栈指针(SP)设置:为函数调用栈分配内存,通常指向进程地址空间的高地址区域(如 Linux 用户栈起始于 0x7fffffff0000
    • 寄存器清零:清除通用寄存器中的随机值,避免干扰后续逻辑(如 x86 的 xor ebp, ebp
    • 参数传递:从内核传递的栈中提取 argc 和 argv,为调用 __libc_start_main 做准备:
      ; x86_64 架构示例(AT&T语法)
      _start:xor %rbp, %rbp          ; 清空帧指针mov (%rsp), %rdi        ; argc → rdilea 8(%rsp), %rsi       ; argv → rsicall __libc_start_main  ; 调用C库初始化函数
      
  2. 跳转至C运行时库
    __libc_start_main(Glibc实现)是 _start 的核心调用对象,其职责包括:

    功能实现细节影响范围
    全局变量初始化执行 .data 段赋值与 .bss 段清零静态存储期变量
    C++全局对象构造调用 _init() 和 __do_global_ctors_auxC++跨平台兼容性
    线程局部存储(TLS)设置线程局部变量模板多线程程序基础
    安全机制启动栈保护(Stack Guard)与地址随机化(ASLR)校验防御内存攻击
    环境变量处理解析 envp 并设置 environ 全局变量进程环境配置

    完成初始化后,该函数最终调用 main(argc, argv, envp),并将返回值传递给 exit()


6. 通过系统调用获取或设置环境变量

getenv 函数

getenv 是一个标准库函数,用于获取环境变量的值。

函数原型

#include <stdlib.h>
char *getenv(const char *name);

参数

  • name:要获取值的环境变量的名称。

返回值

  • 如果环境变量存在,则返回一个指向其值的字符串指针。

  • 如果环境变量不存在,则返回 NULL

示例代码

#include <stdio.h>
#include <stdlib.h>int main() {char *path = getenv("PATH");if (path != NULL) {printf("PATH: %s\n", path);} else {printf("PATH environment variable not found.\n");}char *home = getenv("HOME");if (home != NULL) {printf("HOME: %s\n", home);} else {printf("HOME environment variable not found.\n");}char *nonexistent = getenv("NONEXISTENT_VAR");if (nonexistent == NULL) {printf("NONEXISTENT_VAR not found.\n");}return 0;
}

输出示例

PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
HOME: /home/ltx
NONEXISTENT_VAR not found.

putenv 函数

putenv 是一个标准库函数,用于设置或修改环境变量。

函数原型

#include <stdlib.h>
int putenv(char *string);

参数

  • string:一个形如 "NAME=VALUE" 的字符串,表示要设置的环境变量名称和值。

返回值

  • 如果成功,则返回 0

  • 如果失败,则返回非零值。

示例代码

#include <stdio.h>
#include <stdlib.h>int main() {// 设置新的环境变量char *new_var = "MY_VAR=my_value";if (putenv(new_var) == 0) {printf("Set MY_VAR successfully.\n");} else {perror("Failed to set MY_VAR");}// 修改现有环境变量char *new_path = "PATH=/new/path";if (putenv(new_path) == 0) {printf("Modified PATH successfully.\n");} else {perror("Failed to modify PATH");}// 获取并打印环境变量char *my_var = getenv("MY_VAR");if (my_var != NULL) {printf("MY_VAR: %s\n", my_var);}char *path = getenv("PATH");if (path != NULL) {printf("PATH: %s\n", path);}return 0;
}

输出示例

Set MY_VAR successfully.
Modified PATH successfully.
MY_VAR: my_value
PATH: /new/path

注意事项

  1. putenv 的参数

    • 参数字符串 string 必须是形如 "NAME=VALUE" 的格式。

    • 如果 NAME 已经存在,则其值会被更新为 VALUE

    • 如果 NAME 不存在,则会创建新的环境变量。

  2. 内存管理

    • putenv 会将参数字符串 string 保存在环境变量中,因此该字符串必须在调用后保持有效。通常,建议使用静态分配或动态分配的内存来存储参数字符串。

  3. 线程安全性

    • getenvputenv 在多线程环境中是线程安全的,但需要注意在修改环境变量时的并发问题。

  4. 替代函数

    • 在一些系统中,还有其他函数如 setenvunsetenv,它们提供了更灵活的接口来设置和删除环境变量。这些函数的使用方式与 putenv 类似,但提供了更明确的参数分离。

总结

  • getenv:用于获取环境变量的值。

  • putenv:用于设置或修改环境变量。

这两个函数是操作环境变量的标准库函数,提供了简单易用的接口。在实际应用中,可以根据需要使用这些函数来获取和设置环境变量,以适应不同的运行环境和需求。


文章转载自:
http://canna.tmizpp.cn
http://battalion.tmizpp.cn
http://blae.tmizpp.cn
http://aviatic.tmizpp.cn
http://beaux.tmizpp.cn
http://chevrotain.tmizpp.cn
http://anastomose.tmizpp.cn
http://cacography.tmizpp.cn
http://acetylate.tmizpp.cn
http://beerburst.tmizpp.cn
http://chromatolysis.tmizpp.cn
http://allnighter.tmizpp.cn
http://acidulous.tmizpp.cn
http://apostle.tmizpp.cn
http://abn.tmizpp.cn
http://ampleness.tmizpp.cn
http://acidness.tmizpp.cn
http://abherent.tmizpp.cn
http://brandish.tmizpp.cn
http://cephalocide.tmizpp.cn
http://amendment.tmizpp.cn
http://campaign.tmizpp.cn
http://atmologist.tmizpp.cn
http://bicentenary.tmizpp.cn
http://anagrammatic.tmizpp.cn
http://bedlamp.tmizpp.cn
http://centavo.tmizpp.cn
http://brythonic.tmizpp.cn
http://bungler.tmizpp.cn
http://btw.tmizpp.cn
http://www.dtcms.com/a/281082.html

相关文章:

  • gitee某个分支合并到gitlab目标分支
  • 微信小程序未登录状态下的导航拦截有哪些方法可以实现
  • AI大模型应用架构演进:从LLM基础到Agent协作的范式转移
  • GBase 8a 与 Spring Boot + MyBatis 整合实战:从环境搭建到CRUD操作
  • 扩展:操作系统之高性能网络计算
  • 使用 mongoimport 导入本地 JSON 文件到 MongoDB 及数据查看指南
  • 微信小程序入门实例_____从零开始 开发一个每天记账的微信小程序
  • Rust语言
  • Isaac Sim仿真赋能机器人工作流,推动具身智能在机器人领域研究
  • 深入解析:磁盘级文件与内存级(被打开)文件的本质区别与联系
  • MySQL锁机制与SQL优化详解
  • Vue 中 effectScope() 的全面解析与实战应用
  • 虚拟机删除操作
  • lanch4j将jar转成exe
  • 文心4.5开源背后的战略棋局:百度为何选择All in开放?
  • Django基础(二)———URL与映射
  • 10 款游戏设计工具深度解析,打造卓越游戏项目
  • 在Autodl服务器中使用VNC建立图形界面
  • MySQL查询今天、昨天、上周、近30天、去年等的数据的方法
  • [锂电池]锂电池入门指南
  • Android 多语言适配(I18n)
  • 逻辑回归案例
  • Prompt提示工程
  • just thinking for vocabulary
  • Visual Prompt Tuning核心思路讲解(个人总结)
  • 《大数据技术原理与应用》实验报告七 熟悉 Spark 初级编程实践
  • 物联网系统中MQTT设备数据的保存方法
  • 了解 Android 内存使用情况 - Google I/O 大会演讲
  • ethers.js-8-bigNmber和callstatic模拟
  • 【Android】日志的使用