【Linux】环境变量

引入

我们在命令行中执行如下命令:

复制代码
xian@VM-8-17-ubuntu:~/lession15$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

系统会输出一段由:隔开的路径。这其实就是我们每天都在用的东西,只是我们并不自知。当我们输入ls,ps,gcc命令时,系统便会在这些路径下去找这些命令的可执行文件,并执行。这就是配置环境变量的好处,我们不用每次输入命令时都带上冗杂的路径,环境变量会告诉系统去哪找用到的命令。

定义

环境变量(Environment Variables)是操作系统在进程启动时传递的一组动态键值对(key=value)例如:PATH=/usr/bin:/bin (key是PATH,值:/usr/bin:/bin ),用于配置进程的运行环境。这些字符串形式的变量被 shell、应用程序和系统服务广泛使用,主要功能包括控制程序行为、指定路径以及标识用户身份等。

常见示例:

  • PATH:指定 shell 查找可执行命令的目录路径
  • HOME:标识当前用户的主目录位置
  • USER:记录当前登录的用户名信息

环境变量的传递机制是通过父进程创建,并经由 fork() 和 execve() 系统调用自动传递给子进程,构成了进程间传递上下文信息的重要通道。

main函数的参数

_start

main函数带参数吗,平时我们并没有去带参数,其实main函数是带参数的,main函数也是会被调用的,平时我们说main函数是程序的入口,其实main函数只是我们写的程序的入口,我们写的程序其实还会在编译时还会经过一系列"包装"添加一些东西才能成为可执行程序;程序的起点在Linux中是_start,_start会调用__libc_start_main从内核中获取参数传给main。

建立一个.c文件,编译;将可执行程序转成汇编语言:

bash 复制代码
 objdump -d -M intel code

在这其中找到_start:

bash 复制代码
0000000000001060 <_start>:
    1060:	f3 0f 1e fa          	endbr64
    1064:	31 ed                	xor    ebp,ebp
    1066:	49 89 d1             	mov    r9,rdx
    1069:	5e                   	pop    rsi
    106a:	48 89 e2             	mov    rdx,rsp
    106d:	48 83 e4 f0          	and    rsp,0xfffffffffffffff0
    1071:	50                   	push   rax
    1072:	54                   	push   rsp
    1073:	45 31 c0             	xor    r8d,r8d
    1076:	31 c9                	xor    ecx,ecx
    1078:	48 8d 3d ca 00 00 00 	lea    rdi,[rip+0xca]        # 1149 <main>
    107f:	ff 15 53 2f 00 00    	call   QWORD PTR [rip+0x2f53]        # 3fd8 <__libc_start_main@GLIBC_2.34>
    1085:	f4                   	hlt
    1086:	66 2e 0f 1f 84 00 00 	cs nop WORD PTR [rax+rax*1+0x0]
    108d:	00 00 00 

我们会发现_start调用了一个叫__libc_start_main的东西,这个 是 glibc 库中提供的启动函数,它的作用就是接收 main 地址作为参数,然后调用 main 函数。从这里就可以看出main函数并不是程序的起点,它也会被调用。

复制代码
//伪代码
 _start() {
    int ret = 0;
    int arg_count = 0;

    // 这里硬编码参数数量为 3,也可以换成实际运行时获取的值
    arg_count = 3;

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

}

命令行参数

事实上,main函数是带参数的。

cpp 复制代码
int main(int argc,char* argv[])
{

       
        return 0;
}

平时我们使用main函数时,我们并没有带参数; 这是因为当我们不主动从命令行传递参数时,操作系统会默认调用 main,并将 argc 设为 1,argv[0] 赋值为程序自身的名字(路径)。

main函数的这两个参数是什么呢?仔细观察我们会发现这两个参数的名字很相似,第二个参数argv是一个字符串指针数组,而argc就是这个指针数组的元素个数。

当我们在命令行ls -n时,就会给ls命令的执行程序中的main函数传入"ls -n"作为参数,bash会将这些命令和选项做分割,最终传给 argv数组中。argv[0]="ls",argv[1]="-n",argv[argc]=NULL。

我们来实践一下:

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



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

        }

        //printf("It's pretty!\n");
        return 0;
}
~                                                                                                                                                
~                                

编译之后运行:

bash 复制代码
xian@VM-8-17-ubuntu:~/lession16$ ./code a b c d
argv[0]:./code
argv[1]:a
argv[2]:b
argv[3]:c
argv[4]:d

系统会通过./code找到当前目录我们的可执行程序code,将我们输入的参数全部传给可执行程序,同时我们输入的东西会被bash做切分。

