一,命令行参数
1.1main函数
1.1.1main函数不是程序的开始
在vim code.s我们打开汇编代码能发现

在Linux上是start,window上是_crt_start,这个函数内部调用main的逻辑如下
cpp
_start
{
int ret = 0;
int arg_count=0;
arg_count=3;
if(arg_count == 0)
ret = main();
else if(arg_count==2)
ret = main(argc, argv);
else
ret =main(argc, argv, env);
}
扫描我们写的main函数,然后通过词法语法分析得到count的参数,然后调用不同的main函数
1.1.2main函数的参数
实际上,在Linux系统级的编程中,真正的main函数其实是可以携带参数,常见的声明方式如下
cpp
int main(int argc, char *argv[]);
这两个参数在程序启动时由操作系统自动传递,其中argc是一个整形变量,用来记录命令行参数个数,而argv则是一个指向字符指针数组的指针,用来存储具体的命令行参数字符串
值得注意的是:
cpp
int main(int argc, char *argv[], char *env[]){
(void)argc;//为什么这么写: 应用一下变量,取消告警,
//你设置了但是没有用可能会发出警告
(void)argv;
(void)env;
}
1.2命令行参数的切分与表结构
当我们在终端输入一个诸如ls -a -b-c 的命令并且按下回车时,终端解释器bash会首先拿到这一串完整的输入字符串,随后,bash会以空格作为分隔符,将这个长字符串打散为数个子字符串
这些切分好的字符串随后会被填入一张表中,以上述命令为例,其系统内部构建的指针数组结构如下:
cpp
char *argv[]:
+---+ +----------------+
| 0 | -->| "./code" |
+---+ +----------------+
| 1 | -->| "a" |
+---+ +----------------+
| 2 | -->| "b" |
+---+ +----------------+
| 3 | -->| "c" |
+---+ +----------------+
| 4 | -->| NULL |
+---+ +----------------+
bash在切分子字符串并构建该数组时,汇总数组的末尾强制追加一个NULL指针作为结束标记,此时,参数的总个数argc的值为4
1.3命令行参数的应用演示
为什么要把命令行参数传给main函数,其根本目的在于实现一个程序不同子功能,在Linux中,我们经常使用ls -a ls -l本质都是通过解析命令行参数表来实现的
以下是演示:
cpp
#include <iostream>
#include <string>
using namespace std;
// 演示:如何遍历打印所有的命令行参数
void Test01(int _argc, char* _argv[])
{
for (int i = 0; i < _argc; i++)
{
cout << _argv[i] << endl;
}
}
// 演示:不同的功能是怎么通过命令行参数选项实现的
void Test02(int _argc, char* _argv[])
{
if (_argc != 2)
{
cout << "输入错误!请重新输入" << endl;
}
else
{
string cmd = _argv[1];
if (cmd == "a")
cout << "执行一号功能" << endl;
if (cmd == "b")
cout << "执行二号功能" << endl;
if (cmd == "c")
cout << "执行三号功能" << endl;
}
}
int main(int argc, char *argv[])
{
// Test01(argc, argv); // 取消注释可查看参数遍历结果
Test02(argc, argv);
return 0;
}

