目录

Linux系统-环境变量

【Linux系统】—— 环境变量

1 概念介绍

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

2 命令行参数

2.1 main 函数有参数吗?

  问大家一个问题:main 函数有参数吗?

int main()
{
	return 0;
}

  好像我们平时写的 main函数 都是不用传参的,但不传参就代表不用参数吗?

main函数 至少有两个参数int argcchar *argv[]

int main(int argc, char *argv[])
{
	return 0;
}

main函数 仅仅是我们自己的代码的入口,实际上 main函数 也要被其他函数(Linux中为 _start 函数)调用

2.2 argc 与 argv

  现在我们知道 main函数 有两个参数 argcargv,他们是什么呢?有什么用呢?

argc 是 指针数组 argv 的长度
  我们将 argv 的内容打印出来看看

#include <stdio.h>

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

运行:

https://i-blog.csdnimg.cn/direct/6b6e41e6594b40e5b1822acf9b220800.png#pic_center

  我们发现argv:其实就是将命令以空格作为分隔符,一个一个放入指针数组中

https://i-blog.csdnimg.cn/direct/eec2788a01184d44ba094e8f8262d49f.png

  我们在命令行中写的命令会被当成一个长串字符串,有人帮我们将命令以空格作为分隔符进行切分,被切分后的各个字符即命令行参数。将命令行参数依次放入 argv数组 中,数组中有多少个参数由 argc 指明。同时有效元素放完必须以NULL结尾

2.3 命令如何通过不同参数实现不同子功能

  看到这里,不知道大家有没有想到什么?
  我们平时用的命令,他们本质上就是可执行程序,且一般是由 C语言 写的。我们可以在后面带命令行选项ls -a -l中的 -a-l。我们输入不同的选项,命令可以执行不同的子功能。如果做到的呢?不急,我们先写一段代码

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    if(argc != 2)
    {   
        printf("Usage:%s [-a|-b|-c]\n", argv[0]);
        return 1;
    }   

    const char *arg = argv[1];
    if(strcmp(arg, "-a") == 0)
        printf("这是功能1\n");
    else if(strcmp(arg, "-b") == 0)
        printf("这是功能2\n");
    else if(strcmp(arg, "-c") == 0)
        printf("这是功能3\n");
    else
        printf("Usage:%s [-a|-b|-c]\n", argv[0]);

    return 0;
}

  代码逻辑:如果未带选项或者选项错误,打印提示信息;否则执行对应子功能

https://i-blog.csdnimg.cn/direct/a2946d14c5fd4353851d76b00b9f928d.png#pic_center

  所以:命令行参数是为了让一个程序可以通过选项实现不同的子功能。这也是指令选项的实现原理!
  注:./code 也算一个命令行参数,所以判断语句是 if(argc != 2) 而不是 if(argc != 1)

  谁帮我们进行切分的呢?是shell,Linux 中即 bash

  为什么我们之前不用带命令行参数呢?因为不需要。我们之前写的代码功能就一个,如果我们想让我的代码有不同的子功能就可以带命令行参数

  所以在进程启动时,有一张表 —— argv表,用来支持实现选项功能。

3 环境变量:PATH

  我们发现,执行我们指令的程序需要带 「./」,而执行系统的命令就不需要带 「./」。这是为什么呢?

  无论是我们自己写的程序还是系统的指令,没有本质区别,都是二进制程序。那为什么一个要带路径一个不用呢?
  虽然不知道为什么,但有一点我们清楚:要执行一个程序,必须先找到他!

  正因为要找到它,所以我们运行自己的程序要带「./」表明是在当前路径。我们执行系统命令就不用呢?原因是系统中存在环境变量 PATH,来帮助系统找到目标二进制文件。

  环境变量 PATH 默认情况下在系统中存在,用来标识一串路径,以用来告诉系统去那些路径下找二进制文件

3.1 查看环境变量

  怎么查看PATH环境变量呢?
  
  在系统中,查看所有环境变量
  使用指令:env

https://i-blog.csdnimg.cn/direct/17348dc70a70495587c9b2291d42f868.png#pic_center

  环境变量也是一个变量,环境变量的构成:名称=内容

只想看指定环境变量的内容:
  使用指令:echo $环境变量名称

https://i-blog.csdnimg.cn/direct/3799eb63c50c4c578ff1a0763ed05a4a.png#pic_center

  我们发现 PATH 中的内容都是以绝对路径的形式呈现,以 : 作为分隔符
  当我们输入 ls 等指令,系统不是直接去 /usr/bin 路径去查;而是依次遍历所有路径,最终在 /usr/bin 路径下找到 ls 指令。如果将所有路径遍历完依然没找到,就会报 Command not found。

3.2 将路径添加进 PATH 中

  我们将自己当前的路径添加到 PATH 中,那我们写的 code 可执行也可以不带「./」直接就能被系统找到。
  如何将路径添加进 PATH 中呢?
  PATH 已经是一个变量,我们直接对其赋值