我们把argv称作命令行参数表,进程启动时,bash就会维护一个命令行参数表,将参数以字符串的形式储存起来,储存在用户空间栈中,不是PCB的一部分。main函数的命令行参数表,就是我们命令选项的实现原理,输入的选项与命令行参数中的作比较,给不同的选项实现不同的功能。

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

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

        }
*/
        

if(strcmp(argv[1], "a") == 0)  
{
    printf("这是功能一\n");
}
if(strcmp(argv[1], "b") == 0)
{
    printf("这是功能二\n");
}

        //printf("It's pretty!\n");
        return 0;
}
~                       
bash 复制代码
xian@VM-8-17-ubuntu:~/lession16$ gcc code.c -o code
xian@VM-8-17-ubuntu:~/lession16$ ./code a
这是功能一
xian@VM-8-17-ubuntu:~/lession16$ ./code b
这是功能二
xian@VM-8-17-ubuntu:~/lession16$ 

PATH

系统的命令和我们自己写的程序本质上都是可执行程序,没有本质上的区别,为什么我们的程序在执行时要加上./而ls却直接写命令就行呢?要执行一个程序,首先要把这个程序找到。

还记得./分别是什么吗,'.'是当前目录;'/'是目录分割符;连在一起意味在当前目录寻找可执行文件。系统自身的命令的实现肯定是不在当前目录的,PATH这个环境变量会告诉bash去哪找要执行的命令的可执行程序。

bash 复制代码
xian@VM-8-17-ubuntu:~/lession16$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
xian@VM-8-17-ubuntu:~/lession16$ 

把我们自己的命令也加到PATH中,就可以不带./直接执行了。

环境变量表(环境变量如何被存储)

当系统启动时,bash便会拿到系统中的环境变量,bash会维护一张环境变量表来存储环境变量。

环境变量表就是一个指针数组,按照下标将环境变量储存到数组中。bash会维护两张表(暂时只学到这两个,当然一共不止两个),环境变量表和命令行参数表,当我们输入

复制代码
ls -l -a

时,bash会先拿到这个命令,然后将命令拆分之后形成一张命令行参数表,然后去PATH中找有没有ls这个命令,如果有命令就执行(新建一个子进程),没有就报错。

修改PATH

将程序路径添加到PATH

复制代码
xian@VM-8-17-ubuntu:~/lession16$ PATH=$PATH:/home/xian/lession16
xian@VM-8-17-ubuntu:~/lession16$ code
xian@VM-8-17-ubuntu:~/lession16$ code a
这是功能一
xian@VM-8-17-ubuntu:~/lession16$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/xian/lession16

把我们的程序目录添加到PATH之后,我们执行我们的可执行程序就不需要在加上./了。注意不要再等号前后加空格。

覆盖PATH

复制代码
xian@VM-8-17-ubuntu:~/lession16$ PATH=/home/xian/lession16
xian@VM-8-17-ubuntu:~/lession16$ code b
这是功能二
xian@VM-8-17-ubuntu:~/lession16$ ls -l
Command 'ls' is available in the following places
 * /bin/ls
 * /usr/bin/ls
The command could not be located because '/bin:/usr/bin' is not included in the PATH environment variable.
ls: command not found
xian@VM-8-17-ubuntu:~/lession16$ 

直接将PATH设置为当前路径,同样的不需要再告诉bash在当前目录下找我们的可执行程序,直接运行即可。但是这样操作的话,由于PATH被修改了,没有了系统命令的路径,bash找不到系统命令了,系统命令就不能使用了。

这两种修改环境变量的方式都只有在当前的终端有效,退出重新登录后,PATH又会刷新。

环境变量的从哪来

bash会维护一个环境变量表,那么最开始的时候bash是从哪里拿到的环境变量呢?

配置文件

切换到家目录,查看目录下的所有文件,我们会发现有一个.bashrc。

