摘要:本文深入讲解Linux环境变量与命令行参数的核心机制。文章从main函数的参数形式入手,详细剖析了argc与argv的含义、命令行参数如何被父进程bash打散并传递给子进程,以及如何通过命令行参数实现程序的不同功能。随后系统介绍了环境变量的基本概念,重点讲解了PATH环境变量的作用与配置方法,并通过实验验证了操作系统在PATH中查找可执行程序的机制。文章还介绍了env、getenv、environ等多种获取环境变量的方法,解释了环境变量表的形成过程及其内存级特性,并通过本地变量与环境变量的对比,阐明了环境变量具有全局属性、可被子进程继承的根本原因。

目录

1.命令行参数

2.环境变量

2.1历史经历

2.2环境变量基本概念

2.3Linux当中的环境变量

2.4其他的环境变量

2.5如何多方法获取环境变量

2.6为什么要有环境变量

2.7环境变量的特点和总结


1.命令行参数

main函数可以有参数吗?可以有几个参数啊?是哪几个?

我们首先创建一个testArg.c,然后写下如下代码:

#include<stdio.h>
int main()
{
    return 0;
}

学过C语言的都知道,这个是最基本的main函数,它可以成功运行:

但是我们可能见过如下的main函数:

#include<stdio.h>
int main(void)
{
    printf("hello\n");
    return 0;
}

这个编译后也能成功运行:

有些教材上也会这样写main函数:

#include<stdio.h>
int main(int argc,char *argv[])
{
    printf("hello\n");
    return 0;
}

如果我们编译运行也是能运行成功的:

那么char *argv[]我们知道,是一个指针数组,char*类型要么指向字符,要么指向字符串。

实际上argv是指向一个一个字符串的指针数组,数组有多少个元素,就是int argc,arg即argument:参数,c即count:数量,argv即argument vector即参数(的指针)数组,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]);
    }
    //printf("hello\n");
    return 0;
}

编译运行后有:

那么我们把指令改为./a.out -a有:

把指令改为./a.out -a -b有:

我们可以这样理解,其实我们在命令行输入的东西,其实都是字符串:./a.out是字符串,-a是字符串。而命令行当中的指令被打散成n个字符串,n是空格数+1,并且把每个字符串放在argv中对应的位置。

①为什么这么做呢?②谁来做呢?③怎么做呢?

回答①,如果把代码改为:

#include<stdio.h>
int main(int argc,char *argv[])
{
    if(argc != 2)
    {
        printf("Usage: %s -v1/-v2/-v3\n",argv[0]);
        return 1;
    }
    //int i = 0;
    //for(;i<argc;i++)
    //{
    //    printf("argv[%d]: %s\n",i,argv[i]);
    //}
    //printf("hello\n");
    return 0;
}

这个代码的意思是:当我们输入指令执行代码时,必须要带一个选项,类似./a.out -a一样,否则提示错误,编译运行有如下结果:

如果我们把代码改为:

#include<stdio.h>
#include<string.h>
int main(int argc,char *argv[])
{
    if(argc != 2)
    {
        printf("Usage: %s -v1/-v2/-v3\n",argv[0]);
        return 1;
    }
    if(strcmp(argv[1],"-v1") == 0)
        printf("这是功能1\n");
    else if(strcmp(argv[1],"-v2") == 0)
        printf("这是功能2\n");
    else if(strcmp(argv[1],"-v3") == 0)
        printf("这是功能3\n");
    else
        printf("功能不支持\n");
    //int i = 0;
    //for(;i<argc;i++)
    //{
    //    printf("argv[%d]: %s\n",i,argv[i]);
    //}
    //printf("hello\n");
    return 0;
}

这就相当于argv[0]指向我们的可执行程序,-v1/-v2/-v3是三个选项,但是我们只准用其中有且一个选项。

此时我们把a.out重命名为myls有:

如果我们myls ,myls -v1 ,myls -v2,myls -v3,myls -t有:

那么命令行参数的意义是什么?

我们在Linux用到的大部分指令绝大部分都是C语言写的,如果我们ls ,ls -l, ls -l -a可以分别以下面三种形式呈现:

以ls为代表的各种命令本质是:程序(用C语言写的),而-l,-a我们当时把它们叫做选项,今天我们把选项视作:字符串,选项本质是字符串,可以以一定的方式传递给你ls内部的“main”,在ls内部实现的时候,就可以根据不同的选项,实现类似功能的不同表现形式。通俗来讲,原来我们学的这些命令带的选项本质上就是通过命令行参数实现的!(命令行参数:命令+选项)

②谁来做呢?

当我们输入ls -l -a,这些字符串在没敲回车之前,这些东西并没有被当做一个进程,是被父进程bash先拿到的,在命令行当中启用的所有进程的父进程都是bash,父进程的数据子进程能看到,只要它不修改,那么fork创建子进程之后,代码和数据都是共享的,因此,子进程就把数据从父进程继承下来,子进程就拿到了命令行参数数据了。但是谁给子进程拿到的命令行参数呢,这个需要到后面再讲解。③问题要回答,需要等到讲解到程序替换章节才行!

结论:

①命令行参数,至少有一个;

②进程对应的程序名字,一般是argv[0];

③有几个子串,argc就是几(空格隔开)。

如果我们把代码改为:

#include<stdio.h>
#include<string.h>
int main(int argc,char *argv[])
{
    if(argc != 2)
    {
        printf("Usage: %s -v1/-v2/-v3\n",argv[0]);
        return 1;
    }
    printf("argc = %d\n",argc);
    if(strcmp(argv[1],"-v1") == 0)
        printf("这是功能1\n");
    else if(strcmp(argv[1],"-v2") == 0)
        printf("这是功能2\n");
    else if(strcmp(argv[1],"-v3") == 0)
        printf("这是功能3\n");
    else
        printf("功能不支持\n");
    //int i = 0;
    //for(;i<argc;i++)
    //{
    //    printf("argv[%d]: %s\n",i,argv[i]);
    //}
    //printf("hello\n");
    return 0;
}

然后我们测试多个参数或一个参数时的结果:

命令行参数在被父进程bash获取时,通常被维护成数组的样子,即argv,它是一个指针数组,然后这个数组的每个参数会指向你在命令行当中输入对应的选项,如argv[0] = ./a.out等,当我们在命令行中输入对应的命令行参数的时候,bash就会把命令行参数打散,变成指针数组,依次指向一个一个子串(./a.out)(-v1),有效的元素个数为这个数组的有效元素个数,父进程bash把这些命令行参数维护成表格的样子,然后再传递给argc和argv,我们就能拿到了。如果我们没有在main函数带这些参数,那么就不会传递,其中argv数组必须在我们设置完有效的字符串之后,最后一个元素必须以NULL结尾,我们把代码改为:

#include<stdio.h>
#include<string.h>
int main(int argc,char *argv[])
{
    //if(argc != 2)
    //{
    //    printf("Usage: %s -v1/-v2/-v3\n",argv[0]);
    //    return 1;
    //}
    //printf("argc = %d\n",argc);
    //if(strcmp(argv[1],"-v1") == 0)
    //    printf("这是功能1\n");
    //else if(strcmp(argv[1],"-v2") == 0)
    //    printf("这是功能2\n");
    //else if(strcmp(argv[1],"-v3") == 0)
    //    printf("这是功能3\n");
    //else
    //    printf("功能不支持\n");
    int i = 0;
    for(;argv[i];i++)
    {
        printf("argv[%d]: %s\n",i,argv[i]);
    }
    //printf("hello\n");
    return 0;
}

此时也能正常执行:

NULL就是0,运行到无效字符串时就会执行结束!

argv叫命令行参数,它的本质可以让我们的程序获取将来我们输入的一个一个的选项,可以让我们在程序内部对选项进行判定 ,判定之后就能让我们的程序呈现不同的功能!

2.环境变量

2.1历史经历

如果我们直接讲环境变量,大概率是听不懂的,因此我们需要先谈一下历史经历!

我们在电脑搜索框输入:环境变量,有:

我们点击编辑系统环境变量后有:

点击右下角的“环境变量(N)”有:

其中上面一部分是用户的环境变量,下面一部分是系统环境变量。

