【Linux我做主】细说环境变量

【Linux我做主】细说环境变量

Linux环境变量

Linux环境变量

github地址

有梦想的电信狗

前言

​ 环境变量是操作系统为我们维护的一组 key=value 格式的键值对,它们像“隐形助手”一样,悄悄地决定了程序的运行行为、指令的查找路径以及用户的工作环境

​ 你是否好奇为什么在终端中输入 ls 就能列出文件,而运行自己写的程序却要加上 ./?你是否遇到过不同用户登录后,默认目录和权限有所不同?这一切的背后,其实都离不开环境变量的支持。

​ 本文将带你深入理解环境变量的本质、常见环境变量的作用、如何查看与修改环境变量,以及它们与命令行参数、本地变量、内建命令之间的联系。通过丰富的动图演示和详尽的代码解析,让你从操作层面到系统原理都能全面掌握 Linux 环境变量的精髓。

1. 基本概念

​ 在 Linux 操作系统原理中,环境变量(Environment Variables) 是一类在 操作系统层面定义的全局变量,用于配置操作系统行为和影响进程运行环境。它们通常以 键=值 的形式存在,作用范围可以是用户会话、进程,也可以被继承到子进程。

  • 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数
  • 如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找
  • 环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性

环境变量的本质

  • 本质上是 以字符串形式存储在内存中的一组键值对
  • 每个运行中的进程都维护着一份自己的环境变量表,初始化来源是其父进程

2. 认识常见的环境变量

PATH

  • PATH:Linux系统的指令搜索路径
    • 为什么我们输入系统的指令(ls pwd)运行时不需要加./,而运行我们自己写的程序运行时需要加./
在这里插入图片描述

系统当中,针对于指令的搜索,Linux系统会为我们提供一个环境变量PATH

  • 环境变量PATH是系统开机就已经在shell中存在的

查看PATH

查看PATH的内容:

我们查看环境变量,需要借助echo指令。

  • echo PATH:直接输出PATHPATH默认会被当成字符串处理
  • 查看PATH中的内容需要使用$echo $PATH

查看任何环境变量的值都需要在环境变量名前加上$,一般使用命令echo $envName

# 查看环境变量的内容echo$PATH# $表示取环境变量的内容
在这里插入图片描述

修改PATH

PATH的内容是一系列路径,路径与路径之前用:分隔

在这里插入图片描述
  • 输入lsshell会在这些被冒号分隔开的路径中依次查找ls程序
    • 找到的话,执行。因为ls处于路径/usr/bin路径下,路径/usr/bin位于环境变量PATH中,所以系统可以找到直接执行
  • 输入我们自己的程序mycmd,该程序不在PATH中包含的各个路径下,mycmd程序所处的路径也不在环境变量PATH中,因此找不到,最终提示command not found。这个查找工作一般都是shell来完成的

那么这就意味着,我们自己编写的程序,想要像指令一样不加路径直接运行,有两种方法

  1. 将我们的自己的程序放入PATH中存在的路径中
    • 以程序mycmd为例,只需要将其移动到环境变量PATH中的目录即可
    • 使用mv mycmd /usr/bin命令,常见系统的指令通常位于/usr/bin目录下
  2. 将我们自己的程序所处的路径加入到PATH环境变量中

接下来我们演示第二种方法,并演示PATH的修改

在这里插入图片描述
  • 环境变量PATH访问和修改方法
# 查看环境变量的内容echo$PATH# $表示取环境变量的内容# 修改PATH=$PATH:newContent # PATH=newPath # 错误行为 会把PATH中之前的内容覆盖掉

修改环境变量使用等号=,这里的等号可以类比为赋值,赋值会把PATH中之前的内容覆盖掉。我们需要将指定的路径追加到PATH,因此指令为:PATH=$PATH:newPath,这里的行为依然是覆盖,但是$PATH保证了我们取出了PATH中已有的内容,并在后面加上:newPATH,保证了修改是追加新路径

  • 通过这种方式修改的PATH是一种内存级别的PATH,在shell中保存
  • 如果我们错误的修改了PATH,只需关闭XShell后重新登陆shell
  • shell没有启动时,PATH在系统的配置文件中保存。shell启动时,环境变量从系统的配置文件中加载到内存
    • which指令搜索时,也是在环境变量PATH中搜索的

HOME

  • HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
    • 为什么我们在首次登录Linux时,默认所处的目录是自己的家目录呢?
    • 为什么默认所处的路径/home/userName
