【linux】linux进程概念(四)(环境变量)超详细版

发布于:2025-05-29 ⋅ 阅读:(26) ⋅ 点赞:(0)

小编个人主页详情<—请点击
小编个人gitee代码仓库<—请点击
linux系列专栏<—请点击
倘若命中无此运,孤身亦可登昆仑,送给屏幕面前的读者朋友们和小编自己!
在这里插入图片描述



前言

【linux】linux进程概念(三)(进程状态,僵尸进程,孤儿进程,进程优先级)——书接上文 详情请点击<——
本文由小编为大家介绍——【linux】linux进程概念(四)(环境变量)


一、基本概念

环境变量一般是指在操作系统中用来指定操作系统运行环境的一些参数,环境变量通常具有某些特殊作用,同时它还具有全局属性

  1. 例如:我们在编写c/c++代码的时候,在进行链接的时候,从来不知道我们所链接的动静态库在哪里,但是依旧可以链接成功,生成可执行程序,原因就是有相关的环境变量帮助编译器进行查找

二、认识常见的几个环境变量

  1. PATH:是linux系统的指令的搜索路径
  2. HOME:指定用户的主工作目录(即用户登录到linux系统中时,默认的目录)
  3. SHELL:当前SHELL,它的值通常是/bin/bash

echo $ 查看某个环境变量

echo $环境变量名

  1. 例如我们要查看PATH这个环境变量,那么就应该使用echo $PATH进行查看,同理HOME,SHELL也是

在这里插入图片描述

env 显示所有环境变量

直接使用env指令可以显示所有环境变量

在这里插入图片描述

/dev/pts/0 字符设备

/dev/pts/0 是字符设备,默认显示字符是显示在当前的字符设备上
在这里插入图片描述

  1. 那么我们复制SSH渠道,在复制的SSH渠道上输入hello是显示在复制的SSH渠道中,那么如果我们想要将其显示在当前原SSH渠道,那么可以使用echo > 重定向到原SSH渠道的字符设备上进行显示
    在这里插入图片描述
HISTSIZE 默认保存的历史指令条数

HISTSIZE是默认保存的历史指令条数
在这里插入图片描述

  1. 我们使用方向键中的^,即上尖括号可以不断查看上一次输入的指令,那么在linux中一定是有专门的文件存储小编输入的上一次指令,并且可以查到很多之前输入的指令,这些指令的默认上限是3000条,即linux会默认保存3000条之前在命令行中输入的指令,这个默认保存指令的条数必须被设置,如果不进行设置,那么例如你使用了10年linux,那么你所有的指令都被保存起来了,试想一下,会多浮夸,同时也没有必要保存那么多条,所以这个环境变量是一定需要的,所以默认的配置是保存3000条
    在这里插入图片描述
OLDPWD 保存上一次所处路径

OLDPWD是将上一次所处路径进行保存,因为 cd - 的作用可以返回上一次路径,所以就一定会将上一次路径进行保存,这个OLDPWD就是用于保存上一次所处路径
在这里插入图片描述

  1. 例如小编原本处于/home/wzx/lesson/lesson14这个路径下,那么使用cd -返回上一次路径,那么就返回了OLDPWD保存的上一次路径/home/wzx/lesson/
    在这里插入图片描述
  2. 那么此时再使用echo查看OLDPWD上一次路径,那么就为小编上一次所处的位置
    在这里插入图片描述
  3. 同样的也可以通过 cd $OLDPATH 的方式达到和 cd - 同样的效果
    在这里插入图片描述

经过查看多个环境变量我们得出,环境变量是系统提供的一组name=value形式的变量,不同的变量有不同的用户,环境变量具有全局属性,其中环境变量具有全局属性后文小编会进行证明

三、测试PATH

PATH是linux系统的指令搜索路径
这些搜索路径通过冒号:进行分隔
在这里插入图片描述

  1. 我们知道指令的本质都是可执行程序,这些指令是被放在特定路径下的可执行程序,例如小编也创建一个可执行程序mycmd.c并且编译好之后进行执行
#include <stdio.h>