有时候我们安装软件会询问我们是否将我们的软件添加到系统的path中,这在安装某些如:Python、Java这类编译器的时候会遇到。当时我们并不清楚为什么要这样做,但基本上我们也要这么做。因为如果我们不把环境变量添加到系统里,某些程序运行不了或者在命令行里面运行不了。

往往我们安装软件的时候,改的参数是系统级别的环境变量(上图的Path),然后在里面(点击Path,按编辑)添加一种路径,添加路径完后,某些程序就可以直接运行:

2.2环境变量基本概念

定义:环境变量是系统级别的一些全局变量 ,这些全局变量通常具备不同的用途,这种变量我们叫做环境变量。

但是这样我们确实不怎么理解这个环境变量是什么,环境变量的基本概念:

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

2.3Linux当中的环境变量

在Linux中的环境变量最常见的是:PATH。

我们会发现一个问题,如果我们直接使用ls是可以的:

但是执行我们的可执行程序却需要带./:

为什么ls不用带./呢,即为什么ls不用带路径(./a.out相当于绝对路径了),为什么执行我们自己的可执行程序必须带路径呢?

a.out:command not found,提示这个报错是因为:操作系统要找我要执行的可执行程序,但没找到(命令不存在)!

那么操作系统到底是在哪里找我们的指令的呢?

我们的系统命令在/usr/bin路径下:

如果我们ls /usr/bin/ls是能找到ls命令的:

在/usr/bin目录下是存在我们的命令的,所以系统会自动在/usr/bin路径下找命令。但是为什么系统会在/usr/bin路径下去找,而不去其他路径下找呢?

这是因为/usr/bin路径在系统变量PATH中存在的!即PATH包含了这个路径。操作系统查找可执行程序是在PATH环境变量里去找的!(操作系统查找可执行命令,是在环境变量PATH中查找的)

所以Windows里也有一个Path,点开它会有:

对于类似D:\ProgramFiles\Xshell8这种路径,在该路径下放着一系列的可执行程序。如果我们到文件中查找该路径,能看到:

所以为什么我们安装软件时要把对应软件的路径添加到Path里,因为我们将来想在命令行当中直接执行这个命令不用带路径(如果各位有安装了Python的,可以直接在命令提示符中输入python就能打开python)。

Windows下的环境变量Path和Linux下的PATH这两个工作是一样的,都是直接从Path中直接查找对应的命令是否有效。

如果我们此时输入python,一般会让我们直接下载python,第二步会有:

如果输入y就会把python的路径添加到Windows的Path中,此时在命令提示符中输入:python就能成功运行了:

如果我们输入print("hello")能够成功执行:

并且我们可以在用户或系统级别的环境变量看到这个(双击箭头所指):

那么怎么证明:操作系统,查找可执行命令,是在环境变量PATH中查找的?

如果我们想要查看环境变量的内容,在Xshell输入以下指令:

echo PATH

结果为:

这是因为系统自动把PATH认定为字符串了,如果我们想查看环境变量的内容,可以用:

echo $PATH

此时就能成功看到:

至此,我们就能看到PATH所对应的一个一个的子路径(用':'分割的就是一个子路径),也就是说,如果我们输入ls,它会先在/usr/local/sbin路径下查找有没有ls命令,如果没有,再在/usr/local/bin路径下查找,直到查找到/usr/bin路径下发现有这个命令!如果都没有在子路径找到,那么就会报错:command not found!

所以我们直接a.out是并不能运行的,这是因为我们的a.out并没有在PATH中任何一个子路径下,所以找不到。

①那么如果我们把PATH对应的路径都清空了,那是不是代表绝大多数指令都执行不了了呢?

怎么改环境变量的值呢?

PATH支持'='赋值,即PATH类似我们语言中的赋值语句,如果想改为空,直接:

PATH=' '

注意这个指令,等号左右不能带空格,否则会有:

然后运行刚刚指令有:

此时我们执行很多指令都失败了(可能有些指令还是成功的):

这就证明了,系统搜索命令是在PATH中搜索的,我们把这些路径弄没了就跑不了这些路径下的指令(除非带路径)。

那么我们的系统是不是没了?

我们把Xshell关闭了,重新打开,重新登录,此时我们就能够正常执行ls ,就算echo $PATH也能正常执行:

对比一下我们刚刚的那个路径:

很容易发现,这个PATH环境变量中的内容没有变化(不管是云服务器还是虚拟机都没关系),如果实在怕恢复不了,可以先把环境变量得到之后复制到剪贴板,然后到时候再用PATH赋值该路径即可,如:

PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin'

我们先不要管这个环境变量是从哪里来的,怎么恢复的到时候会说!

②我能不能把我自己的(绝对)路径添加到环境变量里,将来执行我们的可执行程序的时候,就不用带路径了呢?

如果想要导入新的环境变量,用:

export PATH=$PATH:你想要添加的路径

因为PATH = 是赋值,不能直接把我们的路径全部粘贴上去,应当保留之前的环境变量才行。

我们先把我们想要添加的路径复制下来,然后替换'你想要添加的路径'这里即可,类似:

export PATH=$PATH:/home/tan/8.8/the_main

然后我们可以用echo $PATH看结果:

此时我们就成功把该路径添加进去了,可以直接使用a.out(随便在哪个路径下都可以执行):

此时我们执行我们的程序就和执行系统命令完全一样了!

因此,我们在安装软件时添加环境变量,因为我们如果不添加,我们安装的可执行程序默认只能在安装路径下执行,为了让Python和Java能在任意地方随时执行,所以需要把安装好的软件的可执行程序路径添加到环境变量里。

那么这个PATH和当前用户是谁和当前所在路径有关吗,是不是我在任意地方都能执行我要执行的命令呢?

这个PATH是系统定义的全局变量,我们在任何地方都可以被bash找到,然后帮我们搜索路径,而PATH这个环境变量的主要功能是:协助我们执行可执行程序查找的!

全局变量是什么,需要我们到时候讲解本地变量和局部变量的时候才能讲解!

我们之前还学过which命令,可以which 指令 ,来查找指令所在的路径:

which也是在根据PATH进行查找!

2.4其他的环境变量

在Linux系统中环境变量有很多,除了PATH以外,还有其他的环境变量,如何查看这些环境变量呢?

使用:

env

可以查到我当前系统在当前用户下所对应的所有环境变量,env即environment的简写:

有些云服务器可能有HOSTNAME,这个代表主机名称;TERM:当前所采用的终端类型(xterm:远程登录),如果是虚拟机登录的VMware上有:

;SHELL:我们当前所采用的SHELL,叫做:/bin/bash,bash也是一个命令,只不过它运行后就是一个死循环,它会帮我们从命令行获取和解析信息;HISTSIZE:我们可以通过键盘上的↑↓来查找历史命令,而且还可以通过Ctrl+r搜索历史命令,这就决定了你所敲的命令在Linux系统中是会把我们所敲的历史命令记录下来,而记录下来的命令条数总不能一直记录,所以记录的指令个数一共为HISTSIZE个,如果HISTSIZE=1000,代表记录1000条历史命令(可以通过history查看历史命令)(一共1000行,我只展示最后几行,粘贴不了那么长的图片):

但是我们发现有1479条命令,但我们如果使用:

history | wc -l

能查看history的指令条数为:

因为它的编号并不是从1开始(除非你敲的指令少于1000行)!

SSH_CLIENT:代表当前IP地址,登录号等信息:

OLDPWD:

如果我们pwd,为:

在cd指令中我们学到过一个:

cd -

这个指令可以进入最近一次的目录,如果我们cd -有:

我们就进入到之前的路径了,此时我们再env有:

发现OLDPWD是我们之前所在的路径,如果cd - 有:

如果我们:

echo $OLDPWD

有:

也就是说我们cd -的时候,cd是通过OLDPWD来知道最近一次所处的目录(因为环境变量OLDPWD把最近的路径记录了下来),我们的:

cd -

会被bash解释成:

cd $OLDPWD

SSH_TTY:

这代表当前用户所关联的终端文件是谁,如果我们再起一个终端,那么env有:

这个SSH_TTY就算显示器文件,如果我们在SSH_TTY为/dev/pts/1的机器上输入:

echo aaaa>/dev/pts/0

那么就会有:

我们对应的SSH_TTY代表的是和当前用户相关联的终端文件是谁。

USER:

代表当前我们正在访问Linux系统所对应的用户是谁,即我们可以通过:

whoami

指令的结果:

LS_COLORS:

就是我们输入ls的时候,它会给我们带颜色:

LS_COLORS代表ls输出时的颜色输出方案。

PWD:

pwd是通过PWD环境变量来知道当前所处的路径,我们可以用echo $PWD能实现和pwd一样的效果:

且PWD自动更新!PWD会把当前用户所在的路径记录下来,因此我们可以很容易得到当前路径!

之后我们如果想要做一个自己写的pwd,我们只要想办法获取系统里的环境变量就行!

LANG:

表示当前语言,UTF-8代表支持汉字。

LOGNAME:

当前登录用户是谁。

HOME:

Linux系统有自己的家目录,也就是说我们把Xshell关闭重新登录某个用户默认所处的路径就叫做家目录,HOME叫做当前用户的家目录,我们的家目录为什么为/home/tan也是由环境变量决定!root账号的PWD叫做:/root(这里就不演示了)。

换句话说,环境变量本身在我们进行登录系统时被天然的优先初始化,然后形成环境变量,来支撑我们进行各种操作!

而这种环境变量我们可以在任何地方随时访问,这种变量我们叫做:系统级别的全局变量,不同的变量具备不同的用途!

2.5如何多方法获取环境变量

main函数可以有几个参数?

在我们之前的命令行参数中,main函数可以接受一个void类型的参数、int类型的argc和char*类型的argv,共三个参数,但是实际上main函数还可以这样写:

#include<stdio.h>
#include<string.h>
int main(int argc,char* argv[],char* env[])
{
    int i = 0;
    for(;argv[i];i++)
    {
        printf("argv[%d]:%s\n",i,argv[i]);
    }
    return 0;
}

也就是说main函数可以有第三个参数,env实际上是一个指针数组,它的指针指向一个一个的字符串,而这个字符串就算环境变量K=V的字符串。

也就是说我们刚刚使用env看到的所有环境变量:

本质①等号左边是K,等号右边是V(红色区域为key,蓝色区域为value),类似我们在C++学到过的pair(键值对)。

本质②env的结果每一行被当做一行字符串。

我们的系统会把环境变量维护成指针数组的样子,然后把环境变量的地址依次填写进char* env[]里,因此,我们把env指针数组称为环境变量表,且这个环境变量表也必须以NULL结尾。

如果要验证,可以把代码改为:

#include<stdio.h>
#include<string.h>
int main(int argc,char* argv[],char* env[])
{
    int n = 0;
    for(;env[n];n++)
    {
        printf("env[%d]:%s\n",n,env[n]);
    }
    //int i = 0;
    //for(;argv[i];i++)
    //{
    //    printf("argv[%d]:%s\n",i,argv[i]);
    //}
    return 0;
}

有如下的结果:

因此,我们可以让我们的C/C++程序获取系统的环境变量了!

所以获取环境变量方法①main函数获取。

如果我们可以获取环境变量,可以使用getenv函数,我们使用:

man getenv

看getenv具体功能:

翻译一下就是:

方法②通过函数getenv获取单个环境变量。

char *getenv(const char *name)我们需要传入我们想要获取的环境变量名字(拿key获取(等号左边获取))。实际上getenv就是遍历env这个表,然后通过你传入的参数,来匹配每一行等号左边的字符串,如果找到了,就把对应等号右边的字符串提取出来,如果没找到,返回为空,如果找到了;如果找到了,返回指针对应的value!

如:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main()
{
    char* path = getenv("PATH");
    printf("PATH = %s\n",path);
    //int n = 0;
    //for(;env[n];n++)
    //{
    //    printf("env[%d]:%s\n",n,env[n]);
    //}
    //int i = 0;
    //for(;argv[i];i++)
    //{
    //    printf("argv[%d]:%s\n",i,argv[i]);
    //}
    return 0;
}

然后我们运行有:

如果我们打印PATH里面的内容有:

发现完全一样!

此外,我们还可以使用environ这个全局变量来获取环境变量,可以用:

man 7 environ

看具体内容:

其实environ就是一个全局的变量,包含于头文件unistd.h,但是后面优化了一下,就看不到头文件这种了。

方法③使用全局指针来获取环境变量