在这里插入图片描述

​ 原因是,当我们登录时,shell会识别当前登录的用户名,给当前用户填充$HOME环境变量,填充的值为/home/用户名

当我们使用Xshell远程登录Linux时,xshell会为我们分配命令行解释器,命令行解释器会执行类似cd $HOME的命令,因此初次登录时,处于当前用户的家目录

SHELL

  • SHELL当前终端使用的Shell程序,它的值通常是/bin/bash
    • 如何查看当前系统中使用的是哪一个shell呢?
    • 只需要查看环境变量SHELL中的值,命令为:echo $SHELL
# 查看当前系统中正在使用的shellecho$SHELL
在这里插入图片描述
  • 经验证,默认使用的shell一般是/bin/bash

其他常见环境变量

​ 除了以上环境变量,系统中还有很多其他的环境变量。

环境变量如此多,我们进行查看呢?我们可以使用env命令

  • env查看到当前的进程和bash进程从系统中继承下来的所有环境变量
在这里插入图片描述

PWD与OLDPWD

图中的标记对一些常见的环境变量进行了解释,还有一些不同的Linux发行版会显示的其他环境变量

  • 环境变量PWD:记录当前进程所处的工作路径
  • 环境变量OLDPWD:会记录当前进程所处的工作路径的上个路径
    • 我们的cd - 命令会被解析为cd $OLDPWD,因此可以跳转到上次所处的路径中

LOGNAME与USER

在这里插入图片描述
  • 可以看到,当我们在不同用户之间切换时,env获取到的环境变量值中的LOGNAME与USER也都在同步变化。
在这里插入图片描述

SSH_TTY

这里对环境便令SSH_TTY简单提一下。SSH_TTY表示当前终端所使用的字符设备文件。当前SSH_TTY的值为/dev/pts/0

我们再开启一个终端,向该文件中追加内容时,另一个终端中便会显示我们追加的内容,如下所示

在这里插入图片描述

由环境变量理解权限

使用系统调用获取环境变量

在这里插入图片描述

​ 我们可以通过系统调用接口getenv()来获取环境变量,需要给该函数传入字符串格式的环境变量名该函数返回环境变量值,格式为char*风格的字符串,如下所示

在这里插入图片描述

理解权限

  • 我们Linux的权限理解部分,同一个文件,不同的用户在使用时,拥有不同的操作权限
    • 那么系统想要给我们限制权限,首先要获取到我们是谁。
    • 操作系统可以通过系统调用来获取当前用户是谁
  • 不同的用户执行相同的代码时,环境变量获取到的值就不一样,因此可以操作系统可以辨识出当前使用用户的身份
#include<stdio.h>#include<stdlib.h>#include<string.h>#include<unistd.h>// 环境变量,可以让程序获取到当前系统中的用户是谁intmain(){char who[32];strcpy(who,getenv("USER"));if(strcmp(who,"root")==0){printf("你是root用户, 可以做任何事情\n");}else{printf("你是普通用户, 受到权限约束\n");}return0;}
在这里插入图片描述
  • 有了环境变量,程序内部便可以获取环境变量,从而获知当前是哪个用户正在执行指令的
有了环境变量的存在,系统就有了获知当前是哪个用户正在执行指令的能力,获取后就可以将文件属性中的拥有者、所属组和文件所对应的权限进行比对,进而判定当前用户有无特定的权限

3. 总结什么是环境变量

经过以上对环境变量的熟悉和使用,我们可以得出环境变量的最终概念

环境变量是操作系统提供的一组name=value形式的变量,不同的环境变量有不同的用途,通常具有全局属性

环境变量的本质

  • 本质上是 以字符串形式存储在内存中的一组键值对
  • 每个运行中的进程都维护着一份自己的环境变量表,初始化来源是其父进程。
功能示例变量说明
系统路径PATH指定可执行程序查找路径
当前用户信息USER, HOME当前登录用户和主目录
默认语言LANG控制系统语言和字符集
编辑器EDITOR系统默认文本编辑器
库路径LD_LIBRARY_PATH指定动态库搜索路径
shell 类型SHELL当前 shell 程序路径

3. 命令行参数和环境变量的全局性

命令行参数

什么是命令行参数?

  • 命令行参数:是指在命令行界面运行程序时,跟随在程序名称后的额外输出参数,用于控制程序的运行或传递数据

