Bootstrap

[操作系统] 环境变量详解

环境变量(Environment Variables)是操作系统提供的一种机制,通过变量存储一些全局的配置参数。这些变量能够为程序运行提供必要的环境信息,并允许用户和程序自定义或修改。它们可以帮助程序获取特定的信息或执行某些功能。


命令行参数

参数

在 C 语言中,main 函数实际上是有参数的,其中的两个参数如下:

int main(int argc, char *argv[])
  1. 参数含义
    • argc:表示传入参数的个数,包括程序名。
    • argv:一个字符指针数组,存储传入的参数内容。
      • argv[0] 通常是程序名(如 ./code)。
      • argv[1] 开始是用户输入的参数。
  2. 运行时解析
    • 当用户在命令行中输入 ./code a b c 时:
      • 系统会将程序名和参数分割后传递给 main 函数。
      • argc = 4argv 数组包含:{"./code", "a", "b", "c"}

  1. 程序运行机制
    • Shell(如 Bash)会将输入切割为多个字符串。
    • 系统调用(如 execve)会将这些参数存入进程的 argv 表中(如图中所示的数组结构),供程序读取和使用。
  2. 代码示例
#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;
}

运行效果(假设输入 ./code a b c):

argv[0]: ./code
argv[1]: a
argv[2]: b
argv[3]: c
  1. 用途
    • 用于实现程序的选项功能。例如:
      • ls -a -l:列出文件时显示所有文件,按长格式输出。
      • gcc -o output source.c:指定输出文件名。

作用

**main**的命令行参数是实现程序不同子功能的方法!

已知,所使用的指令就是可执行程序,但是在使用同一个指令的时候往往会附带不同的选项进行使用,得到的结果也不同。

Linux中的指令大部分都是C语言编译的程序,当执行的时候附带的选项,就是命令行参数。bash会将指令分割,存入argv中,程序中会有对不同选项的不同处理代码。所以使用指令的时候附带不同的选项会有不同的结果。命令行参数就是就是指令选项的实现原理。

环境变量:PATH

既然指令是可执行程序,我自己写的代码编译后也是可执行程序,那为什么当要运行的时候可以在任何路径下都可以使用ls,但是当想要执行我自己写的可执行程序的时候一定要在文件所在文件夹执行,并且需要在前面带./呢?

环境变量实际上就是存放内容的变量(K-V结构),不同的环境变量存放的内容起着不同的作用。环境变量中的PATH就对这个情况起着作用。

通过bash查询环境变量的值需要使用echo $(变量名),查询PATH值结果如下:

再查询ls的路径:

可以发现PATH存放着三个路径,每个路径用:分隔,而ls的存放地址就是PATH三个路径中其中一个。

当我们运行一个程序的时候,我们必先要找到它,也就是找到该程序存放的路径。而在Linux中,当直接输入命令行字符串,bash拿到字符串,拆分成命令行参数表,然后通过PATH进行查找,bash就会在PATH的三个路径中查找是否有所执行的程序(指令)。会将所输入的内容与这三个路径进行拼接查询,形成绝对路径,找到后进行执行。

那么对于不在PATH存放的三个路径中的程序,需要执行时就必须要指定路径,否则bash无法辨别输入的程序的路径在哪里。所以在执行自己的程序的时候使用./来指定所执行的程序就是在当前路径下。

理解环境变量

当了解PATH的作用后,该段用来理解环境变量,为什么bash要通过PATH来查询指令,PATHbash有什么关系。延伸至全部环境变量进行理解。

和环境变量相关的命令:

  1. echo: 显⽰某个环境变量值
  2. export: 设置⼀个新的环境变量
  3. env: 显⽰所有环境变量
  4. unset: 清除环境变量
  5. set: 显⽰本地定义的shell变量和环境变量

使用env可以显示出所有环境变量,PATH就在显示的环境变量中。

使用export加入新的环境变量。

后使用unset 变量名即可删除环境变量。

在程序中可以使用getenv("xxx")putenv("xxx")查询指定的环境变量值或者加入新的环境变量。

以上就是关于环境变量的相关命令的简单讲解。

从存储角度理解环境变量

**bash**中有两个表,环境变量表,命令行参数表。

环境变量实际上存储于**bash**中的环境变量表中**。**环境变量表和命令行参数的表结构相同,但是内容不同。环境变量表也是指针数组,存放着char*的指针变量,每一个变量存储字符串形式(以\0结尾)环境变量内容。