int main()
{
  int cnt =5;
  while(cnt--)
  {
    printf("hello %d\n",cnt);
  }

  return 0;
}
  1. 那么接下来小编运行一下这个可执行程序,但是只能通过./mycmd的形式进行运行,直接在bash命令行中输入mycmd却无法运行
  2. 其中这个./的目的是告诉linux我们要执行的指令mycmd对应的可执行程序就在当前路径,执行执行对应的可执行程序的时候不要去PATH环境变量中的路径中去找对应指令的可执行程序了,而是去当前路径下去找可执行程序mycmd去运行
    在这里插入图片描述
  3. 那么接来我们进行对比一下系统中的指令,有些指令相对于我们自己的程序不需要加./就可以被运行起来,那么这是为什么呢?
    在这里插入图片描述
  4. 这其中的原因便是文章开头小编介绍的环境变量PATH的缘故,PATH是linux系统的指令默认搜索路径,即当我们在bash命令行中输入指令的时候,linux会首先在这个PATH中使用冒号:分隔的路径下逐个进行查找这个指令对应的可执行程序,如果找到了这个指令对应的可执行程序,那么linux就会去执行这个指令,如果没有找到那么会进行报错
    在这里插入图片描述
  5. 例如ls这个指令就是特定的被放在了/usr/bin这个路径下,这个/usr/bin路径下就会有放置的ls指令对应的可执行程序,那么linux就会去执行这个可执行程序,我们也可以通过将自己的可执行程序mycmd直接放在这个/usr/bin路径下,那么这样也可以达到和ls一样的效果,即不输入./就可以直接运行,在这个地方小编还有一个其它办法同样可以实现我们自己的程序不输入./就可以直接运行
    在这里插入图片描述
  6. 那么接下来我们可以直接对PATH这个环境变量进行修改,将我们的mycmd可执行程序所在的路径也放在PATH这个环境变量中,这个放置是使用赋值=进行放置,不可以直接使用我们的路径直接将PATH进行覆盖,因为这会导致原PATH的内容也被覆盖掉丢失了(就算真覆盖了也没啥,重新打开xshell登录即可恢复),所以我们应该使用$PATH:路径的方式进行添加我们的路径,$PATH的作用是查看$PATH的内容,由于路径之间是使用冒号:进行分隔,所以当使用&PATH之后就有了原PATH环境变量的内容接下来跟上冒号:跟上我们对应的路径即可
  7. 那么添加完成我们的路径之后,接下来在bash命令行中输入我们的指令mycmd,那么linux就会在PATH环境变量中对应的内容中使用冒号分隔的路径中去找对应指令的可执行程序,那么就会在/home/wzx/lesson/lesson14这个路径下找到我们的可执行程序去执行了,此时就实现了我们自己的程序不加路径./就可以直接运行了
    在这里插入图片描述

四、测试HOME和USER

HOME(环境变量):指定用户的主工作目录(用户登录linux系统时,默认的目录)
USER(环境变量):指定当前登录系统的用户名,即操作系统会根据不同的登录用户去对应分配不同的USER环境变量,即不同的环境变量有不同的用户
cd ~进入家目录,这个家目录的位置和HOME指定用户的主工作目录或默认目录相同,代表当不同用户登录linux系统的时候会根据不同用户去分配不同的环境变量
在这里插入图片描述
在这里插入图片描述

五、getenv 通过系统调用获取环境变量

我们还可通过系统调用获取环境变量
在这里插入图片描述

  1. 使用系统调用接口getenv的时候不要忘记包含头文件#include <stdlib.h>
  2. 输入要获取的环境变量名,进行传参给getenv即可通过系统调用获取环境变量
  1. 那么小编使用如下代码调用系统调用接口getenv获取PATH环境变量的值
#include <stdio.h>
#include <stdlib.h>

int main()
{
  printf("PATH:%s\n",getenv("PATH"));

  return 0;
}

运行结果如下,获取成功
在这里插入图片描述

  1. 那么小编使用如下代码获取当前登录用户
#include <stdio.h>
#include <stdlib.h>

int main()
{
  printf("PATH:%s\n",getenv("USER"));

  return 0;
}