复制代码
xian@VM-8-17-ubuntu:~$ cd ~
xian@VM-8-17-ubuntu:~$ ls -al
total 108
drwxr-x--- 15 xian xian  4096 Jan 13 22:40 .
drwxr-xr-x  5 root root  4096 Sep 11 10:39 ..
-rw-------  1 xian xian 11165 Jan 14 20:36 .bash_history
-rw-r--r--  1 xian xian   220 Mar 31  2024 .bash_logout
-rw-r--r--  1 xian xian  3771 Mar 31  2024 .bashrc
drwx------  3 xian xian  4096 Jan  3 22:54 .config
-rw-rw-r--  1 xian xian    47 Sep 11 22:29 .gitconfig
-rw-------  1 xian xian    20 Jan 11 14:39 .lesshst
drwxrwxr-x  5 xian xian  4096 Oct 29 23:29 lession1
drwxrwxr-x  2 xian xian  4096 Nov 11 23:06 lession10
drwxrwxr-x  2 xian xian  4096 Nov  8 16:16 lession1108
drwxrwxr-x  2 xian xian  4096 Dec 13 22:28 lession12
drwxrwxr-x  2 xian xian  4096 Dec 23 22:36 lession13
drwxrwxr-x  2 xian xian  4096 Jan  5 15:30 lession14
drwxrwxr-x  2 xian xian  4096 Jan 11 14:40 lession15
drwxrwxr-x  2 xian xian  4096 Jan 13 22:40 lession16
drwxrwxr-x  2 xian xian  4096 Sep 11 17:18 lession2
drwxrwxr-x  2 xian xian  4096 Nov  5 22:06 lession8
drwxrwxr-x  2 xian xian  4096 Nov  5 22:47 lession9
drwxrwxr-x  3 xian xian  4096 Sep 13 23:39 .local
-rw-r--r--  1 xian xian   807 Mar 31  2024 .profile
-rw-------  1 xian xian 14003 Jan 13 22:40 .viminfo

当我们登录时,bash通过阅读.bashrc和.profile中的内容就能将环境变量配置出来。只要将我们的程序的路径添加到配置文件中,重启也会保存下来。

复制代码
xian@VM-8-17-ubuntu:~$ vim .profile

在.bashrc的末尾加上

复制代码
export PATH="/home/xian/lession16:$PATH"

xian@VM-8-17-ubuntu:~$ echo $PATH
/home/xian/lession16:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
xian@VM-8-17-ubuntu:~$ code a
这是功能一
xian@VM-8-17-ubuntu:~$ 

重启一次终端,我们的程序就可以执行直接执行了。这样或许为我们提供了暂时的便利,但是Linux中的命令都是经过了标准化的发布了流程,经过了无数次测试后才发布的。这样做会有以下风险:

  1. 命令污染风险:Linux系统中的标准命令都经过了严格的标准化发布流程和无数次测试。而我们自己编写的程序可能存在各种未发现的错误。

  2. 安全隐患:如果我们的程序存在安全漏洞,可能会被恶意利用。

  3. 命令冲突:如果我们的程序与系统命令同名,会导致系统标准命令被覆盖。

  4. 维护困难:随着自定义命令增多,PATH环境变量会变得臃肿,增加系统维护难度。

其他环境变量

HOME

HOME是Linux/Unix系统中一个重要的环境变量,它指向当前用户的家目录(主目录)。每个用户登录系统后都会自动设置这个变量,通常路径为/home/用户名

  • 使用示例:

    bash 复制代码
    cd ~  # 波浪线~会被shell自动替换为$HOME的值
    echo $HOME  # 显示当前用户的家目录路径
  • 特殊场景:

    • root用户的家目录通常是/root
    • 在shell脚本中经常用$HOME来引用用户专属目录

HISTSIZE

这个环境变量控制shell历史记录保存的命令数量上限。

  • 典型设置:

    bash 复制代码
    HISTSIZE=1000  # 默认值通常是500或1000
  • 注意事项:

    • 设置过大会占用内存
    • 可通过history命令查看保存的历史命令
    • 修改后需要重新登录或执行source ~/.bashrc生效

LOGNAME和USER

这两个环境变量都表示当前登录的用户名。

  • 区别:

    • LOGNAME是POSIX标准变量
    • USER是BSD风格变量
    • 在大多数现代系统中两者值相同

OLDPWD

这个环境变量保存了上一个工作目录的路径。

  • 典型用法:

    bash 复制代码
    cd -  # 切换到上一个目录,相当于cd $OLDPWD
  • 特点:

    • 由shell自动维护
    • 执行cd命令时会自动更新
    • 可用于编写目录切换相关的shell脚本

注意:所有这些环境变量都可以通过envprintenv命令查看当前设置的值。

获取环境变量的方法

export

export可以添加环境变量:

复制代码
export PETX=12345689

使用env查看环境变量,我们会发现环境变量中确实多了一个PETX。

env

使用命令env我们可以查看系统中的所有环境变量。