这段代码可以直观看出,./code自身作为_argv0也是一个命令行参数,通过接受并判断_argv1传入的选项,程序就能走向不同的分支,从而获得不同的子功能
一,引入环境变量
1.1基本概念
1.1.1环境变量基本概念
环境变量----一般指的是操作系统中用来指定操作系统运行环境的一些参数
如:我们在编写一些C/C++程序的时候,在链接的时候,从来不知道我们所链接的动静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器
环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性
1.1.2常见环境变量
PATH:指定命令的搜索路径
HOME:指定用户的主工作目录(即用户登录到Linux系统中时,默认的目录)
SHELL:当前shell,它通常是/bin/bash
1.1.3怎么看环境变量
|-----------|------------------------|
| 命令 | 作用 |
| echo $VAR | 快速精准查看某一个变量的值 |
| env | 观察所以环境变量,通常配合| grep过滤 |
| set | 查所有本地变量 |
| export | 不带参数可查看所以环境变量列表 |
1.1.4环境变量和本地变量的设置和导入
在Linux Shell中,变量主要分为本地变量(普通自定义变量)和环境变量(全局变量),理解它们以及如何转换是非常重要的
1.1.4.1概念对比
|------------|------------------|----------------|
| 变量类型 | 作用域 | 特点 |
| 本地变量 | 仅限当前shell | 子进程无法继承该变量 |
| 环境变量(全局变量) | 当前Shell进程及其所有子进程 | 全局有效,常用于传递系统配置 |
本地变量是给shell脚本局部使用的
bash如何使用本地变量?bash是一个交互式的命令行解释器
它本身也是一门解释性语言,这门语言它也有自己的词法语法分析我们的shell脚本就是这种语言的一种应用
cpp
[whb@bite-alicloud lesson15]$ i=0; while [ $i -le 10 ]; do echo $i; let i++; done
0
1
2
3
4
5
6
7
8
9
10
[whb@bite-alicloud lesson15]$
解释性语言:
不提前进行整体编译,是边读边执行,但是错误只有运行到那一行才会被发现,开发灵活,跨平台方便,但是通常依赖运行环境,速度会慢一些
shell脚本也可以这么写:我们用一个文件写下很多的命令,文件以#!/bash/bin开头,这就是shell脚本,我们很多配置文件就是用shell脚本写的
cpp
1: test.sh
1 #!/bin/bash
2
3 touch file
4 mv file myfile
5 i=100
6 echo $i

1.1.4.2变量的操作指令
1.定义本地变量
直接使用 变量名=值 可以定义本地变量
等号两边绝对不能有空格,如果值包含空格,需使用双引号包裹
bash
my_var="hello world"
i=10
2.export:导入为环境变量(全局变量)
使用export命令可以将一个本地变量提升为环境变量,或者在定义时直接声明为环境变量
先定义后导出,或者直接导出
bash
var1="ubuntu"
export var1
#或者:
export var2="centos"

3.unset 删除变量
使用unset命令可以清理不再需要的变量,无论是本地还是环境变量,都可以被删除
bash
# 删除刚才定义的环境变量
unset var2
# 验证是否删除(输出为空)
echo $var2
1.2main函数的第三个参数
main函数的完整形态实际上可以包含三个参数(最多三个,从父进程继承过来),其原型完全体代码如下:
bash
int main(int argc, char *argv[], char *env[]);
这第三个参数char *env\[\]就是环境变量表。与命令行参数表类似,它也是一个字符串指针数组,数组的每一个元素都指向一个形如NAME=VALUE的环境变量字符串,并且同样以NULL指针作为整张表的结尾
所以就有如下结论:一个进程内部有两张表:一张是命令行参数表,一张是环境变量表
父进程吧这两张表传递给了子进程
1.3为什么执行自己的程序必须加./
要想在Linux中执行一个可执行程序,核心前提只有一条:系统必须先找到它
当我们在键盘输入ls并且回车时,系统能够立马响应,是因为ls对应的而进程可执行文件原本就存放在系统的/usr/bin.目录下