运行结果如下,获取成功
在这里插入图片描述

  1. 那么操作系统就可以通过如下方式区分当前登录用户是root用户还是普通用户,那么也就可以与文件的权限进行结合起来,如果登录用户是普通用户那么受权限限制,如果登录用户是root用户那么不受权限限制
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main()
{
  char who[32];

  strcpy(who,getenv("USER"));

  if(strcmp(who,"root")==0)
  {
    printf("你是root用户,不受权限限制\n");
  }
  else
  {
    printf("你是普通用户,受权限限制\n");
  }

  return 0;
}

运行结果如下
在这里插入图片描述

六、命令行参数

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

int main(int argc,char* argv[])
{

}
  1. 我们最经常使用的main函数,同样是一个函数,main函数是被Startup()或CRTStartup()进行调用,所以main函数可以传参,可以带有额外参数,其中argc是参数的个数,argv是一个指针数组,我们使用命令行参数时,其实在bash中仅仅是输入了使用空格间隔的字符串,那么linux会将空格间隔出来的字符串的首字符的指针传递给这个argv指针数组,那么就可以使用argv+[]的形式获取这些字符串,那么根据获取字符串的不同去显示不同的内容或运行不同的操作,这就是linux中指令的选项的原型,例如mycmd -a使用空格分隔开的参数个数就对应是2
  2. main函数可以传参,即支持命令行参数是为了为指令,命令,软件等提供命令行选项服务

指令的原型

  1. 那么下面程序就是小编使用main函数进行模拟的根据选项参数的不同可以去对应的不同功能,例如ls -a,ls -l的原型也是类似于下面程序
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int arvc,char* argv[])
{
  if(arvc!=2)
  {
    printf("请输入%s -[a|b|c]\n",argv[0]);
    return 0;
  }
  else if(strcmp(argv[1],"-a") == 0)
  {
    printf("%s[1]->%s ",argv[0],argv[1]);
    printf("功能1\n");
  }
  else if(strcmp(argv[1],"-b") == 0)
  {
    printf("%s[1]->%s ",argv[0],argv[1]);
    printf("功能2\n");
  }
  else if(strcmp(argv[1],"-c") == 0)
  {
    printf("%s[1]->%s ",argv[0],argv[1]);
    printf("功能3\n");
  }
  else
  {
    printf("default输入\n");
  }

  return 0;
}

运行结果如下
在这里插入图片描述

打印命令行参数表

  1. 对应的我们将argv这个指针数组叫做向量表,即命令行参数表,其中会默认在表的最后放入一个NULL空,即如果有四个参数,那么表中实际存储五个值,即参数1,参数2,参数3,参数4,NULL空,那么对应表的数组下标就为0到4,这个4代表有4个参数,所以我们就可以使用类似argv+[]的形式去遍历这个命令行参数表,并且可以使用argv[cnt]的形式作为for循环中的判断条件,以四个参数为例,那么表中第五个位置就会放置一个NULL空,那么对于这个NULL空实际上就对应0,所以随着变量cnt从0开始逐渐增加到4,那么当为4的时候对应命令行参数表,即指针数组的位置就为NULL空,那么就为0,此时结束for循环,那么就可以使用这种方式去遍历命令行参数表
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc,char* argv[])
{
  int cnt=0;
  for(;argv[cnt];cnt++)
  {
    printf("%s[%d]->%s\n",argv[0],cnt,argv[cnt]);
  }

  return 0;
}

运行结果如下
在这里插入图片描述
命令行参数表,其底层如图所示
在这里插入图片描述

使用命令行第三个参数获取环境变量,打印环境变量表

  1. 其实main函数还可以有第三个参数,这个参数同样是指针数组类型,也是向量表,这里即环境变量表,那么我们就可以类似于打印命令行参数表的方式进行打印这里的环境变量表
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

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

  return 0;
}
  1. 运行结果如下,我们的环境变量表就被打印出来了

在这里插入图片描述

环境变量表,其底层如图所示
在这里插入图片描述

七、环境变量为什么具有全局属性

理论