https://i-blog.csdnimg.cn/direct/365acd53d02a4d179a4ed8f05d526575.png#pic_center

  这样我们的指令就能不带 「./」 直接被系统找到了
  
  但是,系统的指令找不到了

https://i-blog.csdnimg.cn/direct/5fe3e74f1a2246479fc499e5f32aba4c.png

  原因是刚刚我们 PATH=/home/gy/lesson13覆盖式的写,将 PATH 中原来的路径覆盖了

https://i-blog.csdnimg.cn/direct/7caebab2bb5941a6b93b33820e3fdacd.png#pic_center

  怎么改回来?不用担心,PATH是一个内存级的变量,想恢复只需重新登录账号即可。
  
  如何追加式的将路径写入PATH呢?如下图:

https://i-blog.csdnimg.cn/direct/8300a806b468495684caa4881abd2b3c.png#pic_center

  当然,如果再退出后登录,PATH 依然恢复默认值

4 环境变量从哪里来

4.1 从存储的角度理解环境变量

  环境变量是由名称和内容组成的,那环境变量是由谁来保存的呢?答案是 bash。

  当我们一旦登录时,系统就会给我们创建一个 bash 进程。bash 从系统中读取所有的环境变量信息,然后在 bash 内部形成一张表 —— 环境变量表。环境变量表和上面的 argv 一样都是指针数组。上述所查看的所有环境变量名称=内容都是一个个字符串,环境变量表指向这一个个字符串

  我们输入的指令如:ls -a -b 首先是被 bash 拿到,bash将 ls -a -b拆分成 ls -a -b,构建一张命令行参数表。后拿着 ls 这个命令,在环境变量表中找到 PATH,再在 PATH 中的各个路径找 ls。所以指令的查找工作是由 bash 自己完成的

  结论:bash 内部会有两张表:命令行参数表和环境变量表

4.2 环境变量最开始从哪来

  我不登录不就没有 bash 了吗?那环境变量最开始从哪来呢?
  环境变量是从系统相关的配置文件来。

bash 最开始启动时,会从配置文件中读取多有环境变量的信息,并在自己内部创建环境变量表

  这个配置文件怎么看?

vim .bash_profile
vim .bashrc
vim /etc/bashrc

https://i-blog.csdnimg.cn/direct/59c656a944ab43b1b4e1d6fd6dc84d99.png#pic_center

  我们登录时,bash 就会被创建。bash 就会读取 .bashrc.bash_profile 配置文件的内容构建出环境变量表。
  如果我们在配置文件中加入自己的路径,那么以后登录,我们的 code 就能不带路径自动被执行。

  如果 Linux 中有 10 个用户登录,系统中就存在 10 个 bash。是不是意味着这 10 个 bash 每一个都要从配置文件中奖将环境变量读到自己的 bash 上下文中?所以每一个 bash 中都存在两张表:命令行参数表和环境变量表
  

5 认识更多环境变量

  每一个环境变量都有其特定的功能,下面我们介绍几个

  • HOME:当前用户的家目录

https://i-blog.csdnimg.cn/direct/72e7dca10d334c4085b0c4693157e903.png#pic_center


  • SHELL:当前用户登录时,用的是哪一个版本的 shell。默认保存的是 bash 的路径(/bin/bash)

https://i-blog.csdnimg.cn/direct/830a05ffac434560b2de0ddb126294b0.png#pic_center


  • USER:当前用户

https://i-blog.csdnimg.cn/direct/b01397055f9141f3afbc9e2bfc668504.png#pic_center


  • HISTSIZE:记录最大历史命令个数

https://i-blog.csdnimg.cn/direct/f00b439d175d4839a9fb51db58133b22.png#pic_center


  • PWD:当前的工作路径

https://i-blog.csdnimg.cn/direct/a8d02030463a4a6b982981c50cefdbbc.png#pic_center


  • OLDPWD:记录上一次所处的工作路径

https://i-blog.csdnimg.cn/direct/fa7b6bfb711447c893448829ae08cc93.png#pic_center

  所以为什么 cd - 可以做最近两次路径的切换?环境变量给我们提供好了

6 对环境变量的操作

前文,我们已经学会了两个获取环境变量的方法

  • env
  • echo $xxx

6.1 导入环境变量

导入环境变量:
  使用指令:export 要导入变量名=要导入变量内容

export MYENV=11223344

https://i-blog.csdnimg.cn/direct/87fcce8794294e1dacc9b9aa92468f44.png#pic_center

  不知大家有没有觉得奇怪
  环境变量是在谁的上下文中的?是 bash。而 export 是命令,是一个由 bash 创建的子进程。进程之间是互不影响的。所以为什么在进程 export 中的操作会影响进程 bash 呢?会改变父进程的环境变量表呢?

  只有一个解释:export 命令是一个内建命令
  内建命令不需要创建子进程,由 bash 自己亲自执行。一般是由 bash 调用函数或者系统调用完成

