文章目录
一、命令行参数
我们先看一段代码:
cpp
int main(int argc, char* argv[])
{
int i = 0;
for(i; i < argc; i++)
{
printf("argv[%d]: %s\n", i, argv[i]);
}
return 0;
}
运行结果如下:

上面的main函数的两个参数就是命令行参数,平时我们使用main函数时默认都是没有使用命令行参数的,但其实它是真实存在的。argv是一个字符指针数组(命令行参数表),它指向一个一个的字符串,argc是字符指针数组的元素个数。(arg是参数英文argrments的缩写,c表示count,v表示vector)
那这两个参数起什么作用呢?我们看运行结果可以猜个大概,我们输入的一串指令本质就是字符串,整体字符串会被分为由空格隔开的一个个字串,每个子串会被存放在argv数组中。
这里我相信大家心里都要疑惑,为什么会有命令行参数?还有许多子问题,这里小编不能全部回答,有一些问题需要学到进程控制时才能理解。
为什么有命令行参数?
我们再来看一段代码:
cpp
int main(int argc, char* argv[])
{
if(argc != 2)
{
//如果不符合命令格式
printf("Usage: %s: -a/-b/-c\n", argv[0]);
return 1;
}
if(strcmp(argv[1], "-a")== 0)
printf("这是功能1\n");
else if(strcmp(argv[1], "-b") == 0)
printf("这是功能2\n");
else if(strcmp(argv[1], "-c") == 0)
printf("这是功能3\n");
else
printf("功能不支持\n");
}
运行结果:

我们之前学习过,命令本质是程序(大部分是C语言写的),上面代码编译形成的可执行程序myls可以把它理解成一种命令,-a -b -c可以理解成命令的选项。所以我们现在明白了,原来所谓选项本质就是字符串,它可以以一定方式传递给命令程序内部的main函数,在命令程序内部实现的时候,就可以根据不同的选项,实现类似功能的不同表现形式。
命令行参数是由谁做的,命令字符串先被谁拿到?
这里小编不解释太多,我们之前已经知道命令行启动的所有进程都是bash的子进程,那么命令程序自然也是bash的子进程,所以命令字符串会先被bash拿到,然后由父进程bash进行一系列操作把命令字符串打散传给子进程(命令程序),然后子进程就拿到了命令行个数和命令行。具体过程在后面讲程序替换时再细讲。
总结:linux系统中,命令行参数被shell获取时,通常会被父进程(bash)维护成一个指针数组(argv),并提取该数组中有多少有效元素(argc),然后再把argv、argc传递给对应子进程的main函数,子进程拿到后就可以用来实现不同功能了。
补充:argv数组最后一个元素必须以null结尾。下面是示例代码:
cpp
int main(int argc, char* argv[])
{
int i = 0;
for(; argv[i]; i++)
{
printf("argv[%d]: %s\n", i, argv[i]);
}
return 0;
}
运行结果:

命令行参数有以下几个特点:
1、命令行参数至少有一个,因为程序要运行至少要有./程序名或者指令名。
2、进程对应的程序名一般是argv[0]。
3、命令字符串有几个由空格隔开的字串,argc就是几。
二、环境变量
环境变量虽然我们没有真正了解过,但是它无处不在,下面我们一起来揭开它的神秘面纱吧。
一个现象引入环境变量
我们之前在学习指令时知道系统级指令可以在任意路径下运行,因为系统级指令可以不加./直接用指令名运行,而我们自己写的可执行程序不做任何修改的情况下只能在可执行程序所在路径下运行,因为系统规定执行自己的可执行程序需要在程序名前加./运行,比如下面:

当我们直接a.out时系统会报 command not found,这个报错背后意味着系统要找我要执行的程序,我们之前的说法是系统会去user/bin/路径下找,当我们把自己的程序拷到user/bin/路径下后,就可以不加./直接运行了。这里我们就可以详细说一说它背后的原理,user/bin/路径其实被包含在系统内的一个环境变量PATH中,操作系统本质会去环境变量PATH中查找可执行命令。
要证明上面说法我们可以用下面的指令查看环境变量的内容:

果然user/bin/在PATH路径中,当一个指令在PATH路径中找到了就可以直接运行,如果指令不在PATH路径中我们直接运行系统就会报command not found ,我们自己创建的可执行程序所在的目录(当前目录)默认是不会被PATH路径包含的,所以我们自己的程序默认需要加./运行。
可是linux为什么要这样设计呢?这主要是处于安全考虑,如果当前目录被加入 PATH,当目录中存在伪装成系统命令的恶意程序(比如命名为 ls 的恶意脚本)时,在系统任意路径下执行 ls 会优先运行当前目录的恶意程序,而非系统真正的 ls 命令,存在安全风险。
指令中会用到PATH:我们之前介绍的which指令本质就是在PATH里找对应的命令:

修改环境变量
修改环境变量的操作和我们在学习语言时的赋值操作类似,我们下面会分别操作将PATH置为空和将我们自己的路径加入环境变量。
将PATH置为空:

我们可以看到将PATH置为空后在user/bin/路径下的外部指令就无法使用了,而入cd、pwd等内部指令还能正常使用。但是不用怕,我们退出Xshell然后重新登陆环境变量PATH就正常了,原理我们后面说。
将当前路径加入PATH:

将当前路径加入PATH后我们的a.out就可以像系统命令一样在任意位置直接运行了。
配环境的本质
在平时我们配java环境、python环境本质就是先将对应的可执行程序下载到电脑里,然后将可执行程序的路径添加到环境变量里,这样可执行程序就可以不局限于可执行程序的路径,而可以在任意路径下运行。
查看环境变量
env指令可以查看系统在当前用户下的所有环境变量:
(小编只挑一部分注释)

环境变量本质
环境变量有的让用户处于默认家目录(cd ~ == cd HOME),有的用来记录当前用户是谁(whoami == echo USER),有的用来记录当前主机名是什么等等,这些环境变量是系统级的全局变量,不同的变量具有不同的用途,它们会在我们用户登陆系统时被天然初始化,我们在任意路径下都可以随时访问。
如何通过代码获取环境变量
1、main函数获取
这里小编要补充一点,环境变量是main函数的第三个参数,前两个main函数参数是命令行参数,我们已经介绍过了,这三个main函数参数都是可以省略的,如果要显示使用第三个参数的话前两个参数也要带上,这是严格要求的。
main函数第三个参数本质也是一个字符指针数组(环境变量表),也是指向一个一个的字符串,这里的字符串是环境变量的KV键值对,如下图所示:(左边小方块environ后面解释)

我们下面拿代码来验证一下:
cpp
//环境变量参数
int main(int argc, char* argv[], char* env[])
{
int n = 0;
for(; env[n]; n++)
{
printf("env[%d]: %s\n", n, env[n]);
}
return 0;
}
运行结果:

2、通过函数获取单个环境变量
getenv函数可以获取单个环境变量,使用该函数需要包含stdlib.h头文件。

getenv函数参数传环境变量名(key),返回值是指向环境变量的具体数据(value)的指针,若环境名参数不存在则返回nullptr。示例如下:
cpp
#include <stdlib.h>
int main()
{
char* path = getenv("PATH");
printf("PATH = %s", path);
}
运行结果:

3、通过environ变量获取
C语言为我们提供了一个全局指针变量environ,在linux系统下使用不需要包含unistd.h头文件,只需要声明一下 extern char** environ 即可使用。

它是一个二级指针,指向了字符指针数组(环境变量表)的首元素地址,既然它是一个二级指针,我们又可以通过environ[]访问整个字符指针数组,示例如下:
cpp
int main()
{
extern char** environ;
int n = 0;
for(; environ[n]; n++)
{
printf("environ[%d], %s\n", n, environ[n]);
}
}
运行结果:

环境变量的来源
上面我们介绍了如何用代码获取当前进程的环境变量,这只是方法,我们还需要知道环境变量从哪来。首先我们要知道环境变量本质就是一些数据,环境变量可以类比命令行参数,我们当前的子进程环境变量其实是从父进程
(bash)来的,我们之前学习过,子进程会继承父进程的代码和数据。
有了前面的知识,我们可以做个总结:父进程bash在命令行解析运行程序时会维护两张表:命令行参数表和环境变量表,命令行参数表一直在变,因为命令行一直在输入指令,而环境变量表相对比较稳定,父进程bash在fork子进程后子进程会继承父进程的代码和数据,子进程自然而然就可以看到并获取环境变量了。
看到这里我想大家一定还有问题,父进程bash是如何获取环境变量数据的呢?我们知道命令行参数数据是由用户输入的,父进程bash可以直接获取,那环境变量呢?小编这里直接揭晓答案了,后面用示例验证,其实bash是从系统的配置文件中获取的环境变量,系统中有一些配置文件会记录当前环境变量的名字和内容,当我们登陆Xshell时系统会创建并运行bash,bash就会用读文件操作读取这些配置文件中的内容,然后在bash进程内部动态申请指针数组(环境变量表),接着解析读取到的环境变量内容依次放入环境变量表中。所以我们介绍的命令行参数表和环境变量表这两张表本质是内存级的,这里我们就能理解为什么前面我们把PATH清空后再重启Xshell后环境变量PATH又复原了,原因就是重启Xshell后系统重新读取配置文件把环境变量又加载到内存中了。
这些配置文件一般位于普通用户的家目录中,只不过它们是隐藏文件,所以要查看它们需要加-a,我们重点关注用方框框起来的两个文件:

我们来看看这两个文件中的内容,里面大部分都是shell脚本,大家看不懂没关系,简单来说父进程bash会执行这些脚本把对应的环境变量导到它的地址空间里或者说程序里。
验证:
下面我们来验证一下,我们修改.bash_profile文件中的PATH路径,把我们自己的路径添加进去,然后echo一句话:

我们退出Xshell然后重新登陆:

我们看到输出的一句话代表配置文件确实被执行了,被添加进PATH的路径里的可执行文件也可以像指令一样在任意位置运行了。
但是并不是所有环境变量都是从配置文件来,还有少部分是bash启动之后动态获取或创建的,比如PWD,它是从bash调用系统调用接口getcwd() 获取的,具体过程如下:当 bash 启动时,会调用 getcwd() 获取初始 cwd,并存入 PWD 环境变量。当用户执行 cd 命令切换目录时,bash 会先通过 chdir() 系统调用修改自身的 cwd,然后立即调用 getcwd() 获取新的 cwd 路径,更新到 PWD 环境变量中。
getcwd()文档:

getcwd()可以获取当前进程的工作路径,哪个进程调用getcwd()就返回哪个进程的当前工作路径,它的第一个参数是缓冲区,第二个参数是缓冲区的大小,返回值是当前工作路径字符串的首元素地址。
下面是一段示意代码,不仅是bash可以调用getcwd,子进程也可以通过getcwd获取子进程的pwd。
cpp
int main()
{
char pwdbuf[128];
char* pwd = getcwd(pwdbuf, sizeof(pwdbuf));
printf("%s\n", pwd);
return 0;
}
运行结果:

环境变量的作用
我们前面介绍了许多环境变量的周边概念,那环境变量在实际开发中有哪些作用呢?下面小编编写一个只有自己才能运行的程序,root来了都不行。
cpp
int main()
{
char* who = getenv("USER");
if(strcmp(who, "fdb") != 0)
{
printf("我不让你执行:%s\n", who);
return 1;
}
//自己才能运行的程序:
printf("这个程序只有我能执行,root也没招!\n");
return 0;
}
运行结果:
借助这个例子小编想告诉大家不同的环境变量会有不同的用途,它是全局的,如果你想获取可以随时获取。系统在某些场景下会自动获取环境变量,比如(cd -) (cd ~) pwd等等。
本地变量和相关指令
在linux系统中,除了环境变量,还有本地变量的概念,本地变量只在父进程(bash)内部有效,它不会进环境变量表,不会像环境变量一样能继承给子进程。下面是几个相关的指令介绍:
echo: 显⽰某个环境变量值
export: 设置⼀个新的环境变量

env: 显⽰所有环境变量
unset: 清除环境变量

set: 显⽰所有变量,包括本地变量和环境变量
环境变量的全局性
下面我们来聊聊环境变量为什么是全局变量,我们还是先看一段示例代码:
cpp
int main()
{
char* myenv = getenv("MYENV");
if(myenv == NULL)
{
printf("MYENV不存在!\n");
}
else
{
printf("MYENV=%s\n", myenv);
}
return 0;
}

上面的运行结果是 "MYENV不存在!:是因为我们创建的MYNEV是本地变量,.本地变量只有bash能拿到,/a.out创建的子进程拿不到这个本地变量。

当我们把MYENV导成环境变量后,子进程就能拿到这个环境变量了,同一份代码结果也发生了变化。
正因为bash的环境变量会把环境变量继承给子进程,而子进程也会创建子进程也可以继承到环境变量,所以以bash为发起点,它的子进程、孙子进程...都能继承到bash的环境变量,所以我们说环境变量是全局变量,具有全局属性。
内建命令的引出
我们先看下面的示例:

为什么这里的由bash创建的echo命令子进程能拿到本地变量MYENV并打印呢?这里小编需要引入一个概念:内建命令
在linux中,大部分命令是可执行程序,也就是user/bin路径下的可执行文件,需要通过bash创建的子进程执行,还有一部分命令因为执行的时候没有风险,需要bash进程自己执行,我们把这些命令称作内建命令。
(但是有些内建命令比较特殊,也会在user/bin路径下存在,但是它运行时不会创建子进程,这样做是为了在脚本模式中能执行echo命令。)
以上就是小编分享的全部内容了,如果觉得不错还请留下免费的关注和收藏
如果有建议欢迎通过评论区或私信留言,感谢您的大力支持。
一键三连好运连连哦~~