【Linux】环境变量

目录

  • 一、环境变量
    • [1.1 命令行参数](#1.1 命令行参数)
      • [1.1.1 main 函数是可以带参数的](#1.1.1 main 函数是可以带参数的)
      • [1.1.2 为什么要给 main 函数传参?为什么要有命令行参数呢?](#1.1.2 为什么要给 main 函数传参?为什么要有命令行参数呢?)
    • [1.2 有关环境变量的引子](#1.2 有关环境变量的引子)
    • [1.3 常见的环境变量](#1.3 常见的环境变量)
    • [1.4 用代码获取环境变量](#1.4 用代码获取环境变量)
    • [1.5 环境变量表是谁传递的呢?](#1.5 环境变量表是谁传递的呢?)
    • [1.6 和环境变量相关的命令](#1.6 和环境变量相关的命令)
    • [1.7 初识内建命令](#1.7 初识内建命令)

个人主页:矢望

个人专栏:C++LinuxC语言数据结构

一、环境变量

1.1 命令行参数

1.1.1 main 函数是可以带参数的

如上图,main函数可以带参数。它们分别是什么呢?

第二个参数是一个指针数组,而第一个参数是一个int类型,它表示的是指针数组中的元素个数

那我们来打印看看它们存储的是什么吧。

如上图,出现了这样的现象。

其中我们所输入的./code a ...等命令,本质上是一串字符串。将来被shell命令行拿到之后,它会以空格作为分隔符,将这一个字符串转化成若干个子串。argc原则上就是子串的个数。然后会按照argv的下标将这些拆分出的子串一一指向好。

main函数里面的参数通常被称为命令行参数

1.1.2 为什么要给 main 函数传参?为什么要有命令行参数呢?

下面来看这段很"奇怪"的代码:

cpp 复制代码
#include <stdio.h>
#include <string.h>

int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        printf("该命令使用错误!\n");
        printf("正确用法:./code -a|-b|-c|其它\n");

        return 1;
    }

    if(strcmp(argv[1], "-a") == 0)
    {
        printf("你触发了该命令的第一种功能\n");
    }
    else if(strcmp(argv[1], "-b") == 0)
    {
        printf("你触发了该命令的第二种功能\n");
    }
    else if(strcmp(argv[1], "-c") == 0)
    {
        printf("你触发了该命令的第三种功能\n");
    }
    else printf("你触发了该命令的默认功能\n");

    return 0;
}

编译运行

所以命令行参数的本质应用是为了实现一个命令可以根据不同的选项实现不同的子功能这也是Linux中所有命令选项功能的实现方式
:
1、 命令行参数至少有一个,也就是argc >= 1argv[0]一定会有元素,它指向的就是程序名
:
2、 命令的选项是以空格分隔的字符串,一个字符也是字符串
:
3、 一共有argc个,argv[argc - 1]是元素的最后一个,而argv[argc]一定是NULL

1.2 有关环境变量的引子

我们之前讲解过,我们自己编译好的程序必须加./才能跑,这时候系统才能够找到你的程序,而系统默认的指令都安装在usr/bin目录下,系统默认会去这个目录下查找。

那么问题来了,你随便输入一个命令字符串,Linux它怎么知道自己要去usr/bin路径下查找,谁告诉它的?它还会去其它路径下找吗?

首先,Linux中存在所谓的环境变量的东西。这种变量在全系统范围内全局有效,其中有一个环境变量叫做PATH

查看PATH变量里面的内容:echo $PATH

它的内容是以冒号作为分隔符的多串路径组成的字符串,它的本质就是协助Linux进行指令查找的环境变量 ,它会告诉Linux,如果一个用户执行可执行文件没有指明路径的话,就在PATH内容的路径下一个一个路径的查找。

所以如果我们在执行我们的程序时不想带./,可以将我们的可执行程序拷贝到PATH指定的任意一个路径下,但不建议这样做,这样会污染Linux的命令池。今天了解PATH之后,我们还有第二种做法,我们可以把我们的可执行程序的路径添加到PATH里面,这样也就不用加./了。

在添加之前要注意不要直接PATH=你的文件路径,这样PATH里面的路径就只有你的文件的路径了,Linux下的绝大部分指令就都运行不了了,除了一些特殊的命令。你要在之前的PATH之后用冒号作为分割,再加上你的文件路径PATH=$PATH:你的文件路径 。等号两边不要加空格,如PATH = $PATH:XXXBash 会将其解释为:执行名为 PATH 的命令,参数是 =$PATH:XXX

当然,如果你的PATH已经改错了,也不必担心,只需重启你的SSH客户端(Xshell等)就好了,只要你不是永久修改就可以复原。

接下来我们就可以直接执行我们的可执行程序了。

Windows下也有环境变量,其中Path的作用和Linux下相同。当你在Path中添加你想添加的程序的所在路径,你就可以在命令行,在系统的任意位置,直接使用程序名,直接启动该程序

1.3 常见的环境变量

环境变量是系统级变量,有变量名和变量内容,往往具有全局属性

通过env命令就可以查看Linux下所有的环境变量。

其中有一个环境变量HISTSIZE,我们知道,在Linux中按上下键可以调出历史我们输过的命令,而history | wc -l用于统计当前会话中执行过的命令数量。

如上图,虽然可以存储Linux的历史命令,但也不能无止境的存储吧,所以Linux会限制存储的条数,而环境变量HISTSIZE中存储的就是限制条数,可能你的Linux上值是1000,我的上面的限制条数是10000

其中环境变量HOSTNAME中存储着你机器的主机名。

其中环境变量SHELL中存储的是你当前使用的是什么版本的shell

其中OLDPWD中存储的是你上次所在的路径,cd -命令会进入到你上次所处的路径,它就依赖OLDPWD

其中环境变量SSH_TTY中存储的是你当前登录所对应的终端文件。

其中USER中存储的是用户名,PWD中存储的是当前路径,HOME存储的是家目录,LOGNAME存储的是当前登录用户是谁。

1.4 用代码获取环境变量

前面我们已经知道main函数可以带参数了,我们之前带了两个参数,实际上main函数最多可以带三个参数,第三个参数char* env[]。它就是用来接收环境变量数组的。这个数组最终也是以NULL结尾的。

我们可以打印出来看看它接收到了什么。

cpp 复制代码
#include <stdio.h>
#include <string.h>

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

    return 0;
}

编译运行

如上图所示,env获取了这张环境变量表,我还打印出来了,所以之后我们的进程会获得两张表,一张表叫做命令行参数表 ,一张是环境变量表

那么除了这样获取这张表,还有其它方式获取吗? 有的,系统中还存在一个全局变量char** environ,它就指向这张表。

使用这个变量获取,和之前的做法是几乎一模一样的。

cpp 复制代码
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main()
{
    extern char **environ; // 声明一个外部全局变量environ

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

    return 0;
}

最常见最好用的获取环境变量的方式char *getenv(const char *name); ,成功:返回指向环境变量值的指针(字符串),失败:返回 NULL(环境变量不存在)。最佳实践:使用getenv获取

接下来,通过一段代码,让大家了解它的用法。

cpp 复制代码
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

char* c = "hello world";

int main()
{
    char* user = getenv("USER");
    printf("user: %s\n", user);

    if(user == NULL)
    {
        printf("执行失败\n");
        return 1;
    }

    if(strcmp(user, "wuhu") == 0)
    {
        printf("用户合法,可查看机密文件\n");
        printf("机密文件: %s\n", c);
    }
    else if(strcmp(user, "root") == 0)
    {
        printf("超级用户也无法查看\n");
    }

    return 0;
}

编译运行
wuhu

root

如上,这就是getenv的具体用法了,这样就可以写出来与系统相关的代码了。

那么环境变量有什么用呢?就如上面所讲的不同的环境变量会有不同的应用场景。环境变量是程序与操作系统对话的语言,是代码与运行环境之间的桥梁,让程序变得智能、灵活、可配置

1.5 环境变量表是谁传递的呢?

这张表默认是在bash内部的,bash是一个在内存当中的进程,所以命令行参数表和环境变量表,都是在内存中存储的临时的表,它们是内存级的。

我们所启动的程序,我们所执行的命令都是bash进程的子进程,而我们的命令行参数和环境变量表本质上就是bash进程也就是父进程的数据!另外父子进程的代码和数据是可以共享的,父进程fork创建子进程之后,子进程可以看到父进程的数据,这也是为什么就算没有传递char* env[],子进程依旧可以通过getenv获取环境变量表的原因

所以,环境变量具有全局属性!

为什么getenv有效:

cpp 复制代码
// 即使没有 char* env[] 参数
int main() 
{ 
    // getenv() 通过全局变量 environ 工作
    // environ 在进程创建时已从父进程继承
    char* user = getenv("USER");  // 可以工作!
    return 0;
}

总结:是父进程(如bash)传递的

那么bash进程的环境变量又是从哪里来的呢? 是从Linux系统的配置文件来的。bash进程会将配置文件中的内容加载到自己的上下文数据中。 那么这是什么配置文件呢?

主要配置文件:

如上图,在一个用户的家目录下会存在隐藏文件.bashrc、.bash_profilebash会从这些文件中加载环境变量,其中.bash_profile会访问.bashrc,而.bashrc又会访问/etc/bashrc

bash 配置文件加载的层次结构:
共用加载流程
非登录交互式 Shell 加载流程
登录 Shell 加载流程
登录 Shell
非登录 Shell
用户登录/启动 bash
Shell 类型?
执行 /etc/profile
执行 ~/.bash_profile
在 .bash_profile 中:

source ~/.bashrc
直接执行 ~/.bashrc
执行 ~/.bashrc 主体内容
在 .bashrc 中:

source /etc/bashrc
执行 /etc/bashrc 内容
配置加载完成

所以我们可以在.bash_profile中加上一段话,证明确实获取了环境变量,然后再使用env打印出来。

重新登录:

果然是这样,环境变量是从Linux配置文件中获取的。而每个用户的家目录下都有属于自己的隐藏的配置文件,所以使用其它用户登录时,就不会出现这段话,我们使用root登录一下看看。

这是因为每一个用户登录时都会重新启动一个bash,所以每个bash它获取的配置文件不一样。如下图,我们现在有两个用户登录,也就有两个bash进程。

1.6 和环境变量相关的命令

export设置一个新的环境变量unset清除环境变量

接下来我们尝试创建一个环境变量。

如上图,我定义了一个变量,我也可以看到这个变量的内容,但是这个变量没有在环境变量表里,此时它叫做本地变量。那么如何将它变成环境变量呢?

就是使用export命令,当要导入的命令本来就存在时,export直接加变量名就可以将它导入成为环境变量。也就是将它导入进了环境变量表里,此时它就是内存级的。如果导入的变量本身就不存在,那就加变量名和它要包含的内容

如上图,现在它就成为环境变量了。 另外可以使用unset清除它。

那么我们所创建的环境变量能不能被进程获取呢?我们写一份代码获取它们。

cpp 复制代码
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
    printf("TEST_ENV: %s\n", getenv("TEST_ENV"));
    printf("TEST_ENVV: %s\n", getenv("TEST_ENVV"));
    
    return 0;
}

编译运行

所以子进程可以获取父进程的环境变量,环境变量具有全局属性的本质:环境变量可以被子进程继承

以上命令对环境变量所做的操作全部都是内存级的,不会影响配置文件。

另外还记得刚刚遇到的本地变量吗? 接下来带你了解它,我们先把创建的环境变量删除掉。

如上图,本地变量和环境变量的区别:本地变量无法被子进程继承,所以不具备全局性,只有在bash内部可以访问

本地变量的核心作用在于提供脚本内的临时数据存储和局部作用域管理,仅在当前Shell进程或函数内部有效,不会污染全局命名空间或传递给子进程。是编写清晰、安全、可维护Shell脚本的基础工具。

1.7 初识内建命令

此时比较敏锐的兄弟会发现一个问题,既然本地变量无法被子进程继承,那么echo这个子进程是如何知道本地变量里面的内容的?!

如上图,使用echo命令之后,它就是bash进程的子进程,按理来说它不能继承本地变量,但是它继承了,另外当我们清空环境变量PATH之后,我们发现,并不是所有命令都不能运行,有一部分命令还是可以运行的。

这时,你应该注意到,命令和命令之间是不一样的,明确在特定路径下存在二进制文件的命令叫做普通命令内建命令:在Shell内部自己定义,是bash内部的一次函数调用,它不依赖第三方路径

那么我们从上图中看到了echo的二进制文件,为什么内建命令也可以看到明确的二进制文件?

不是同一个东西:你看到的是同名但不同实现的外部命令,某些环境可能禁用内建命令,需要外部版本。内建 echobash 内存中的函数代码,没有独立的文件

总结:
以上就是本期博客分享的全部内容啦!如果觉得文章还不错的话可以三连支持一下,你的支持就是我前进最大的动力!
技术的探索永无止境! 道阻且长,行则将至!后续我会给大家带来更多优质博客内容,欢迎关注我的CSDN账号,我们一同成长!
(~ ̄▽ ̄)~

相关推荐
努力努力再努力wz3 小时前
【Linux网络系列】:JSON+HTTP,用C++手搓一个web计算器服务器!
java·linux·运维·服务器·c语言·数据结构·c++
物理与数学10 小时前
linux 内存分布
linux·linux内核
东城绝神11 小时前
《Linux运维总结:基于ARM64+X86_64架构使用docker-compose一键离线部署MySQL8.0.43 NDB Cluster容器版集群》
linux·运维·mysql·架构·高可用·ndb cluster
creator_Li11 小时前
即时通讯项目--(1)环境搭建
linux·运维·ubuntu
Mr'liu12 小时前
MongoDB 7.0 副本集高可用部署
linux·mongodb
文静小土豆13 小时前
Rocky Linux 二进制 安装K8S-1.35.0高可用集群
linux·运维·kubernetes
暮云星影13 小时前
二、linux系统 应用开发:整体Pipeline流程
linux·arm开发
weixin_4307509315 小时前
OpenMediaVault debian Linux安装配置企业私有网盘(三) 静态ip地址配置
linux·服务器·debian·nas·网络存储系统
4032407315 小时前
[Jetson/Ubuntu 22.04] 解决挂载 exFAT 硬盘报错 “unknown filesystem type“ 及只读权限问题的终极指南
linux·运维·ubuntu