我们C/C++main函数是可以有参数的

intmain(int argc,char* argv[]){return0;}
  • 其中:
    • argc是一个整数,argv是一个字符串数组(指针数组)
    • argv字符串数组的大小由argc决定

main函数也是函数,因此main函数可以调用其他函数,同时也可以被其他函数调用C/C++程序运行起来时,int argcchar* argv[]这两个参数会被调用方进行传参。

  • 用户层面上main函数是被第一个调用的函数
  • 系统层面上:第一个调用的函数是Startup()函数或CRTStartup()函数,该函数会调用main函数,给main函数传参

使用以下代码查看char* argv[]中的内容:

intmain(int argc,char* argv[]){int i =0;for(; i < argc;++i){printf("argv[%d]->%s\n", i, argv[i]);}return0;}
在这里插入图片描述

可以看到char* argv中的内容,就是我们输入的指令和附带的选项

  • argv[0]./mycmd
  • argv[i]后面附带的不同选项

为什么会这样呢?

这是因为,我们在**bash中输入的指令,本质上都是字符串,bash会把他们以空格为分隔符,分割成子字符串,分割成多少个字符串,就初始化argc为多少**

打散后 , argv指针数组中,每个空间,存放的都是相应字符串的地址。最后一个空间存放的是空指针NULL

  • 在系统层面上,被打散的字符串的地址存入char* argv[]中并初始化argcshell命令行解释器,会把这两个参数传递给程序的main函数。
  • 既然char* argv[]的最后一个位置的值为NULL,那么我们遍历argv[]就有了新的方式:
    • for循环的第二个位置表示循环终止条件,当argv[i]的值为NULL时,循环结束,也就完成了对argv[]的遍历
在这里插入图片描述
intmain(int argc,char* argv[]){int i =0;for(; argv[i];++i){printf("argv[%d]->%s\n", i, argv[i]);}return0;}

以上就是命令行参数。以空格为分隔符。将输入的指令分割的过程,就叫做命令行解析

为什么要有main函数要有命令行参数?

  • 我们Linux中的指令都是C语言写的
    • main函数的命令行参数为指令、工具、软件提供命令行选项的支持
    • 调用同一个程序时,使用不同的选项,可以完成不同的功能

main函数的第三个参数

main函数不只有argc和argv两个参数,还可以带第三个参数,如下:

intmain(int argc,char* argv[],char* env[]){return0;}
  • 这里的第三个参数char* env[]表示的是从父进程继承过来的环境变量,其内容和env命令输出的内容一模一样
    • 使用以下代码验证
// C/C++ 程序 两张表 main函数的第三个参数// 1. 命令行参数 向量表 2. 环境变量 向量表 这两张表的结构一模一样intmain(int argc,char* argv[],char* env[]){int i =0;// argv 和 env 的最后一个元素存储的是NULL,遍历到结尾时,条件自动为假,所以退出循环for(; env[i];++i){printf("env[%d]->%s\n", i, env[i]);}return0;}
在这里插入图片描述
  • 综上:通过参数和char* env[]查看的环境变量和env命令查看到的环境变量一模一样
    • char* argv[]char* env[]的结构一模一样,且最后一个元素都是NULL指针
  • 不能简单的认为程序启动时,就是简单的将程序加载到内存。

而是程序在启动时,Startup函数会调用main函数,给main函数传递参数,传递两张表。命令行参数表和环境变量表

环境变量具有全局属性

​ 我们在命令行下所运行的所有进程,都是bash的子进程。bash在启动的时候,会从操作系统的配置文件中读取环境变量信息。子进程会继承父进程交给我的环境变量。子进程可以通过main函数的第三个参数char* env[]访问环境变量。

  • 而子进程再创建子进程,环境变量表就被无穷尽的传递下去了,因此说,环境变量具有全局性

环境变量也是进程的数据,进程具有独立性,父子进程fork之后的代码共享,数据,读时共享,写时拷贝

  • 一个进程还没有创建子进程时,如果已经创建好了环境变量,再创建子进程,就会被子进程继承下去
  • 环境变量的继承有两种方式
    • main函数传参
    • 直接继承

4. 环境变量可以被子进程继承

如何验证环境变量可以被子进程继承呢?

我们可以在系统的环境变量中加入一个我们自己的环境变量

增加环境变量命令

export [name=value]