实际上,这个全局指针不用初始化,也可以不包含头文件,我们可以直接通过这个全局指针来获取所有的环境变量,environ是一个耳机指针,指向环境变量表:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main()
{
    extern char **environ;
    int i = 0;
    for(; environ[i]; i++)
    {
        printf("environ[%d]:%s\n",i,environ[i]);
    }
    //char* path = getenv("PATH");
    //printf("PATH = %s\n",path);
    //int n = 0;
    //for(;env[n];n++)
    //{
    //    printf("env[%d]:%s\n",n,env[n]);
    //}
    //int i = 0;
    //for(;argv[i];i++)
    //{
    //    printf("argv[%d]:%s\n",i,argv[i]);
    //}
    return 0;
}

运行有:

那么进程是如何获取环境变量的?

不是进程自己获得了环境变量,是进程的父进程(bash)获得了环境变量,父进程不仅仅获得了环境变量,还形成了环境变量表!

环境变量表是数据吗?是父进程的数据吗?

我们之前讲过,我们命令行中所启动的子进程会继承父进程的代码和数据,当不修改数据时,数据能被子进程看到,因此能用上述三种方法获得父进程的环境变量表!

父进程bash在进行数据解析时,会形成两张表,一张表叫做:命令行参数表(argv);一张表叫做:环境变量表(env)。其中命令行参数表一直在变;而环境变量表相对比较稳定。

那么bash又是从哪里拿到的环境变量呢?

它是从系统配置文件中获取到的!系统配置文件会记录相关环境变量的内容,当bash就会读取系统配置文件,然后创建一个指针数组,指向这些配置文件的内容,创建出了一个环境变量表。也就是说:环境变量表是:内存级的表!

因此我们的PATH在清除后为什么重新启动XShell又能重新恢复了的原因是:我们清除的PATH是内存级别的,当重新登录时,bash会重新从配置文件里读取环境变量(包含PATH)。

配置文件在哪里?

它在我们当前任意一个用户的家目录下的隐藏文件里,如果我们切换到家目录并且ls -al显示所有文件(包含隐藏文件):

我们需要注意隐藏文件:

我们只需要知道配置文件在哪里即可,具体内容我们看了也看不懂。

2.6为什么要有环境变量

如果我想写一个只有自己能执行的可执行程序呢(root都不能执行)?

我们写下如下代码:

#include<stdio.h>
int main()
{
    printf("我是一个只有我自己能够执行的可执行程序!\n");
    return 0;
}

这是最初始的代码,如何保证自己能够执行呢?

我们通过getenv得到USER,只有tan才能登录,因此写下如下代码:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
    char* who = getenv("USER");
    if(strcmp(who,"tan"))
    {
        printf("我不让你执行:%s\n",who);
        return 1;
    }
    printf("我是一个只有我自己能够执行的可执行程序!\n");
    return 0;
}

系统会检测当前登录用户,只有tan用户才有权利执行该文件!(如果登录时是用tan,但是用su切换到其他用户,其USER还是tan,其他用户登录时还是可以执行)

因此,环境变量为什么要有,答案是:不同的环境变量,会有不同的用途!

如果我们自己想写一个自己的pwd可以把代码改为:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
    printf("%s\n",getenv("PWD"));
    //char* who = getenv("USER");
    //if(strcmp(who,"tan"))
    //{
    //    printf("我不让你执行:%s\n",who);
    //    return 1;
    //}
    //printf("我是一个只有我自己能够执行的可执行程序!\n");
    return 0;
}

此时就能用./self执行pwd命令了(路径不管怎么改都还是能成功获取):

pwd是什么吗?

进程会记录下来当前的工作路径称为cwd ------> 当前路径:cwd(所以我们新建文件或删除文件时它会默认再当前路径下新建/删除文件)-----> 父进程bash有没有cwd? --------> 所有进程都有cwd,父进程bash的cwd在bash进程自己的task_struct内部保存 ---------> 创建子进程,以父进程task_struct为模板创建。

自己的pwd从哪里来 -------> 从bash来。

所以cwd是操作系统内核的东西,因此操作系统会给我们系统调用来获取当前的cwd,用getcwd获取:

翻译过来是:

其中两个参数buf,size,buf为缓冲区,传递这个缓冲区,会把当前工作路径打印出来,size为缓冲区的大小。演示代码:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
int main()
{
    char pwdbuf[128];
    char* pwd = getcwd(pwdbuf,sizeof(pwdbuf));
    printf("%s\n",pwd);
    //char* who = getenv("USER");
    //if(strcmp(who,"tan"))
    //{
    //    printf("我不让你执行:%s\n",who);
    //    return 1;
    //}
    //printf("我是一个只有我自己能够执行的可执行程序!\n");
    return 0;
}

此时运行有:

因此也能获得当前工作路径(进程对应的工作路径)。

bash的pwd从哪里来?

bash在底层调用getcwd,然后更新环境变量!bash从getcwd获得当前的工作路径后更新环境变量表中的PWD,此时就能被子进程看到了!

修正概念:环境变量有相当一部分(大部分)是从配置文件中来,还有一部分是动态获取的!

即:环境变量大部分是从配置文件中来,也有少部分是启动之后,动态获取或者创建的!

2.7环境变量的特点和总结

环境变量为什么有全局属性?

在Linux的命令行上,我们可以在上面定义变量,如:key=100,然后echo $key就能看到key的值:

但这种变量并没有在全局变量当中(环境变量中),这个key变量只在本shell中有效,即我们在环境变量表中并不能看到key这个环境变量。

如果想要把key变成环境变量,输入:

export key

此时就能查到了:

如果想要把key取消掉全局变量属性,输入:

unset key

如果我们这样写:

export key=1234

也能看的到key在环境变量里:

我们把最开始key=100,没有在环境变量里的变量叫做:本地变量!(在命令行中不加export)

本地变量:只在bash内部有效。

bash并不会把key添加到环境变量表里,只有环境变量表才能被子进程继承。

如何查本地变量?

set

它会把所有的本地变量(包含环境变量)的所有变量都打印出来:

不过这样的方式会刷屏!

本地变量我们未来大概率不会使用,提出来是为了对比!

如果我们输入以下代码:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
int main()
{
    char* env = getenv("MYENV");
    if(env == NULL)
        printf("MYENV不存在!\n");
    else
        printf("%s\n",env);
    //char pwdbuf[128];
    //char* pwd = getcwd(pwdbuf,sizeof(pwdbuf));
    //printf("%s\n",pwd);
    //char* who = getenv("USER");
    //if(strcmp(who,"tan"))
    //{
    //    printf("我不让你执行:%s\n",who);
    //    return 1;
    //}
    //printf("我是一个只有我自己能够执行的可执行程序!\n");
    return 0;
}

如果直接执行,结果一定为:

如果我们定义一下MYENV的本地变量:

然后再执行:

此时还是MYENV不存在 !因为./self本质是创建了一个子进程,而./self拿不到bash创建的本地变量,但是如果:

export MYENV

此时再运行就能了:

我们知道./self是子进程,环境变量会被子进程继承下去,本地变量不会被子进程继承!(孙子进程,曾孙子进程也能拿到环境变量)

环境变量一旦建立好,所有的子进程都能拿到,因此环境变量有全局属性(系统级的全局变量)

那么:

echo $MYENV

为什么当MYENV为本地变量的时候,它仍然能正常输出结果呢?

echo是命令吗?要不要创建子进程?echo自己,怎么获得的本地变量?

在Linux系统里,大部分命令是真正存在的可执行程序,需要通过创建子进程的方式来执行。但在Linux中,有一部分命令,执行的时候,没有风险,需要bash自己执行,这种命令叫做内建命令!

也就是说echo在执行的时候相当于bash中的一个函数去执行了。但是我们:

which echo

会有:

虽然它确实存在在一个文件里,但是在命令行上写echo都会被bash解释成bash内部的函数,不会创建子进程。至于为什么还存在一个echo在文件里,Linux还有一种执行模式叫做:以脚本的形式执行,脚本模式执行时,它需要真正存在一个echo命令,不然执行会报错!

cd命令也不会创建子进程,因为我们如果cd,bash的PWD会变化,如果是创建子进程的话,那么cd对应的数据并不会传递到父进程里(它只能继承父进程的数据,但是不能给父进程传递数据),因此,cd指令也是一个内建命令。内建命令到自定义Shell再详细讲解!

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