【Linux】进程概念(五) 命令行参数与环境变量的深度解析

【Linux】进程概念(五) 命令行参数与环境变量的深度解析

文章目录


前言:命令行参数数组和环境变量env数组最后一个元素都是NULL。

一、命令行参数

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

为什么有命令行参数?

我们再来看一段代码:
intmain(int argc,char* argv[]){if(argc !=2){//如果不符合命令格式printf("Usage: %s: -a/-b/-c\n", argv[0]);return1;}if(strcmp(argv[1],"-a")==0)printf("这是功能1\n");elseif(strcmp(argv[1],"-b")==0)printf("这是功能2\n");elseif(strcmp(argv[1],"-c")==0)printf("这是功能3\n");elseprintf("功能不支持\n");}
运行结果:
在这里插入图片描述
我们之前学习过,命令本质是程序(大部分是C语言写的),上面代码编译形成的可执行程序myls可以把它理解成一种命令,-a -b -c可以理解成命令的选项。所以我们现在明白了,原来所谓选项本质就是字符串,它可以以一定方式传递给命令程序内部的main函数,在命令程序内部实现的时候,就可以根据不同的选项,实现类似功能的不同表现形式。

命令行参数是由谁做的,命令字符串先被谁拿到?

这里小编不解释太多,我们之前已经知道命令行启动的所有进程都是bash的子进程,那么命令程序自然也是bash的子进程,所以命令字符串会先被bash拿到,然后由父进程bash进行一系列操作把命令字符串打散传给子进程(命令程序),然后子进程就拿到了命令行个数和命令行。具体过程在后面讲程序替换时再细讲。
总结:linux系统中,命令行参数被shell获取时,通常会被父进程(bash)维护成一个指针数组(argv),并提取该数组中有多少有效元素(argc),然后再把argv、argc传递给对应子进程的main函数,子进程拿到后就可以用来实现不同功能了。
补充:argv数组最后一个元素必须以null结尾。下面是示例代码:
intmain(int argc,char* argv[]){int i =0;for(; argv[i]; i++){printf("argv[%d]: %s\n", i, argv[i]);}return0;}
运行结果:
在这里插入图片描述
命令行参数有以下几个特点:
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后面解释)
在这里插入图片描述
我们下面拿代码来验证一下:
//环境变量参数intmain(int argc,char* argv[],char* env[]){int n =0;for(; env[n]; n++){printf("env[%d]: %s\n", n, env[n]);}return0;}
运行结果:
在这里插入图片描述

2、通过函数获取单个环境变量

getenv函数可以获取单个环境变量,使用该函数需要包含stdlib.h头文件。
在这里插入图片描述
getenv函数参数传环境变量名(key),返回值是指向环境变量的具体数据(value)的指针,若环境名参数不存在则返回nullptr。示例如下:
#include<stdlib.h>intmain(){char* path =getenv("PATH");printf("PATH = %s", path);}
运行结果:
在这里插入图片描述

3、通过environ变量获取

C语言为我们提供了一个全局指针变量environ,在linux系统下使用不需要包含unistd.h头文件,只需要声明一下 extern char** environ 即可使用。
在这里插入图片描述
它是一个二级指针,指向了字符指针数组(环境变量表)的首元素地址,既然它是一个二级指针,我们又可以通过environ[]访问整个字符指针数组,示例如下:
intmain(){externchar** 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。
intmain(){char pwdbuf[128];char* pwd =getcwd(pwdbuf,sizeof(pwdbuf));printf("%s\n", pwd);return0;}
运行结果:
在这里插入图片描述

环境变量的作用

我们前面介绍了许多环境变量的周边概念,那环境变量在实际开发中有哪些作用呢?下面小编编写一个只有自己才能运行的程序,root来了都不行。
intmain(){char* who =getenv("USER");if(strcmp(who,"fdb")!=0){printf("我不让你执行:%s\n", who);return1;}//自己才能运行的程序:printf("这个程序只有我能执行,root也没招!\n");return0;}
运行结果:
在这里插入图片描述


在这里插入图片描述
借助这个例子小编想告诉大家不同的环境变量会有不同的用途,它是全局的,如果你想获取可以随时获取。系统在某些场景下会自动获取环境变量,比如(cd -) (cd ~) pwd等等。

本地变量和相关指令

在linux系统中,除了环境变量,还有本地变量的概念,本地变量只在父进程(bash)内部有效,它不会进环境变量表,不会像环境变量一样能继承给子进程。下面是几个相关的指令介绍:
echo:显⽰某个环境变量值
export:设置⼀个新的环境变量
在这里插入图片描述
env:显⽰所有环境变量
unset:清除环境变量
在这里插入图片描述
set:显⽰所有变量,包括本地变量和环境变量

环境变量的全局性

下面我们来聊聊环境变量为什么是全局变量,我们还是先看一段示例代码:
intmain(){char* myenv =getenv("MYENV");if(myenv ==NULL){printf("MYENV不存在!\n");}else{printf("MYENV=%s\n", myenv);}return0;}
在这里插入图片描述
上面的运行结果是 “MYENV不存在!:是因为我们创建的MYNEV是本地变量,.本地变量只有bash能拿到,/a.out创建的子进程拿不到这个本地变量。
在这里插入图片描述
当我们把MYENV导成环境变量后,子进程就能拿到这个环境变量了,同一份代码结果也发生了变化。
正因为bash的环境变量会把环境变量继承给子进程,而子进程也会创建子进程也可以继承到环境变量,所以以bash为发起点,它的子进程、孙子进程…都能继承到bash的环境变量,所以我们说环境变量是全局变量,具有全局属性。
在这里插入图片描述

内建命令的引出

我们先看下面的示例:
在这里插入图片描述
为什么这里的由bash创建的echo命令子进程能拿到本地变量MYENV并打印呢?这里小编需要引入一个概念:内建命令
在linux中,大部分命令是可执行程序,也就是user/bin路径下的可执行文件,需要通过bash创建的子进程执行,还有一部分命令因为执行的时候没有风险,需要bash进程自己执行,我们把这些命令称作内建命令。
(但是有些内建命令比较特殊,也会在user/bin路径下存在,但是它运行时不会创建子进程,这样做是为了在脚本模式中能执行echo命令。)

以上就是小编分享的全部内容了,如果觉得不错还请留下免费的关注和收藏
如果有建议欢迎通过评论区或私信留言,感谢您的大力支持。
一键三连好运连连哦~~

在这里插入图片描述

Read more

【高企年报观察】拒绝Excel打架:我们如何用低代码搭建高企年报自动化系统,将填报时间从3天压缩到1小时

【高企年报观察】拒绝Excel打架:我们如何用低代码搭建高企年报自动化系统,将填报时间从3天压缩到1小时

拒绝Excel打架:我们如何用低代码搭建高企年报自动化系统,将填报时间从3天压缩到1小时 每年2-4月,高新技术企业财务部都会上演一场“年度大戏”。 财务专员拿着税务局加计扣除申报表,问研发助理:“你们这21个项目,工时分摊到底怎么算的?” 研发助理翻出去年12月的Excel记录:“当时王工说大概30%,李工说20%,我按印象填的……” 知识产权专员抱着一摞专利证书:“这5项专利年费忘了缴,还能补吗?年报里要不要填?” 市场部发来邮件:“去年那款B产品销售额1200万,算不算高新收入?对应哪个专利来着?” ——然后所有人开始翻微信记录、找历史邮件、打Excel补丁。 三天后,财务总监终于在申报截止前按下“提交”键。 “明年一定提前准备。” ——然后明年,剧本重演。 这不是能力问题,这是工具问题。 2026年,我们在一家年研发投入3000万元的装备制造企业,用3周时间,基于简道云搭建了一套“高企年报自动化管理系统”。 从此,他们每年填报年报的时间从3人·天压缩到1人·小时。 稽查人员突击进场时,2小时内提供全套备查资料,零补正、零调减。 今天,我把这套系统的架

By Ne0inhk
小鹏VLA 2.0的“神秘涌现”:从痛苦到突破,自动驾驶与机器人如何突然“开窍”?

小鹏VLA 2.0的“神秘涌现”:从痛苦到突破,自动驾驶与机器人如何突然“开窍”?

大家好,我是数据与算法架构提升之路,专注于AI、自动驾驶和机器人领域的最新动态。今天,我们来聊聊小鹏汽车在2025科技日上爆出的重磅消息:VLA 2.0和人形机器人IRON的“涌现”过程。这不仅仅是技术迭代,更是像科幻小说一样的突然“觉醒”。如果你对自动驾驶的未来感兴趣,这篇文章绝对值得一读!我们将基于小鹏自动驾驶负责人刘先明和机器人副总裁米良川的独家对话,揭秘背后的故事。 * 刘先明 | 小鹏汽车自动驾驶负责人 他于2016年博士毕业于伊利诺伊大学厄巴纳-香槟分校(UIUC),曾在Facebook(现Meta)、Cruise任职,从事机器学习与计算机视觉领域的前沿研究工作。现全面负责小鹏汽车自动驾驶中心业务和组织管理工作。 * 米良川 | 小鹏汽车机器人副总裁及AI技术委员会负责人 他是机器人与AI领域的资深专家。曾在NVIDIA任职十余年,有深厚的GPU并行计算、移动计算、深度学习及自动驾驶技术功底;并曾于CMU机器人研究所深造,且拥有创办机器人公司的实践经验。 涌现的奇迹:从失败边缘到全新大陆 想象一下:一个项目经历了数月的失败,团队内部甚至多次讨论是否要停掉它

By Ne0inhk
宇树 G1 机器人开发入门:有线 & 无线连接完整指南

宇树 G1 机器人开发入门:有线 & 无线连接完整指南

适用读者:机器人二次开发者、科研人员 开发环境:Ubuntu 20.04(推荐) 机器人型号:Unitree G1 EDU+ 前言 宇树 G1 是一款面向科研与商业应用的高性能人形机器人,支持丰富的二次开发接口。在正式进行算法调试与功能开发之前,首要任务是建立稳定的开发连接。本文将详细介绍两种主流连接方式:有线(网线直连) 与 无线(WiFi + SSH),并附上完整的配置流程,帮助开发者快速上手。 一、有线连接(推荐新手优先使用) 有线连接通过网线直接将开发电脑与 G1 机器人相连,具有延迟低、稳定性高、不依赖外部网络的优势,是新手入门和底层调试的首选方式。 1.1 前置条件 所需物品说明开发电脑推荐安装 Ubuntu 20.04,或在 Windows 上使用虚拟机宇树 G1 机器人确保已开机且处于正常状态网线(

By Ne0inhk

0-1学习FPGA之底层资源——LUT

目录 碎碎念! 1. 什么是LUT? 2.  LUT 与传统逻辑门的对比 3. LUT可以怎么用? 3.1 实现组合逻辑 3.2 搭建LUT_RAM(分布式RAM) 3.3 移位寄存器 碎碎念! 开这个专栏是为了重新巩固基础,温故而知新,顺便查漏补缺弥补之前的知识漏洞~内容会尽量写的清晰,0基础的小伙伴也能看懂,欢迎交流~如有问题请艾特我^ w ^ 1. 什么是LUT? LUT(look-up table)就是查找表,是FPGA中实现组合逻辑功能的基本单元。可以看做一个小SRAM,存储逻辑真值表,输入看做地址线,读取出对应的逻辑输出结果。(后面有举例) LUT有LUT1、LUT2、LUT3、LUT4、LUT5、LUT6。分别指代的输入口的数量,输入口可以作为地址去读写"

By Ne0inhk