bash 复制代码
xian@VM-8-17-ubuntu:~/lession16$ env
SHELL=/bin/bash
PWD=/home/xian/lession16
LOGNAME=xian
XDG_SESSION_TYPE=tty
HOME=/home/xian
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=00:tw=30;424;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.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:*.t1;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:*.ra31:*.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:*.avif5:*.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:*.1;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:=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=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:*.1;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:*.0;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:*~=00;90:*#=00;90:*.bak=00;90:*.crdownload=00;90:*.dpkg-dist=0*.dpkg-new=00;90:*.dpkg-old=00;90:*.dpkg-tmp=00;90:*.old=00;90:*.orig=00;90:*.part=00;90:*.rej=00;90:*.rpmnew=00;90:*.rpmorig=00;90:*.rpmsav90:*.swp=00;90:*.tmp=00;90:*.ucf-dist=00;90:*.ucf-new=00;90:*.ucf-old=00;90:
PROMPT_COMMAND=history -a; history -a; 
SSH_CONNECTION=222.173.124.254 63265 10.2.8.17 22
LESSCLOSE=/usr/bin/lesspipe %s %s
XDG_SESSION_CLASS=user
TERM=xterm
LESSOPEN=| /usr/bin/lesspipe %s
USER=xian
DISPLAY=localhost:10.0
SHLVL=2
XDG_SESSION_ID=242454
XDG_RUNTIME_DIR=/run/user/0
SSH_CLIENT=222.173.124.254 63265 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/0/bus
MAIL=/var/mail/xian
SSH_TTY=/dev/pts/1
OLDPWD=/home/xian
_=/usr/bin/env
xian@VM-8-17-ubuntu:~/lession16$ 

unset

通过unset我们可以删除环境变量:

复制代码
xian@VM-8-17-ubuntu:~/lession16$ unset PETX
xian@VM-8-17-ubuntu:~/lession16$ 

getenv

getenv是一个c语言库函数,用于在程序运行时获取操作系统的环境变量值。函数原型:

c 复制代码
char *getenv(const char *name);
 

通过这个系统调用我们可以实现一个只有自己才能调用的程序。

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

// 带环境变量参数的 main 函数
int main(int argc, char *argv[], char *env[]) {
    (void)argc;  // 未使用参数,避免编译警告
    (void)argv;  // 未使用参数,避免编译警告
    (void)env;   // 未使用参数,避免编译警告

    const char *who = getenv("USER");
    if (who == NULL)
        return 1;

    if (strcmp(who, "xian") == 0) {
        printf("这是程序的正常执行逻辑!\n");
    } else {
        printf("Only xian can do!\n");
    }

    return 0;
}        

xian@VM-8-17-ubuntu:~/lession16$ vim code1.c
xian@VM-8-17-ubuntu:~/lession16$ gcc code1.c -o code1
xian@VM-8-17-ubuntu:~/lession16$ ./code1
这是程序的正常执行逻辑!
xian@VM-8-17-ubuntu:~/lession16$ 

env(environ)就是环境变量表。

环境变量的特性

环境变量是可以被子进程继承的,而我们创建的所有进程都是bash的子进程,所有环境变量是有全局的特性的。

本地变量

在Bash命令行中可以直接定义变量,这类变量称为本地变量:

bash 复制代码
xian@VM-8-17-ubuntu:~/lession16$ i=1
xian@VM-8-17-ubuntu:~/lession16$ echo $i
1
xian@VM-8-17-ubuntu:~/lession16$

本地变量仅在当前终端会话中有效。使用set命令可以查看所有本地变量和环境变量。通过export命令可以将本地变量转换为环境变量:

bash 复制代码
xian@VM-8-17-ubuntu:~/lession16$ export i

内建命令

通常情况下,我们执行的命令都是作为进程运行的,且这些进程都是bash进程的子进程。然而,内建命令的执行方式不同:它们不会创建新的子进程,而是由bash直接调用相应的函数或系统调用来完成。

当我们覆盖PATH时,这些命令依然能够执行,因为它们就不在PATH中,它们本身就是shell的一部分。pwd就是内建命令。

相关推荐
Mr. Cao code2 小时前
Docker文件数据卷实战:挂载与优化
运维·docker·容器
Zoey的笔记本2 小时前
「软件开发缺陷管理工具」的闭环追踪设计与自动化集成架构
java·服务器·前端
愈努力俞幸运2 小时前
f12网络教程 客户端 服务端 服务器前端 后端
运维·服务器
MediaTea2 小时前
Python OOP 设计思想 13:封装服务于演化
linux·服务器·前端·数据库·python
xinxinhenmeihao2 小时前
使用长效代理是否存在安全风险?长效代理适合哪些应用场景?
服务器·网络·安全
未定义.2212 小时前
第3篇:UI自动化核心操作:输入、点击、弹窗、下拉框全场景实战
运维·python·ui·自动化·jenkins·集成测试·pytest
释怀不想释怀2 小时前
Zabbix(安装模式)
运维·云原生·zabbix
linweidong2 小时前
AUTOSAR如何自动化生成BSW、RTE、AP模块并进行一致性校验?
运维·实时互动·自动化
大佐不会说日语~2 小时前
Docker部署旧版本系统MySQL5.7+乱码问题解决方案
运维·docker·容器