6.2 删除环境变量

删除环境变量:
  使用指令:unset 环境变量名

unset MYENV

https://i-blog.csdnimg.cn/direct/1b7d98ff198f4e5ab592559fe2a13a84.png#pic_center

6.3 通过代码的方式获得环境变量

6.3.1 main函数参数获取环境变量

  main函数有参数吗?有!
  main函数最多有几个参数?
  3个!

  那还有一个参数是谁呢?
  最后一个是 char *env[]

int main(int argc, char *argv[], char *env[])
{
	return 0;
}

  char *env[]是什么呢?
  char *env[] 是一个指针数组,未来父进程 (如:bash) 可以把环境变量表传给 main 函数,由 char *env[] 接收。环境变量表与命令行参数表的形式是一模一样的,最后都以NULL结尾

  我们写一段代码验证一下

#include <stdio.h>
#include <string.h>

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

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

注:(void)argc;的写法是为了不让编译器报警告。在 gcc 中如果我们定义了变量确不去用他会报警告,将其强转表示使用了 argc 变量,就不会报警告

https://i-blog.csdnimg.cn/direct/59112442e21d4f2796bb9de0f6132f74.png#pic_center

  此时 main函数 就获取了环境变量,这个环境变量是谁的呢?是父进程也就是 bash 的。

  结论:环境变量可以被子进程继承。环境变量在系统中具有全局特性

  注:Linux 中 main 函数是由 _start 函数调用的,_start 会先扫描 main 函数的参数个数,再执行相应 main函数。大致如下:

_start
{
	int ret = 0;
	int arg_count = 0;

	//扫描 main 函数的参数个数
	arg_count = 0/2/3;

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

6.3.2 getenv 函数获取环境变量

  输入指令:man 3 getenv 查看 getenv 函数

https://i-blog.csdnimg.cn/direct/46bfa3e66bfe4fa788e378a7b16879f6.png#pic_center

getenv:

  • 函数功能:获取一个环境变量的内容。获取成功返回内容的其实地址;否则返回NULL

代码实验:

#include <stdio.h>
#include <stdlib.h>

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

    char *value = getenv("PATH");
    if(value == NULL)
        return 1;
    printf("PATH->%s\n", value);
    
    return 0;
}

https://i-blog.csdnimg.cn/direct/752fe7f47bd14b7a8d5b2669ff31dee4.png#pic_center

  如果我只想让我写的程序只能我执行,就算 root 也没门,该怎么设计?
  我们可以运用环境变量。
  整个系统只有一个人知道登录用户是谁:bash。在 bash 中的环境变量表中记录着环境变量 USER,而 bash 可以将环境变量交给子进程,子进程可以根据环境变量进行身份识别

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

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

    char *who = getenv("USER");
    if(who == NULL)
        return 1;
    
    if(strcmp(who, "gy") == 0) 
        printf("这是程序正常执行逻辑\n");
    else  
        printf("只有 gy 能执行\n"); 
        
    return 0;
}

  为什么全局变量要有全局特性呢?
  因为这样子进程就可以将环境变量继承下去,子进程就可以结合环境变量做个性化操作。比如上面指定一个只能直接执行的程序。

6.3.3 全局变量 environ 获取环境变量

  输入指令:man environ 全局变量 environ
  environ 指向环境变量表

https://i-blog.csdnimg.cn/direct/8a557e129baa49f999c21d9f3e04113a.png#pic_center

  为什么 environ 是 char** 类型的,因为environ指向的是环境变量表,而环境变量表本身就是一个指针数组

注:使用 environ 之前,需将其声明

#include <stdio.h>
#include <unistd.h>

extern char **environ;

int main(int agrc, char *agrv[])
{
    (void)agrc;
    (void)agrv;

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

https://i-blog.csdnimg.cn/direct/15f39229e5be420ab6906d26d3d6a62b.png#pic_center

  上述三种获取环境变量的方法中,我们最常用的是第二种,因为法一和法三都是获取整张环境变量表,而法二获取的是单个环境变量的内容。

7 本地变量

先看操作:

https://i-blog.csdnimg.cn/direct/bda902e94fff4c14949e44c67ed0d46d.png#pic_center

  如上图,我们可以直接在命令行中定义变量,我们将这个变量称为本地变量。

  此时我们用 env 查看环境变量,发现并没有找到 i 变量。这也说明我们定义的 i 变量不是环境变量

查看本地变量:
  使用指令:set

set 可以查看本地变量和环境变量

https://i-blog.csdnimg.cn/direct/0b9aec4fc36c4750be086c7ebc9eeecd.png

删除本地变量:
  使用指令:unset 本地变量名

  所以什么是本地变量呢?
  本地变量不会被子进程继承,只会在 bash 内部被使用。

  
  


好啦,本期关于 环境变量 的知识就介绍到这里啦,希望本期博客能对你有所帮助。同时,如果有错误的地方请多多指正,让我们在 Linux 的学习路上一起进步!