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

初识Linux · 环境变量

目录

前言:

命令行参数

环境变量

直接看现象

更多的环境变量

尝试理解环境变量


前言:

今天介绍的是一个较为陌生的名词,环境变量,在学习环境变量之前,我们需要一定的预备知识,这个预备知识是命令行参数,所以今天我们介绍环境变量的时候,介绍顺序是先介绍命令行参数,然后直接看有关环境变量的现象,到更多的环境变量,最后我们尝试来理解环境变量。

现在进入第一个话题,预备知识——命令行参数。


命令行参数

介绍命令行参数我们从main函数开始,相信不少人是比较疑惑的,这和C语言的main函数有什么关系?还真有,只不过是我们最开始忽略了main函数的参数而已,相信这个时候有人开始有反应了,main函数我们学习的时候确实会有人带参数,只是因为没有方法传参进行调用,也就不了了之了:

  5 int main(int argc,char* argv[])
  6 {
  7                                                                                                                                                                                                               
  8                                                                                                                      
  9   return 0;                                                                                                          
 10 } 

今天的命令行参数就从argc argv开始。

那么先来一段代码:

  5 int main(int argc,char* argv[])
  6 {
  7   for(int i = 0;i < argc;i++)
  8   {
  9     printf("%s\n",argv[i]);                                                                                                                                                                                   
 10   }
 11 
 12   return 0;
 13 }

为什么写这样一段代码呢?我们不妨对参数进行一点分析,int argc说明argc是一个整形,后面的argc是一个char*类型的数组,就代表说这个数组的内容是char*,那么我们应该是可以尝试进行打印的,所以我们写了如下代码,make一下之后,我们看看结果是什么:

结果是我们在命令行参数不管输入多少都会进行打印,并且打印顺序默认是从左到右,即最开始的一定是./test,那么这是为什么呢?

我们再来看一段代码:

  6 int g_val = 100;
  7 int main()
  8 {
  9   int a = 1;
 11   pid_t id = fork();
 12   if(id == 0)
 13   {
 14     printf("a=%d&&g_val=%d\n",a,g_val);
 15   }
 16   return 0;
 17 }  

打印结果:

这个打印结果很简单,这里提问,子进程为什么能打印g_val的值呢

我们都知道全局变量是在main函数栈帧创建之前就创建了的,按道理来说应该是和子进程没有关系才对,那么,子进程能打印这是怎么回事呢?

这里就得出来一个重要结论,父进程的数据,默认能被子进程看到并访问!

我们现在有了两个预备知识:

1 main函数的命令行参数可以使用,命令行参数是我们输入得来的

那么作用是什么?不要忘记我们ls的不同选项了,是不是就可以结合命令行参数,类似于通过下标的方式,来执行不同的功能了?这就是命令行参数要做的事。

2 父进程的数据默认能被子进程看到并访问

第三个知识是,我们平常输入指令的地方,也就是命令行解释器,提问,我们是输入给谁的呢?为什么我们输入了对应的指令,就可以有相应的指令运行呢?

这里,就是命令行解释器,我们平常输入的ls cd等命令,输入了之后,在进程部分我们知道它们实际上也是一个一个的进程,那么它们的父进程是什么?

不管我们写了任何的代码,不管怎么运行,不管我们使用的是任何的指令,不管带了多少个选项,它们本质都是进程,我们打印出来它们的ppid,在ps里面,查对应的ppid,就发现是bash进程。

这个场景是不是很像VS中cin的时候,等待输入?

所以实际上我们的操作都是对bash进程操作的,虽然我们是运行了子进程,但是本质都是给的bash进程。

预备知识3:

3 命令行给的指令等,都是传给bash的


环境变量

3个预备知识已经就位,首先提一个问题,ls运行的时候是进程,我们自己写的代码运行的时候./test也是进程,它们两个都是子进程,凭什么我们自己写的代码就需要./ ,要在当前路径寻找呢?ls等指令就不需要呢?

直接看现象

我们直接引出结论,因为有PATH这个环境变量的存在,我们可以使用echo打印出来看看:

这样是不行的,我们需要$符号:

我们可以看到有多个路径,以冒号为分隔符。

而且可以看到,ls的指令是在bin目录里面,在PATH里面恰好也有,而查找命令就是根据PATH里面有没有对应的路径,如果没有对应的路径,就需要我们自己指定路径了。

这是PATH的用途。

这样更加直观,我们可以指定路径,来执行我们所写的代码。

这是直接看现象部分,Linux中有全局设置,即是告诉编译器,应该去哪里寻找命令。

那么我们如果想要和系统一样,直接执行呢?也就是说我们要修改PATH,或者说将可执行文件放在PATH里面的目录里面,我们先来试试第二种,将我们对应的路径加进去:

使用该指令,将我们的可执行文件所在的文件路径加进去即可,然后就可以执行运行。

这里有一个需要注意的点,test在Linux中是一个命令,如果我们的可执行文件是test的话,就会导致结果容易混淆,这里推荐将可执行文件换个名字:

此时就好了。

这是第二种,我们来试试第一种,直接修改PATH:

此时我们直接修改,然后会发现PATH的路径只有我们赋值的,这是一种覆盖:

但是此时就会发现,bin目录的,或者是说原来的路径里面的指令都使用不了,但是我们的main还是可以使用:

好了,是不是感觉麻烦来了?什么指令都用不了了,此时,有同学会注意到为什么echo还可以使用?

这里就先埋个伏笔,我们通过这个现象,引入一个问题,我们打印出来的Hello world是来源于内存还是磁盘呢?

答案不用多说,自然是内存,那么我们使用指令的时候,bash进程肯定是要先找到指令,那么去哪里找呢?结合我们打印Hello world,我们知道是先将字符串加载到虚拟地址,也就是到了内存里面,同理,bash找指令的时候,是不是应该到虚拟内存去找?

当我们登录到Linux系统的时候,许多配置文件是先加载到了bash进程,那么环境变量是否在配置文件里面呢?当然是,所以我们能看到的PATH不过是内存级的变量,如果我们重启一下,那么我们对PATH的修改就没有用了,因为配置文件我们没有修改。

那么对于配置文件和指令,我们可以有一个结论,因为bash要执行指令的时候,需要找到该指令,那么配置文件就一定比指令先加载到bash进程里面。

既然提到了配置文件,我们不妨看几个:

对于vim来说,viminfo就是它的配置文件之一,所以我们yum的时候,是十分方便的,因为它会帮我们下载对应的配置文件,我们就不用单独配置文件了。

更多的环境变量

我们现在了解了一个环境变量,我们来看看更多的环境变量:

因为图片大小原因,这里截图部分,我们使用指令env即可查出来一张表,环境变量表,里面包好了所有的环境变量,我们每个简单介绍一下:

SHELL变量,外壳程序,可以理解为bash是它实例化的一个对象,HISTSIZE历史大小,表示该机器能存储我们指令的最大个数,我们平时使用指令是可以上翻下翻找我们使用过的指令的,那么查找是有个度,就是这个环境变量,是我们存储指令的最大数目,默认是1000.

可以使用指令history来查看我们写过的指令。HISTTIMEFORMAT历史时间格式,PWD代表当前路径,它是可以变化的,因为我们不管在哪个路径下是用pwd都可以打印当前路径,就是因为它是变化的。HOME不用多少,当前用户的家目录。

带有UTF的环境变量是表示当前的编码是什么,比如这台机器的编码就是UTF-8,下面那个让人头皮发麻的是颜色设置。

还有这些,比如PATH,USER等。

以上就是其他的环境变量。

那么我们能不能自己来点环境变量呢?当然是可以的:

难道这么简单?

这也没有找到啊?

为什么又可以打印呢?

哦~原来是因为HELLO是个本地变量,我们只需要知道一下本地变量就可以了,没啥用这东西,那么我们如何让HELLO变成环境变量呢?

export即可,如何取消我们设置的环境变量呢?

 unset即可。

尝试理解环境变量

