Linux 环境变量 与 命令行参数

什么是环境变量

  • 从结构上来看,环境变量就是操作系统维护的一组:key-value 的键值对。

不知道你是否有一个疑问:为什么我们写代码编译链接 形成的可执行程序要运行起来需要带路径呢?Linux 内置的命令也是可执行程序,为什么这些命令就不用带路径呢?

  • 我们都知道,Linux 的系统指令是放在 /usr/bin 目录下的。/usr/bin 是系统命令的默认搜索路径,所以执行系统的指令不需要带路径!

  • 也就是说如果我们自己写的可执行程序也想不加路径直接用名称来运行,我们只要将可执行文件移动到 /usr/bin 目录下就可以啦!

    c 复制代码
    #include<stdio.h>
    
    int main()
    {
    	printf("hello environment\n");
    
    	return 0;
    }

    mv test /usr/bin/__test:将形成的 test 可执行文件移动到 /usr/bin 目录,并将文件重命名为 __test,那么这个 __test 程序,就能够不加路径直接运行啦!

命令行获取环境变量

除了将可执行文件,放到系统指令的默认搜索路径,还有没有其他的办法呢?因为将可执行文件放到系统命令的默认搜索路径是不优雅的行为!我要是能将可执行文件所在的路径设置为系统命令的搜索路径,那也一样能做到嘛!

PATH 环境变量

事实上,系统命令的默认搜索路径可不止 /usr/bin 一个,可是有好多呢。PATH 环境变量里面全都是呢!那么怎么查看 PATH 环境变量中的内容呢?echo PATH 行嘛?不行的哦!直接 echo PATH 会被解析成字符串的,要打印 PATH 的内容,需要加上 $ 符号!

我们看到,PATH 的内容有如下特征:

  • 由很多的路径组成。
  • 路径与路径之间由冒号隔开。

PATH 不是系统命令的默认搜索路径嘛!那么我们怎么加入自己的目录呢?修改 PATH 内容的本质就是对 PATH 重新赋值!

那我们来看看将我们写的可执行文件所在的目录添加到 PATH 环境变量需要

  • 首先我们获可执行文件所在的目录:pwd

  • 然后就是将这个目录添加到 PATH 环境变量中。思考🤔:PATH=/root/cppcpp/cpp/12_6_cpp_2 这么写是不是正确的呢?你这么做了,发现我们写的可执行文件也能直接运行,但是,原 PATH 中的内容就不见了!想要新增目录,可以这么写:

    bash 复制代码
    PATH=$PATH:/root/cppcpp/cpp/12_6_cpp_2

    可以看到可执行文件所在的目录被添加到了 PATH 环境变量中:
    直接运行可执行程序:__test

    我们重新启动 xshell ,然后打印 PATH 环境变量中的内容,发现我们刚才添加的目录消失不见了!这是为什么呢?

  • 这是因为,我们每次启动 xshell 操作系统会在 PATH 环境变量的配置文件中加载,给 PATH 环境变量赋值。这就意味着,如果我们不修改 PATH 环境变量的配置文件,再每次 xshell 重新启动的时候,我们都要重新添加目录!这就非常麻烦!

