1. 命令行参数
1.1 概念
#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;
}
我们使用C语言写代码时,都会使用到main函数,但是不会给main函数设置参数。其实main函数有自己的参数,可以向上面一样写参数类型。第一个参数是整型变量,第二个参数是字符指针数组,用于存储字符串的。并使用for循环打印字符指针数组中指向的字符串。
执行myproc程序结果如上。当命令行中只输入该程序,argv数组只存储了"./myproc",argc变量为1。如果后面使用空格隔开输入一些字符串选项,argc变量就是命令行中以空格隔开字符串的个数,argv数组就会存储这是字符串的指针。
因此,我们可以得出结论,main函数第一个参数是命令行参数的个数,第二个参数存储命令行各个参数的字符串数组。main函数前两个参数叫做参数列表
1.2 指令如何实现不同功能
#include <stdio.h>
#include <string.h>
//./myproc -opt1 -opt2 -opt3
int main(int argc, char* argv[])
{
if (argc != 2)
{
printf("Usage: myproc -opt\n");
return 1;
}
if (strcmp(argv[1], "-opt1") == 0)
{
printf("功能1\n");
}
else if(strcmp(argv[1], "-opt2") == 0)
{
printf("功能2\n");
}
else if(strcmp(argv[1], "-opt3") == 0)
{
printf("功能3\n");
}
else
{
printf("默认功能\n");
}
return 0;
}
上面的代码中,我们让用命令行运行该程序的人,在该程序后面必须加一个选项。argc变量记录命令行参数个数,可以通过argc来判断是否命令行中后加上一个参数。如果在命令行中加上第二个参数,再判断该参数是否跟"-opt1"、"-opt2"、"-opt3" 三个字符串中的任意一个相等,相等的话,就会实现某种功能,不过这里使用打印来替代。
当我们在命令行中运行该程序,如果不加上选项,会打印提示消息。如果选项是-opt1,就会打印功能1。其实就类似于"ls -l"这行指令,判断ls指令后面跟的选项是否为代码中已实现功能的标记符号。这就是linux指令后面代选项能完成相应的功能的部分原理。
1.3 命令行进程
在终端中,你输入的命令行参数都会被shell拿到。shell是一个命令行界面软件,linux操作系统中一般是bash进程。shell拿到命令行的参数,会按照空格打散,形成一张字符串列表,并记录参数个数,就是我们所说的argv和argc变量。而一般命令行启动的程序的父进程就是bash进程。
子进程一般会拷贝父进程的数据,尤其是只读的数据。因此,子进程就可看到传进来的参数列表。
2. 环境变量
2.1 main函数参数
#include <stdio.h>
#include <string.h>
int main(int argc, char* argv[], char* env[])
{
for(int i = 0; env[i]; i++)
{
printf("env[%d]: %s\n", i, env[i]);
}
return 0;
}
main函数的第三个参数也是一个字符串指针数组,这个数组就存储了环境变量。使用for循环进行打印,其中for循环的判断部分直接写个env[i],因为最后一个元素是NUll空指针,就会退出循环。
运行结果如上,我们会发现打印出许多的信息。这些就是环境变量,环境变量以key=value形式一行一行的展现。有些占许多行是因为字符太多,一个屏幕放不下才会折行。
其中有我们熟悉的bash进程路径,还有pwd当前路径,还有home家路径。
其实在linux操作系统中,输入env指令,就可以查看跟刚刚相同的环境变量。
2.2 认识各种环境变量
(1)PATH
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("I am a process, pid: %d\n", getpid());
return 0;
}
我们运行一个普通程序,需要在前面加上./,这与可执行程序名组成相对路径。相当于告诉操作系统该可执行程序的位置在哪,系统才会执行。
那这是怎么做到的呢?这是因为linux操作系统会启动bash进程,而该进程会加载环境变量。我们可以通过env指令后面加上“$”,再加上具体环境变量名称,就可以获取该环境变量的内容。环境变量中有个PATH变量,记录着许多路径。它是系统可执行文件的搜索集合。其中就包含有/usr/bin路径。
既然系统的进程会通过PATH环境变量下的路径去寻找匹配的可执行文件,那么我们可以将myproc程序的路径添加到PATH变量中。你要在命令行输入你要改变的环境变量名再加上个“=”符号,其中“$PATH”表示原有的环境变量,再加上冒号,后面跟上添加的路径。
使用env查看PATH变量,添加路径成功,直接输入myproc,运行程序成功。我们就可以通过改变环境变量直接运行可执行文件,不需要添加路径。但是如果我们关掉shell重启,我们刚添加的路径还在吗?
(2)USER
USER变量会记录shell启动后的用户名。
进程内部有个uid变量,该变量记录的是哪个用户启动的进程。那么你在启动进程的时候,系统怎么知道你是谁?并且把你的uid写到进程的pcb中?
这是因为启动shell进程,系统会读取用户和系统相关环境变量的配置文件,形成自己的环境变量表。而所有在命令行启动的程序,都是shell进程的子进程。子进程会继承父进程的许多属性,环境变量表中的大部分属性会被拷贝。因此,子进程就会读取继承下来的环境变量表中的USER变量。
由此引深开来,所有在命令行启动的程序都是bash进程的子进程,那么这些子进程会继承bash进程许多属性,那么cwd就会以bash进程为基础。
(3)SHELL
shell变量是记录你登录时启动的哪种版本的shell进程,linux下是bash。
pwd变量是保存当前进程所在的工作路径。那为什么存在pwd环境变量呢?我们下面通过代码获取环境变量。
- 第一种方法可以是使用main函数的参数列表中的第三个字符串数组变量,判断数组中字符串前三个字符是否为PWD,再对后面的字符串进行切割,获取工作路径。
- 第二种方法是使用系统封装的getenv函数,来获取相应的环境变量。
(4)PWD
getenv函数可以获取环境变量,只需要传入变量名字符串,就会返回该变量的内容。如果没有与传入字符串匹配的变量,会返回空指针。
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("%s\n", getenv("PWD"));
return 0;
}
我们运行envtest程序可以获取当前工作路径,pwd指令也可以获取当前工作路径。此时,我们完成了对pwd指令的实现。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
const char *who = getenv("USER");
if (strcmp(who, "Greg_122") == 0)
{
printf("正常执行命令!\n");
return 0;
}
else
{
printf("无权访问!\n");
return 1;
}
return 0;
}
我们通过getenv函数,可以获取启动当前进程的用户,再做个判断,看启动该进程的用户是不是指定的用户。
这里可以看到,当用户为Greg_122时,可以正常访问。即使是超级用户root,也无法运行该程序内部的正常内容。这就实现了权限的设置。
(5)OLDPWD
oldpwd会记录上次所在的工作路径。而“cd -”指令就可以回到上次所在的工作路径,说明这条指令是通过获取OLDPWD环境变量实现的。
2.3 char **environ
获取环境变量还可以通过environc二级字符类型指针,这是一个全局的指针变量,指向的是环境变量表的第一个元素。
#include <stdio.h>
#include <unistd.h>
int main()
{
for(int i = 0; __environ[i]; i++)
{
printf("%s\n", __environ[i]);
}
return 0;
}
写一个for循环遍历获取环境变量,运行结果如下。
3 本地变量
3.1 认识本地变量
linux操作系统中,shell启动后不仅要环境变量,还有本地变量。可以通过“变量名”加上“=”,再加上“变量内容”来定义。定义一个本地变量后可以使用echo指令加上$符号,再跟上变量名,来打印内容。其中$符号类似一种指针的用法。定义了一个本地变量,还可以使用到指令中。
通过env指令查看环境变量,发现没有刚刚定义的本地变量。那么本地变量存放在哪里呢?shell会将本地变量使用一个指针数组维护起来,形成一张本地变量表。不仅有本地变量表,还会有环境变量表,命令行参数表,都是指针数组。
3.2 转换成环境变量
我们可以通过set指令读取到所有的本地变量和环境变量。那本地变量与环境变量有什么关系呢?
我们可以通过export指令,将本地变量i导出到环境变量表中。还可以使用export后面加上变量定义,导出到环境变量表中。如下图所示,操作系统会先创建一个bash进程来管理命令行启动的程序。bash进程中会维护环境变量表env,命令行参数表,还有本地变量表。
但是一旦退出,重新登陆shell,你会发现不管你定义的本地变量,还是导入到环境变量新增变量,都不存在了。所新增的变量只作用于当前启动的shell进程,一旦会话结束就会销毁新增变量。
3.3 与环境变量比较
与本地变量不同,环境变量会被传递给子进程。本地变量默认情况下不会传递给子进程。我们下面可以写一个demo展示一下。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
const *isrunning = getenv("ISRUNNING");
if (isrunning == NULL)
{
while(1)
{
sleep(1);
printf("当前进程首次启动!\n");
}
}
else
{
printf("当前进程已经启动,不要再启动了!\n");
}
return 0;
}
我们定义一个指针变量,用来接受getenv函数返回的指针,如果该进程有ISRUNNING环境变量,那么isrunning指针就不为空,会打印else下的语句。如果不存在,就会一直while循环打印“当前进程首次启动”这句话。
我们添加一个ISRUNNING本地变量,运行该程序,发现会一直循环打印“当前进程首次启动”这句话。当我们添加到环境变量中,运行该程序,只会打印一句话,表明ISRUNNING变量被子进程获取,而本地变量却不能被子进程获取。
由此得出一个结论,环境变量具有“全局属性”,可以被bash的所有子进程获取。那为什么环境变量要具有这种性质呢?
创作充满挑战,但若我的文章能为你带来一丝启发或帮助,那便是我最大的荣幸。如果你喜欢这篇文章,请不吝点赞、评论和分享,你的支持是我继续创作的最大动力!