为什么是usr/bin,不是别的目录
系统内部定义了一个名为PATH的全局环境变量,它专门用来存储系统搜索可执行指令的默认路径集合,我们可以通过在终端输入 echo $PATH来查看其内容
bash
[zbc@VM-0-9-opencloudos ~]$ echo $PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin
当我们在命令行输入一个指令时,命令解释器bash就会顺着PATH中配置路径,从左到右依次去对应目录下遍历,搜寻同名二进制文件,因此可以找到,我们自己的程序所在目录不在PATH里面所以找不到
为了解决这个问题,我们必须通过./显式告诉系统,在当前绝对路径/相对路径下查找并且运行该程序
二,环境变量的底层表结构和溯源
2.1环境变量表的组织形式
当我们通过终端登录Linux系统时,系统启动了一个bash进程,如果同时有10用户登录,系统内就会存在10个独立的bash程序,各自维护一套环境变量表,环境变量表以NULL结尾
bash
char *env[]:
+---+ +------------------------------------+
| 0 | -->| "XDG_SESSION_ID=14400" |
+---+ +------------------------------------+
| 1 | -->| "HOSTNAME=bite-alicloud" |
+---+ +------------------------------------+
| 2 | -->| "TERM=xterm" |
+---+ +------------------------------------+
| 3 | -->| "SHELL=/bin/bash" |
+---+ +------------------------------------+
| ...| -->| ... |
+---+ +------------------------------------+
| n | -->| "PATH=/usr/local/bin:/usr/bin:..." |
+---+ +------------------------------------+
|n+1| -->| NULL |
+---+ +------------------------------------+
2.2环境变量的内存级与磁盘级修改
有一种追加式的修改方式:
bash
[whb@bite-alicloud lesson15]$ PATH=$PATH:/home/whb/code/code/merge_class/lesson15
[whb@bite-alicloud lesson15]$ echo $PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/whb/.local/bin:/home/whb/bin:/home/whb/code/code/merge_class/lesson15
[whb@bite-alicloud lesson15]$
但是此时只需要把终端关闭,重连一下就恢复了
如果我想永久修改环境变量,应该怎么办
注意:这里只提供方法,一般情况下,不建议修改
为了搞懂底层原理,我们引出配置文件。每次我们重新打开终端,命令行解释器bash在被拉起的那一刻,会引到当前用户的家目录~底下去读取几个隐藏的脚本文件。执行一遍,然后在内存中动态构建初始环境遍历表。我们知道bash也是一个进程,这个初始环境变量就是bash进程内部的环境变量表,核心部分就是
bash
~/.bash_profile
~/.bashrc

不仅如此,~/.bashrc还会进一步去调用系统级别的/etc/bashrc,在这些玩家里,早就写好了默认的环境变量导出指令(比如export PATH=...)


.bash_profile调用.bashrc,.bashrc调用系统的.bashrc

设置完后重登机器就能生效了
三,获取环境变量的三种核心方式
3.1法一,通过main函数的第三个参数隐式获取
前面提到的main函数完全体参数,利用循环遍历该指针数组,直到遇到NULL为止(这个办法根本没人用):
cpp
#include <stdio.h>
int main(int argc, char *argv[], char *env[]){
for (int i = 0; env[i] != NULL; i++)
printf("env[%d]: %s\n", i, env[i]);
return 0;
}
3.2法二:通过全局第三方指针environ显示获取
如果不想修改main函数的参数声明,同时又想遍历完整的环境变量,C语言提供了一个全局的外部指针environ。它在<unistd.h>中声明(我们也要手动进行extern声明)
cpp
#include <stdio.h>
extern char **environ;
int main(){
for (int i = 0; environ[i] != NULL; i++)
printf("%s\n", environ[i]);
return 0;
}
为什么可以获取?因为这个environ指针是一个二级指针,其指向环境变量表的表头

3.3法三:通过系统调用API getenv定向获取
方法一虽然能拿到所有环境变量,但是不能获取某个特定的环境变量,此时可以使用C标准库提供的getenv接口:
cpp
#include <stdio.h>
#include <stdlib.h>
int main(){
char *user = getenv("USER");
if (user != NULL)
printf("Current user is: %s\n", user);
return 0;
}
3.4法四:子进程继承父进程获取环境变量
前三种办法关注的是如何在当前进程中读取环境变量,而方法四探讨的是环境变量的来源与传递机制,在Linux系统中,环境变量有全局性,这种全局性是通过子进程会默认继承父进程的环境变量表来体现的