环境变量最开始是从哪来?

<font style="color:rgb(6, 6, 7);">Bash</font>在启动时会读取并设置环境变量,并将这些环境变量传递给由Bash启动的任何程序。而读取的内容来自操作系统本身的配置文件。

当尝试通过指令或者程序操作等方法修改环境变量后,在关闭**bash**,重新启动bash后会发现会回复为之前的配置。就是因为每次启动bash读取的环境变量都是从配置文件读取,如果需要保持自定义的环境变量一直生效的话就需要修改配置文件,这样在每次bash启动后都会按照修改后的进行初始化。

如果当Linux中启动了多个bash,比如在XShell中打开了多个页面启动bash,那么也会在每次启动的时候都会进行初始化。

通过修改配置文件进行永久修改:

  • ~/.bash_profile~/.bashrc:用户级环境变量配置文件。
  • /etc/profile/etc/bashrc:全局环境变量配置文件。

修改配置文件后,需使用以下命令使其生效:

source ~/.bashrc

libc中定义了一个全局变量environbash启动的时候会读取并设置环境变量,environ用来将这些环境变量传递。

environ声明为:

extern char **environ;

它指向一个字符指针数组,数组的每个元素都是一个指向以<font style="color:rgb(6, 6, 7);">'\0'</font>结尾的字符串的指针,指向的该指针就是环境表。<font style="color:rgb(6, 6, 7);">environ</font>作为全局变量,可以将当前<font style="color:rgb(6, 6, 7);">bash</font>对应的环境表信息进行传递给子进程。

这也是为什么可以通过程序来得到环境变量,因为通过<font style="color:rgb(6, 6, 7);">bash</font>执行的子进程可以使用<font style="color:rgb(6, 6, 7);">environ</font>取出或者修改环境变量。子进程可以获取环境变量,也就可以确定当前<font style="color:rgb(6, 6, 7);">bash</font>的各种信息,比如通过<font style="color:rgb(6, 6, 7);">HOME</font><font style="color:rgb(6, 6, 7);">USER</font><font style="color:rgb(6, 6, 7);">LOGNAME</font>等信息来进行个性化编程。比如在程序中用<font style="color:rgb(6, 6, 7);">if</font>限制只有当前用户可以执行这种行为,通过<font style="color:rgb(6, 6, 7);">cd ~</font>的时候默认进入的是当前<font style="color:rgb(6, 6, 7);">bash</font><font style="color:rgb(6, 6, 7);">HOME</font>环境变量对应的家目录。

虽然环境变量可以被子进程继承,但是如果子进程尝试修改环境变量时就会发生写时拷贝。默认是<font style="color:rgb(6, 6, 7);">bash</font>和子进程之间使用一个环境表,但是如果发生写时拷贝的话子进程就会独立拥有一份修改过的拷贝。

获取环境变量

使用命令行第三个参数

在C语言中,**<font style="color:rgb(6, 6, 7);">main</font>**函数可以接受三个参数,通常定义为**<font style="color:rgb(6, 6, 7);">int main(int argc, char *argv[], char *env[])</font>**

*env[]实际就是环境表,是在操作系统调用main的时候传递给程序的,作为一个全局变量存储在main栈帧中。可以通过main函数的第三个参数env访问环境变量表:

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

Tip:C语言的入口函数实际上并不是mainmain本身就有三个参数,所以在进入main之前需要选择接收不同数量参数的main函数。

实际上的入口函数是start

start
{
    int ret =0
    int arg_count=0;
    arg_count=3;

    if(arg_count == 0)
        ret = mainO;
    else if(arg_count==2)
        ret = main(argc, argv);
    else
        ret =main(argc, argv, env);
}

使用全局变量environ

libc中定义了一个全局变量environ,它指向环境变量表:

#include <stdio.h>
extern char **environ; // 声明
int main()
{
    for (int i = 0; environ[i]; i++) {
        printf("%s\n", environ[i]);
    }
    return 0;
}

需要注意的是,environ未包含在任何头文件中,因此需要手动声明。

使用库函数getenv

getenv函数可以获取指定的环境变量值:

#include <stdio.h>
#include <stdlib.h>
int main()
{
    printf("%s\n", getenv("PATH"));
    return 0;
}

命令

上文已经对相关指令进行讲解。

理解环境变量特性

环境变量具有全局特性