我们所运行的进程都是子进程,bash本身在启动的时候,会从操作系统的配置文件中读取环境变量信息,子进程会继承父进程交给子进程的环境变量,那么bash是所有进程的父进程,bash创建的子进程有可能再创建子进程,那么此时的进程关系就形成了一颗多叉树的形状,那么对应由于子进程要继承父进程的环境变量,而bash又是所有进程的父进程,那么bash中的环境变量在全部进程中都会有一份,那么此时环境变量就具有全局属性

如何证明?

在证明前小编需要先引出本地变量,即在bash命令行中定义出的变量是本地变量,本地变量只在本bash内部有效,不会被子进程继承,本地变量不同于环境变量,本地变量不归属环境变量,同样环境变量不归属本地变量

  1. export设置一个新的环境变量,unset清除环境变量
  2. 本地变量可以通过export将自己变为环境变量
  3. 当本地变量使用export变成环境变量之后,可以通过unset将环境变量再变回本地变量
  4. 可以使用set查看环境变量和本地变量
  1. 例如小编在bash命令行中定义一个本地变量MY_VALUE,接着再使用env显示环境变量,通过管道将数据传输给grep进行过滤MY_VALUE,结果什么都不显示,即无法在环境中找到MY_VALUE
  2. 那么接下来小编使用echo $查看MY_VALUE,查看到MY_VALUE对应的值了555
    在这里插入图片描述
  3. 同样的我们还可以使用set去查看这个MY_VALUE本地变量,内容有点多这里小编只截取带有MY_VALUE的部分,可以看出其正好是小编定义的本地变量555
    在这里插入图片描述
  4. 同样的也可以使用set显示环境变量和本地变量,使用管道将数据传输给grep,使用grep过滤本地变量MY_VALUE,那么就可以过滤出本地变量MY_VALUE
    在这里插入图片描述
  5. 我们知道我们运行的程序,也是fork的子进程,继承fork的环境变量,那么小编使用如下代码通过getenv查找我们定义的本地变量MY_VALUE是否也被继承下来了
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc,char* argv[],char* env[])
{
  printf("%s\n",getenv("MY_VALUE"));

  return 0;
}

运行结果如下
在这里插入图片描述

  1. 运行程序,访问出现错误,子进程中的环境变量中并没有MY_VALUE,是由于无法我们定义的本地变量MY_VALUE由于不是环境变量所以没有被继承
  1. 那么我们使用export将bash中的小编定义的本地变量MY_VALUE变成环境变量,这时候就可以使用env | grep MY_VALUE的方式在bash的环境变量中找到MY_VALUE,即此时MY_VALUE是bash的环境变量,那么此时我们运行我们的程序,如果程序它可以找到环境变量MY_VALUE,那么就说明子进程继承了fork的环境变量
    在这里插入图片描述
  2. 看!找到了,那么就说明小编运行的自己的程序mycmd即子进程继承了fork的环境变量,所以所有的进程都会直接或间接的继承fork的环境变量,进程是一个多叉树的结构,那么在全局中,bash及其对应的子进程以及子进程的子进程等都会有一份和fork一样的环境变量,那么就说明环境变量具有全局属性
    在这里插入图片描述
  3. 小编使用export(设置一个新的环境变量)将MY_VALUE直接设置环境变量之后,子进程会继承fork父进程的环境变量MY_VALUE,这时候运行程序,子进程可以找到环境变量MY_VALUE,接下来使用unset(清除环境变量)将环境变量变成本地变量,子进程不会继承fork父进程的本地变量MY_VALUE这时候运行程序,子进程就找不到环境变量MY_VALUE了,和第8点的结论一样,同样可以说明环境变量具有全局属性
    在这里插入图片描述

八、命令分为常规命令和内建命令

内建命令的引出

在这里插入图片描述

  1. 观察上图小编在bash命令行中定义了一个本地变量MY_VALUE,本地变量MY_VALUE不会被子进程继承,那么根据小编之前的理论,指令都是fork的子进程,那么echo也是fork的子进程咯?那么就是说echo这个子进程不会继承本地变量MY_VALUE,那么为什么echo却可以查看本地变量MY_VALUE的值呢?
  2. 究其原因是因为之前小编之前为了大家理解不得不讲bash是通过fork创建子进程去完成任务的,其实事实不是这样的,而是在bash中有两批命令,第一批是常规命令,即bash通过fork创建子进程完成任务,第二批是内建命令,即bash不使用fork创建子进程而是由bash亲自执行任务,类似于bash调用了自己写的或系统提供的函数