我们首先来看一个函数,我们想要通过代码的形式,来看到环境变量,所以我们使用environ,它是一个二级指针,不出意外,指向的是一个char*的数组,那么我们打印看看:

int main()
{
        extern char** environ;
        for(int i = 0;environ[i];i++)
        {
                printf("env[%d]:%s\n",i,environ[i]);
        }
        return 0;
}

欸!这个东西,怎么那么,像我们刚才看到的环境变量?对!它们就是我们刚才看到的环境变量。

那么结合我们的预备知识2,父进程的数据可以被子进程看到,我们知道最开始加载到了bash进程环境变量,可以被子进程打印出来,那么在bash里面是如何对诸多环境变量进行管理的呢?

这里的environ是二级指针的数组,供子进程使用,我们不难猜测,bash进程组织环境变量是靠的一张表,char* env里面存储的就是所有的环境变量,而environ指向的就是env数组,那么就可以通过下标的方式进行打印。

既然数据都共享了,父进程不妨直接给子进程两张表,一张表是命令行参数表,一张表是环境变量表。

此时,我们在命令行解释器输入的-a使用到了命令行参数表,环境变量表我们可以这样:

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

所以引入了main函数的第三个参数,env环境参数表,这里使用env[i]作为判断条件,我们还可以证明一件事,argv[] env[]都是以NULL结尾的。

看一个库里面的函数,叫做getenv,简单的,我们直接实例代码即可:

int main(int argc,char* argv[],char* env[])
{
        char* str = getenv("PATH");
        if(str)
        {
                printf("%s\n",str);
        }
        return 0;
}

简而言之,getenv就是用来查找环境变量的。

所以现在查找环境变量有三种方式,一种是getenv,一种是main函数的参数,一种是environ二级指针。

我们现在进入最后一个话题,为什么我们最开始修改了PATH,但是仍然可以执行echo命令呢?

我们如果export一个环境变量,env里面可以查找到:

但是我们结合父进程的数据能被子进程看到,但是子进程的数据不应该被父进程看到这个点,难道bash执行export不会创建子进程吗?

是的,如果创建了子进程,bash的env表就是不能看到HELLO,所以这种指令是特殊指令,叫做内建命令,是由bash自己来执行的,不是创建了子进程的。

到此,环境变量就结束了!尚未不清楚的点后续更新~


感谢阅读!


http://www.kler.cn/a/312161.html

相关文章:

  • 2-UML概念模型测试
  • HTML(超文本标记语言)
  • UI资源分包 -- 基于Xasset框架代码实例
  • 前端入门一之ES6--面向对象、够着函数和原型、继承、ES5新增方法、函数进阶、严格模式、高阶函数、闭包
  • Java面试要点02 - 自动装箱与拆箱的原理与性能解析
  • Spring——容器:IoC
  • VSCode开发ros程序无法智能提示的解决方法(二)
  • 亲测openEuler 22.03 LTS 安装MySQL 5.7和注意事项
  • Spring 源码解读:实现自定义注解处理器
  • Linux文件IO(二)-文件操作使用详解
  • 论文解读《COMMA: Co-articulated Multi-Modal Learning》
  • mysql 重置密码
  • 部署Prometheus+Grafana批量监控Linux服务器
  • Django框架全面指南
  • 如何解决“json schema validation error ”错误? -- HarmonyOS自学6
  • 鸿蒙环境服务端签名直传文件到OSS
  • HTTP协议详解以及常见的状态码
  • 设计模式之外观设计模式
  • Ubuntu24.04部署docker
  • VD2811A SOP-8封装 可直接替代XB8886G芯片 大电流充放电锂保芯片
  • 智能机巢+无人机:自动化巡检技术详解
  • qt char*与QString互转
  • 巧用联合与枚举:解锁自定义类型的无限潜力
  • 【深度学习|PyTorch】基于 PyTorch 搭建 U-Net 深度学习语义分割模型——附代码及其解释!
  • 【HTTP】方法(method)以及 GET 和 POST 的区别
  • 控制浏览器显示隐藏c++