# env可以查看全部的环境变量# echo $PATH 等可以查看特定环境变量的内容# 那么如何增加环境变量# 增加该环境变量 MY_VALUE=123456exportMY_VALUE=123456
  • export增加环境变量后,env命令中就多了我们添加的环境变量
在这里插入图片描述
  • export增加环境变量后,我们运行的bash的子进程./mycmd中,获取到的环境变量也有我们新增加的环境变量
在这里插入图片描述

这是因为:环境变量可以被子进程继承

  • 发源进程只有一个,是bash,因此**bash及其的所有子进程构成的进程树,拥有相同的环境变量**

如果今天我想让我的所有进程都遵守一套规则,我可以把这套规则放在bash的环境变量中,这样所有的子进程都会有相同的环境变量

系统中的权限,每条指令都应该遵守,这些功能的实现就和环境变量有关

取消环境变量

# 取消我们刚刚新增的环境变量 MY_VALUEunset MY_VALUE 

bash进程取消环境变量MY_VALUE后,之后的子进程就也没有相应的环境变量了,读者可以自行验证

  • 另外,编译器在编译时,会对main函数的命令行参数进行检查,会进行条件编译。如果main函数有参数,会传入相应个数的参数。没有参数,就不传入参数

5. 本地变量与内建命令

本地变量

与环境变量对应的还有本地变量。

什么是本地变量?本地变量就是我们直接在shell中定义的变量

# 直接在bash命令行中定义a=1b=2c=3

本地变量只会在本bash内部有效,不会被子进程继承

set命令可以显示系统中所有的变量。包括环境变量和本地变量

在这里插入图片描述

本地变量不能被子进程继承

  • 我们用本地变量MYVAL来测试,以下程序查看MYVAL的值
intmain(int argc,char* argv[],char* env[]){printf("MYVAL: %s\n",getenv("MYVAL")); return0;}
在这里插入图片描述

内建命令

  • 观察以下输出,思考问题
在这里插入图片描述
  • 前文提到,bash命令行中执行的程序,都是bash的子进程。
  • 前文提到,本地变量只在本bash内部有效,不会被子进程继承
  • 那么,echo $MY_val命令,echo也是Bash的子进程,./mycmd也是bash的子进程,无法获取到本地变量。echo也是bash的子进程,为什么能取到本地变量的?

我们纠正之前的结论:命令行中所启动的指令,不一定全都要创建子进程

我们的命令要被分成两类

  • 常规命令:执行时,bash通过创建子进程执行
  • 内建命令:执行时,bash不创建子进程,而是bash自己亲自执行的。类似于bash调用了自己写的或者系统提供的函数。已知常见的内建命令如下:
    • echoecho是由bash执行的,bash内部有一个函数叫echoecho $MY_val时,echo获取到bash内部的本地变量输出出来就行了
    • cd:每个进程都有自己的当前工作目录,如果cd命令在执行时创建子进程,那么cd改变的将是子进程的工作目录,而不会改变主进程的工作目录

结合系统调用体会内建命令cd

在这里插入图片描述

​ bash也是一个程序,其内部在实现时,读取到用户输入cd时,bash并不会创建子进程,而是直接调用系统调用chdir改变bash程序当前的路径。

我们可以用如下代码进行验证:

由于我们执行mycmd时,bash会创建子进程,因此我们无法直接观察到bash的工作路径改变,我们可以观察/proc目录下进程的cwd目录

// 验证 cd 命令intmain(int argc,char* argv[]){printf("before change:\n");sleep(40);if(argc ==2){chdir(argv[1]);}printf("change end:\n");sleep(20);return0;}
  • sleep()是为了给我们输入指令查看进程的cwd留出时间
在这里插入图片描述

通过C语言提供的外部变量获取环境变量

在这里插入图片描述
// 通过C语言提供的 外部变量 environ 获取环境变量intmain(){int i =0;externchar** environ;for(; environ[i];++i){printf("%s\n", environ[i]);}return0;}
在这里插入图片描述
在这里插入图片描述

6. 结语

​ 至此,我们已经系统地学习了 Linux 环境变量的方方面面:它们的结构、用途、修改方式、继承机制,以及它们在程序启动和系统权限管理中的关键角色。从 PATHHOME从命令行参数到环境变量表,从本地变量的局部性到子进程的继承性,环境变量贯穿了整个 Linux 系统的运行脉络