3.4.1核心原理
当我们通过fork()系统调用创建子进程时,子进程会复制父进程的绝大部分数据,其中就包括环境变量表
**注意写时拷贝:**子进程在刚被创建时,其environ指针指向的内存空间与父进程完全一致
3.4.2示例代码
以下代码演示了父进程通过putenv或setenv自定义一个环境变量,然后通过fork创建子进程,子进程无需显示设置,便能直接继承并且读取该变量:
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
// 1. 父进程在自己的独立空间内设置一个自定义环境变量
// setenv 参数:键, 值, 是否覆盖(1表示覆盖)
setenv("MY_TEST_VAR", "Hello_Linux_Process", 1);
printf("[父进程] 成功设置环境变量 MY_TEST_VAR\n");
// 2. 创建子进程
pid_t id = fork();
if (id < 0) {
perror("fork failed");
return 1;
}
else if (id == 0) {
// 3. 子进程逻辑
printf("[子进程] 开始尝试获取父进程留下的遗产...\n");
// 子进程直接调用 getenv 获取父进程在 fork 前设置的变量
char *val = getenv("MY_TEST_VAR");
if (val != NULL) {
printf("[子进程] 成功继承! 获取到的值为: %s\n", val);
} else {
printf("[子进程] 未找到该环境变量。\n");
}
exit(0);
}
else {
// 父进程等待子进程退出
wait(NULL);
}
return 0;
}
四,其他环境变量
4.1所有环境变量罗列


4.2环境绑定与权限控制用法
在Linux环境编程中,我们可以通过获取进程的环境变量或用户的身份标识,来实现特定的用户权限绑定
4.2.1核心逻辑与安全防范
编写此类程序时,仅仅依赖getenv("USER")是不够安全的,原因如下:
1.环境变量可伪造:任何用户(尤其是root)都能在终端通过export USER = your_username临时修改变量,从而绕过简单的字符串检查
2.UID的唯一性:在Linux系统中,用户的UID是硬性绑定的,root用户的UID永远是0,通过getuid()系统调用可以获取真实的当前用户ID
4.2.1.1示例代码
cpp
#include <iostream>
#include <cstdlib> // 包含 getenv
#include <unistd.h> // 包含 getuid, geteuid
#include <string>
int main() {
// 1. 定义允许执行该程序的专属用户名
const std::string AUTHORIZED_USER = "your_username";
// 2. 获取当前环境变量中的 USER 值
const char* env_user = std::getenv("USER");
std::string current_user = env_user ? env_user : "";
// 3. 获取当前进程的实际用户 ID (UID)
uid_t current_uid = getuid();
// 4. 核心安全检查:
// - 检查环境变量 USER 是否匹配
// - 确保当前用户不是 root (root 的 UID 永远为 0)
if (current_user == AUTHORIZED_USER && current_uid != 0) {
// 正确的逻辑分支
std::cout << "[验证通过] 欢迎回来," << AUTHORIZED_USER << "!" << std::endl;
std::cout << "执行核心绝密逻辑... 正确答案是:42" << std::endl;
}
else {
// 其他人(包括 root)或环境变量被篡改时的分支
std::cout << "[错误] 权限不足:该程序已被锁定,仅限指定用户执行。" << std::endl;
// 故意输出误导性的错误信息或静默退出
return 1;
}
return 0;
}
4.2.2基于自定义环境变量的控制逻辑
除了系统自带的环境变量外,我们还可以通过自定义环境变量来控制程序行为
4.2.2.1实力代码
通过进程级的数据传递,来控制子进程的一些逻辑
cpp
#include <iostream>
#include <cstdlib> // 包含 getenv
#include <string>
int main() {
// 1. 获取自定义的 FLAG 环境变量
const char* env_flag = std::getenv("FLAG");
std::string flag_val = env_flag ? env_flag : "";
// 2. 控制逻辑:仅当 FLAG 显式设置为 "1" 时允许运行
if (flag_val == "1") {
std::cout << "[SUCCESS] FLAG 验证成功,程序开始正常运行!" << std::endl;
// 核心业务逻辑...
} else {
std::cout << "[ERROR] 缺失关键环境变量或参数不正确,程序终止。" << std::endl;
return 1;
}
return 0;
}