我们想要一劳永逸的话就要修改 PATH 环境变量的配置文件。

  • 当前用户环境变量的配置文件

    • cd ~:进入当前用户的家目录。
    • ls -al:查看家目录下的所有文件。
  • .bashrc.bash_profile 文件修改

    这里面有两个文件:.bashrc.bash_profile。在这两个文件中,我们能够为当前用户配置属于自己的 PATH 环境变量,也就是说配置这两个文件只会对当前用户生效!

    我们可以看到这两个文件中都有一段脚本代码(红框框的部分)。

    • .bash_profile 文件中表示,如果家目录下存在 .bashrc 文件,就会执行这个文件。
    • 同理,在 .bashrc 文件中表示,如果 /etc/bashrc 文件存在那么就执行这个文件。

    可以得出结论:环境变量的配置文件有很多,配置文件之间还存在调用关系。

    想要为当前用户配置环境变量,修改这两个文件都可以(我们以修改 .bashrc 文件为例),有两种书写的方式:

    • `export PATH="$PATH:/root/cpp"
    • PATH=$PATH:/root/cppcpp
      export PATH

      修改 .bashrc 文件之后,我们可以使用:source .bashrc 刷新一下配置文件,我们就能看到我们通过修改配置文件添加的环境变量了!

事实上,xshell 启动之后,操作系统加载环境变量的配置文件的顺序是:

bash 复制代码
/etc/profile ----> ~/.bash_profile ----> ~/.bashrc ----> /etc/bashrc

这是 centos 的加载顺序,具体的 linux 操作系统可能会有差异,但是原理都是差不多的!

现在我们就能够理解,为什么我们在命令行窗口输入一个命令,如果不存在这个命令,报错的提示是:command not found 了!

  • 这证明,执行一个命令,他会在 PATH 环境变量中搜索,是否存在这个变量!如果不存在,就是因为找不到嘛!
  • 当然,你也可以指定路径运行指令就不用在 PATH 环境变量中搜索了!

HOME 环境变量

这个环境变量的值就是登陆用户的家目录!

  • root 用户:

  • 普通用户:

    我们登陆 xshell 的时候,为啥默认就是在登录用户的家目录呢?

    事实上,登录 xshell 的时候,xshell 会识别到登录的用户是谁,然后填充 HOME 这个环境变量。登录成功之后,xshell 分配 bash,执行类似 cd 命令,进入登录用户的家目录!

SHELL

这个环境变量可以查看使用的是哪一个 shell

查看所有的环境变量

bash 复制代码
env

env 命令可以查看 bash 进程从系统继承下来的环境变量。

可以看到这里面就有几个是我们刚才讲到的,这里在补充几个:

  • HOSTNAME:主机名字。我们在命令行中看到的命令行提示符的一部分就有主机名,他就是根据 HOSTNAME 环境变量获取的,然后打印到显示器上!

  • USER:这个环境变量是登录用户的用户名,在命令行提示符中就会用到!

  • PWD:这个环境变量保存的是当前进程所处的目录,bash 不也是个进程嘛!

  • LOGNAME:这个和 USER 可以认为是一样的,你可以通过切换用户来验证。

  • OLDPWD: 这个环境变量保存的是当前进程上一次处于那个目录,这就是 cd - 的原理啦!

  • HITSIZE:我们上下键可以切换我们历史输入的命令,其实历史输入的命令是被保存到 ~/.bash_history 中的!而这个 HITSIZE 就是能够保存历史输入命令的最大条数!

函数获取环境变量

我们要是想要在我们自己写的程序中获取环境变量怎么办呢?

我们来看一个函数吧:char* getenv(const char* name) 记得包含头文件:#include<stdlib.h>

通过传入环境变量的 key 获取他的 value

我们可以写一个代码获取 PATH 环境变量

c 复制代码
#include<stdio.h>
#include<stdlib.h>
int main()
{
    printf("PATH=%s\n", getenv("PATH"));
    return 0;
}

main 函数参数获取环境变量

在使用 main 函数的参数获取环境变量之前,我们先来学习一个知识点:命令行参数

命令行参数

什么是命令行参数

我们之前其实就一直在用命令行参数,就是不知道叫这个东东罢了!

这里的 -a -l 选项就是命令行参数啦!

命令行参数是干什么的,有什么用?

命令行参数就是用来给某些函数传参的,这些系统指令是不是也是一个一个的进程!那么他的本质就是可执行程序,比如同一个 ls 命令,带上不同的选项,就能实现不同的效果,不就是通过参数来控制的嘛!带上不同的选项本质就是传入不同的参数!

到后面我们会自己来写一个命令行解释器的程序,那时候你就更能理解了!

大家都知道 main 函数其实也是有参数的,并且 C 语言的入口函数其实也不是 main 函数,而是一个叫做 startup 的函数。这就意味着,main 函数也是被其他函数调用的,那么就能够做到为 main 函数传参!

argc && argv

我们先来看看 main 函数的其中两个参数吧:

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

int main(int argc, char* argv[])
{
   return 0;
}
  • argc:整形,表示 argv 数组中的元素个数 。
  • argv:一个 char* 类型的数组。

好的,不管这么多,我们直接打印这个数组来看看是个什么东西:

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

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

可以看到,argv 中的内容与运行可执行程序时是否带选项,带的选项数量都有关联:

你已经看出来了是吧:就是将运行可执行程序的方式和各种参数当做一个字符串,然后对字符串做分割,将运行可执行程序的方式和各种选项分别当做一个字符串填充到 argv 这个字符串数组中!

这样做有什么用呢

我们就可以根据可执行程序加上不同的选项,表现出不同的功能啦:

我们根据 argv 的内容和数量做出判断,不同的选项对应不同的功能!

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

int main(int argc, char* argv[])
{
    if(argc != 2 || (strcmp(argv[1], "--help") == 0))
    {
        printf("%s [-a|-l|-b]\n", argv[0]);
    }
    else
    {
        if(strcmp(argv[1], "-a") == 0)
        {
            printf("这是功能一\n");
        }
        else if(strcmp(argv[1], "-l") == 0)
        {
            printf("这是功能二\n");
        }
        else if(strcmp(argv[1], "-b") == 0)
        {
            printf("这是功能三\n");
        }
        else
        {
            printf("%s [-a|-l|-b]\n", argv[0]);
        }
    }
    return 0;
}

这是不是就跟 ls 命令有点相似啦!

命令行参数表

命令行参数表就是那个 argv 数组哈,我们给可执行程序带的选项都会被写入命令行参数表!命令函数参数表的结构是这个样子的:

这是什么意思呢?就是说这个命令行参数表最后一个有效元素的下一个元素一定是 NULL 这个 NULL 是自动设置的!

那么我们就多了一种打印命令行参数的方式啦:

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

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

环境变量表获取环境变量

main 函数中可不止 argc argv 这两个参数,还有一个参数是:char* env

这个 env 变量和 argv 变量是一样的!指的是结构是一样的,最后一个有效元素的下一个元素会被自动设置为 NULL。我们也可以直接打印这个环境变量表来看看,究竟是怎么个事儿!

我们可以看到也是顺利的将环境变量给打出来了!

你可能就会有一个疑问了:
./test 会启动一个进程,在这个进程没有启动之前是没有环境变量的,但是启动这个进程之后为什么就会有环境变量呢?

  • 我们运行的进程,其实都是 bash 的子进程,bash 本身在启动的时候就会从操作系统的配置文件中加载环境变量!环境变量也是数据,子进程当然会继承父进程的数据啦!换句话说,我们定义的环境变量在所有进程中是都可以被看到的,因此环境变量具有全局属性!

如何验证环境变量是可以被继承的?

首先要验证这个问题,我们需要为我们的 bash 进程添加一个自定义的环境变量!然后在子进程中获取所有的环境变量,看是否会出现我们自定义的环境变量!

那么怎么为 bash 进程添加环境变量嘞?

我们能在命令行直接定义变量嘛?向下面这样:

我们看到,这样定义变量用 echo 命令也是可以打出来的!但是我们在 env 中并没有找到我们定义的这个变量!说明这种方式定义的变量并不是环境变量!直接在命令行定义的变量叫做本地变量。这个问题我们等会再说!

想要为 bash 添加环境变量需要加上一个 export,像下面这个样子:

想要取消一个环境变量可以使用 unset 命令,添加一个环境变量之后,unset 之后就不能查找到这个环境变量啦!

那我们就可以写代码来验证环境变量是可以被继承的啦:

c 复制代码
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>

int main(int argc, char* argv[], char* env[])
{
    pid_t id = forrk();
    if(id == 0)
    {
        printf("I am child process, i have env: MY_VALUE\n", getenv("MY_VALUE"));
    }
    else
    {
        sleep(100);
    }
    return 0;
}

可以看到子进程能够继承 bash 的环境变量!

这里有一个问题,如果在子进程中修改了从 bash 进程继承下来的环境变量,会怎么样呢?

  • 环境变量也是数据,如果子进程修改父子进程共享的数据,那么就会发生写时拷贝,这个知识点在进程的创建一文中详细讲解过哦!

你可以在子进程中调用 setenv 函数,修改或者添加环境变量来验证子进程修改环境变量不会影响父进程的环境变量!

本地变量

刚才讲到过本地变量的概念:在命令行直接定义的变量。

这个本地变量在本 bash 内部有效,不会被子进程继承(验证方法跟验证环境变量能够被子进程继承是一样的,这里就不在演示了!),这是因为本地变量没有必要继承给子进程!


查看本地变量:set,这个命令可以用来查看本地变量和环境变量!

我们定义了一个本地变量:MY_VALUE 在环境边两个中找不到这个变量,但是在 set 中能够找到!

另外本地变量也是可以变成环境变量的,你应该都猜到怎么做了~只需要 export 本地变量就能够将本地变量变成环境变量!

可以看到,将刚才定义的本地变量 MY_VALUE export 之后就能在环境变量中查找到啦!

environ 获取环境变量

在 C 语言中,提供了一个 char** 类型的变量 environ 指向父进程的环境变量表!在使用的时候需要用 extern 声明一下,C 语言的语法嘛!

内建命令与普通命令

看上面的现象,我们定义了一个本地变量,通过 echo 能将本地变量打印出来!

我就有一个问题啦:echo 是一个命令吧,我们之前说过所有的命令都是 bash 的子进程,我们又知道,MY_VALUE 是一个本地变量,不能被子进程继承,那么,echo 命令是如何拿到 MY_VALUE 变量,并把他打印出来的呢?

  • 如果你看过我之前写的文章,你就知道啦,其实并不是所有的命令都是 bash 的子进程,就比如这个 echo 命令,他并不是 bash 的子进程,而是 bash 这个进程自己来执行的!因此 echo 命令既能够打印出来环境变量也能够打印出来本地变量!

我们将那些不用创建子进程,而由 bash 进程来执行的命令称为内建命令。
我们将那些需要创建子进程,有子进程来执行的命令称为普通命令!

我们之前使用的 cd 命令也是一个内建命令,可以使用系统调用 chdir 来实现 cd 命令!

知识点:

  1. 环境变量的概念。
  2. 获取与修改环境变量
    • 命令行
    • 函数
  3. 环境变量全局属性的理解。
  4. 本地变量与环境变量。
  5. 内建命令与普通命令。
相关推荐
云飞云共享云桌面25 分钟前
8位机械工程师如何共享一台图形工作站算力?
linux·服务器·网络
Peter_chq1 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
一坨阿亮2 小时前
Linux 使用中的问题
linux·运维
dsywws3 小时前
Linux学习笔记之vim入门
linux·笔记·学习
幺零九零零4 小时前
【C++】socket套接字编程
linux·服务器·网络·c++
wclass-zhengge4 小时前
Docker篇(Docker Compose)
运维·docker·容器
李启柱4 小时前
项目开发流程规范文档
运维·软件构建·个人开发·设计规范
free5 小时前
netstat中sendq/recvq用于排查发送端发送数据的问题
服务器
小林熬夜学编程5 小时前
【Linux系统编程】第四十一弹---线程深度解析:从地址空间到多线程实践
linux·c语言·开发语言·c++·算法
力姆泰克5 小时前
看电动缸是如何提高农机的自动化水平
大数据·运维·服务器·数据库·人工智能·自动化·1024程序员节