​ 希望这篇文章不仅帮助你掌握了环境变量的使用技巧,更让你对 Linux 操作系统背后的逻辑有了更深的理解。


以上就是本文的所有内容了,如果觉得文章对你有帮助,欢迎 点赞⭐收藏 支持!如有疑问或建议,请在评论区留言交流,我们一起进步

分享到此结束啦
一键三连,好运连连!
你的每一次互动,都是对作者最大的鼓励!征程尚未结束,让我们在广阔的世界里继续前行! 🚀

Read more

【前端实战】构建 Vue 全局错误处理体系,实现业务与错误的清晰解耦

【前端实战】构建 Vue 全局错误处理体系,实现业务与错误的清晰解耦

目录 【前端实战】构建 Vue 全局错误处理体系,实现业务与错误的清晰解耦 一、为什么要做全局错误处理? 1、将业务逻辑与错误处理解耦 2、为监控和埋点提供统一入口 二、Vue 中的基础全局错误处理方式 1、Vue 中全局错误处理写法 2、它会捕获哪些错误? 3、它不会捕获哪些错误? 4、errorHandler 的参数含义 三、全局错误处理的进阶设计 1、定义“可识别的业务错误” 2、在 errorHandler 中做真正的“分类处理” 3、补齐 Promise reject 的捕获能力 4、错误处理的策略化封装 四、结语         作者:watermelo37         ZEEKLOG优质创作者、华为云云享专家、阿里云专家博主、腾讯云“

By Ne0inhk

前端数据库 IndexedDB 详解:构建强大的离线Web应用

前端数据库 IndexedDB 详解:构建强大的离线Web应用 * 引言:为什么需要前端数据库? * IndexedDB核心概念解析 * 1. 数据库(Database) * 2. 对象存储(Object Store) * 3. 索引(Index) * 4. 事务(Transaction) * 5. 游标(Cursor) * 完整代码示例:实现一个联系人管理器 * 1. 初始化数据库 * 2. 添加联系人 * 3. 查询联系人 * 通过ID查询 * 通过索引查询 * 4. 更新联系人 * 5. 删除联系人 * 6. 高级查询:使用游标和范围 * IndexedDB最佳实践 * IndexedDB的浏览器支持情况 * 使用第三方库简化开发 * 常见应用场景 * 总结 引言:为什么需要前端数据库? 在现代Web开发中,我们经常需要处理大量结构化数据。传统的localStorage和sessionStorage虽然简单易用,

By Ne0inhk
Java算法OJ(10)哈希表练习

Java算法OJ(10)哈希表练习

目录 1.前言 2.正文 2.1俩数之和 2.2无重复字符的最长子串 2.3罗马数字转整数 2.4整数转罗马数字 3.小结 1.前言 哈喽大家好吖,今天来分享几道哈希表相关的练习题,操作比较基础但是思想比较重要,另外有许多思路与解法都是学习参照题解中诸位大佬的做法,都很有思路对我们很有启发性,欢迎大家在评论区多多交流,废话不多说让我们直接开始吧。 2.正文 2.1俩数之和 提交链接: 1. 两数之和 - 力扣(LeetCode)https://leetcode.cn/problems/two-sum/description/?envType=problem-list-v2&envId=hash-table 这道题当然无脑遍历肯定能直接解出来,但既然要锻炼哈希表的熟练程度就用HashSet来完成: 先初始化该哈希表。遍历,如果存在一个数组的数,

By Ne0inhk
【贪心算法-第三弹——Leetcode-179.最大数】

【贪心算法-第三弹——Leetcode-179.最大数】

1.题目解析 题目来源 测试用例  2.算法原理  3.实战代码 代码解析  *4.贪心策略的合理性证明(离散数学——全序关系) 完全性 反对称性 传递性  1.题目解析 题目来源 测试用例  2.算法原理  I.由题目我们知道需要返回将数组的所以数字组合形成的一个最大的数字,所以我们可以将整数类型转化为字符串,这样便于运算 II.这里如果使用暴力解法就是从前到后将每个数字高位值更大的排到前面然后不断遍历组合字符串即可,但是这样时间复杂度会很大,不妨使用贪心的思路,这里"贪心"的思路是: III.需要注意的是当数组中全部是0的情况,此时我们理想的返回值就是一个字符"0",但是如果不特殊处理上述逻辑就返回的是类似"00000"这样的情况,显然不行。所以我们在最后要判断字符串的首位元素是否为"0&

By Ne0inhk