该部分实际上就是上文所述的environ使得环境变量可以继承给子进程,只要不修改,就会在当前bash下具备全局特性。

本地变量

bash中记录着两套变量:环境变量、本地变量。

通过bash也可以直接进行变量的创建和修改等操作。

bash本身就是解释器,要进行脚本语言的解释,像python解释器一样。但也就像在一个函数创建变量一样,这样创建的变量属于局部变量,作为bash的本地变量。作为局部变量,只会在当前进程内,不会被像环境变量一样被子进程继承。

内建命令(built-in command)

为什么使用export i就可以导入bash呢?因为存在着内建命令,bash使用setenv()来替换这个操作。

内建命令是Bash shell的一部分,它们不需要创建新的进程就可以执行。这意味着Bash可以直接调用这些命令的函数,而不需要启动一个新的程序实例。这样做可以提高效率,因为避免了进程创建和销毁的开销。

当你在Bash中执行一个内建命令时,Bash不需要创建一个新的子进程来执行这个命令。相反,Bash可以直接调用内部的函数来执行命令,或者通过系统调用完成所需的操作。

例如Bash处理<font style="color:rgb(6, 6, 7);">export</font>命令。<font style="color:rgb(6, 6, 7);">export</font>是一个内建命令,Bash可以直接执行它,而不需要创建一个新的子进程。这样做的好处是提高了效率,因为Bash可以直接调用内部函数或系统调用来完成命令的执行。

常用环境变量功能整理

  1. **PATH**
    • 用途:指定可执行文件的搜索路径。
    • 说明:PATH环境变量包含了一系列目录路径,操作系统会在这些路径中搜索用户输入的命令对应的可执行文件。如果命令不在PATH中,用户需要指定完整的路径来执行程序。
  2. **HOME**
    • 用途:存储用户的主目录路径。
    • 说明:HOME环境变量通常用于确定用户的主目录,许多程序会使用这个变量来存储或查找用户相关的文件。
  3. **SHELL**
    • 用途:指示用户使用的shell类型。
    • 说明:SHELL环境变量通常包含用户登录时使用的shell程序的路径,如/bin/bash
  4. **LANG****LC_***
    • 用途:设置语言环境和区域设置。
    • 说明:这些变量用于指定系统的语言、字符集和区域设置。例如,LANG可以设置为en_US.UTF-8来使用美国英语和UTF-8字符集。
  5. **TERM**
    • 用途:指示终端类型。
    • 说明:TERM环境变量告诉程序所使用的终端类型,这样程序就可以根据终端的能力来调整自己的行为,例如在文本编辑器中启用颜色显示。
  6. **EDITOR****VISUAL**
    • 用途:指定默认的文本编辑器。
    • 说明:这些变量用于告诉系统默认使用哪个文本编辑器,例如设置EDITOR=vim
  7. **PS1**
    • 用途:定义命令行提示符。
    • 说明:PS1环境变量用于定义Bash shell的命令行提示符,用户可以根据需要自定义提示符的显示内容和格式。
  8. **MAIL**
    • 用途:指示邮件存放的位置。
    • 说明:MAIL环境变量通常用于指定邮件存放的目录,这样shell可以在有新邮件时通知用户。
  9. **LOGNAME**
    • 用途:显示当前登录用户的用户名。
    • 说明:LOGNAME环境变量通常包含当前登录用户的用户名,这个变量可以被shell脚本用来显示用户信息。
  10. **HISTFILE**
    • 用途:指定命令历史记录文件的位置。
    • 说明:HISTFILE环境变量用于指定Bash shell命令历史记录的存储文件,这样用户在下次登录时可以恢复之前的历史记录。
  11. **TZ**
    • 用途:设置时区。
    • 说明:TZ环境变量用于指定系统的时区,例如设置为TZ="America/New_York"
  12. **LD_LIBRARY_PATH**(Linux)或 **DYLD_LIBRARY_PATH**(macOS)
    • 用途:指定动态链接库的搜索路径。
    • 说明:这些变量用于在运行时指定动态链接库(.so文件或.dylib文件)的搜索路径,这样程序可以加载到不在标准库路径中的库。

总结

环境变量是Linux系统中一个不可或缺的机制,通过灵活运用环境变量,可以极大地提高系统的可用性和效率。了解环境变量的基本概念、设置方式以及在程序中的使用方法,对于开发和管理系统具有重要意义。

以上就是关于环境变量的全部讲解。

;