模拟内建命令

系统调用接口chdir,可以将当前调用的进程的工作目录更改至指定路径下
在这里插入图片描述
那么由于查看进程的工作目录(当前进程的路径即当前进程所在的目录位置)需要找到其PID和进入对应的proc中进程对应的PID中的cwd需要一定时间,所以小编需要在程序运行起来之后对进程进行休眠,即使用系统调用接口sleep,sleep可以指定让进程休眠n秒
在这里插入图片描述
使用系统调用接口chdir和sleep时不要忘记包头文件 #inclue <unistd.h>

  1. 那么小编就是用命令行参数和chdir模拟cd命令 cd / 即改变当前路径至根目录
#include <stdio.h>
#include <unistd.h>

int main(int argc,char* argv[])
{
  sleep(30);//这里需要给小编查看进程未改变前的工作目录留出时间
  if(argc==2)
  {
    chdir(argv[1]);
  }
  sleep(30);//这里进程已经更改完成工作目录,但是此时进程不能退出结束,一旦退出结束之后
            //小编将无法查看更改后的工作目录了,所以这里需要留出时间给小编用于查看进程
            //更改之后的工作目录

  return 0;
}

运行结果如下,更改成功,将进程的工作目录更改至根目录/了
在这里插入图片描述

  1. 类似的bash中的cd指令也是这样的道理,只不过大概是这种形式,将小编的程序名mycmd替换成cd,bash也是一个进程,bash内部也会由很多语句函数,在bash内部中直接使用strcmp去判断这个argv[0],第一个参数位置是不是cd,如果是cd,那么就去在bash内部去调用chdir系统调用接口将当前bash的工作目录更改到第二个参数(要更改的工作目录)argv[1]上,因为调用cd是这种形式,例如 调用 cd / 这个 cd / 是一个字符串那么就是bash的命令行参数,会将 cd / 通过空格进行划分,cd作为第一个参数,将其字符串的指针放到argv[0]这对应就为要调用的指令,/ 作为第二个参数,将其字符串的指针放到argv[1]这对应就为要更改的工作目录位置,那么这就是cd指令的原理,这种的在bash内部调用bash自己实现的函数或系统实现的函数的命令成为内建命令
  2. 那么echo也是一个内建命令,那么内建命令echo不是bash的子进程,而是在bash内部实现的命令,那么就可以使用bash的本地变量进行显示了,这样小编在bash命令行中定义的本地变量MY_VALUE就可以自然而然的被bash的内建命令echo进行调用显示其对应值999了
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main(int argc,char* argv[])
{
  if(strcmp(argv[0],"cd")==0)
  {
    chdir(argv[1]);
  }

  return 0;
}

九、通过第三方变量environ获取环境变量

我们还可以通过第三方变量environ获取环境变量,libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时,要进行声明
在这里插入图片描述
在这里插入图片描述

  1. 在使用environ的时候的函数原型应该是int main(int argc,char* argv[])的形式
  2. 同时还应该声明二级指针environ
  3. 那么在使用的时候类似的就要类似于原main中第三个参数env去使用env[]去访问的形式,这里的environ也应该使用environ[]的形式进行去访问环境变量
  1. 那么小编使用如下代码使用第三方变量environ去访问获取并且遍历环境变量
#include <stdio.h>
#include <unistd.h>

int main(int argc,char* argv[])
{
  extern char** environ;

  int cnt=0;
  for(;environ[cnt];cnt++)
  {
    printf("[%d]->%s\n",cnt,environ[cnt]);
  }

  return 0;
}
  1. 运行结果如下,遍历成功

在这里插入图片描述


总结

以上就是今天的博客内容啦,希望对读者朋友们有帮助
水滴石穿,坚持就是胜利,读者朋友们可以点个关注
点赞收藏加关注,找到小编不迷路!


网站公告

今日签到

点亮在社区的每一天
去签到