目录
一、基本概念及其核心作用
1、基本概念
2、核心作用
二、常见环境变量
三、查看环境变量方法
四、测试PATH
1、对比执行:./project和直接执行project的区别
2、思考:为何某些命令可直接执行而无需路径,但我们的二进制程序却需要指定完整路径?
3、PATH 解析
各目录的作用
关键点
1. 查找顺序
2. sbin vs bin
3. 用户级目录
4、方法一:将可执行程序拷贝到环境变量PATH的某一路径下
5、方式二:将可执行程序所在的目录导入到环境变量PATH当中
6、关于export PATH=$PATH:/home/hmz的一些小知识
具体解析
注意事项(重要!!!)
永久生效的方法(以 ~/.bashrc 为例):
五、测试HOME
1、分别以root和普通用户执行echo $HOME,对比输出差异
普通用户:
超级用户:
2、执行cd ~; pwd,理解~与$HOME的关系
1. ~ 的作用
2. $HOME 的作用
3. ~ 与 $HOME 的关系
4. 示例验证
脚本内容 (compare_home_tilde.sh)
使用方法
5. 注意事项
总结
六、测试SHELL
七、环境变量相关命令
1、echo:显示指定环境变量的值
2、export:设置新的环境变量
1️⃣ 设置临时环境变量
2️⃣ 查看变量值
3️⃣ 验证是环境变量(对子进程可见)
命令分解解释
为什么用这个命令测试 export?
4️⃣ 删除环境变量(下面会介绍)
5️⃣ 验证已删除
3、env:显示所有环境变量
环境变量说明
4、unset:删除环境变量
1. 添加测试环境变量
2. 验证变量是否存在
3. 使用 unset 清除变量
4. 再次验证变量
5. 检查是否从环境变量中移除
关键说明:
5、set:显示本地定义的Shell变量和环境变量
1. 查看所有变量(快速测试)
2. 开启/关闭调试模式
📌 总结
八、环境变量的组织方式
九、main函数的参数(超重点!!!)
1、无参数形式
2、带参数形式
参数解析:
argc(Argument Count)
argv(Argument Vector)
示例代码:
运行示例:
1. 参数切分的参与者
2. 关键细节
3、扩展:envp(环境变量,非标准)
注意事项:
4、编写命令选项选择
十、_start函数与main函数的关系
1、_start:程序的真正入口
2、main:开发者定义的入口
3、关键区别
4、示例流程
5. 特殊情况
6、总结
十一、通过代码获取环境变量
方法一:使用命令行第三个参数
方法二:通过第三方变量environ获取
十二、通过系统调用获取或设置环境变量
getenv 和 putenv 函数解析
1. getenv 函数(获取环境变量)
函数原型
功能
示例
输出示例
注意事项
2. putenv 函数(设置/修改环境变量)
函数原型
功能
示例
输出示例
注意事项
3. 主要区别
4. 综合示例
输出示例
5. 安全注意事项
6. 替代方案(POSIX)
示例
输出
7、总结
十三、环境变量通常具备全局属性
1、核心知识点
环境变量的全局特性
(1) 初次运行程序无输出
(2) 导出环境变量
(3) 再次运行程序输出结果
2、思考现象背后的原理:为什么会出现这种现象?
进程环境变量继承机制
为什么需要 export?
十四、环境变量和本地变量
1、作用范围
2、定义与导出方式
环境变量
本地变量
3、子进程继承性
4、常见用途
5、查看与删除
6、扩展知识
7、总结
十五、关于内置命令的必知知识
1、内置命令的特点
2、常见内置命令分类
1. 基础操作
2. 变量管理
3. 流程控制
4. 作业控制
3、如何判断命令是否为内置?
4、与外部命令的区别
5、注意事项
一、基本概念及其核心作用
1、基本概念
环境变量(Environment Variables)是操作系统中用于动态配置程序运行环境的一种机制。它们以键值对(Key-Value)的形式存储,可以被操作系统、应用程序或脚本读取和修改。
举例:
在C/C++编程时,虽然我们不知道动态/静态库的具体位置,但链接仍能成功,正是因为有环境变量帮助编译器定位这些库文件
-
定义:环境变量(environment variables)是操作系统或用户定义的用于指定运行环境参数的全局变量,用于存储系统路径、配置参数、临时数据等。
-
作用范围:
-
系统级:影响所有用户和进程(如
PATH
)。 -
用户级:仅对当前用户生效(如
HOME
)。 -
进程级:由父进程传递给子进程(如通过脚本设置的变量)。
-
2、核心作用
-
配置管理:避免硬编码路径或参数(如数据库连接字符串)。
-
跨进程通信:父进程通过环境变量向子进程传递信息。
-
系统行为控制:例如
NODE_ENV=production
决定 Node.js 运行模式。 -
简化命令:将常用目录加入
PATH
后可直接执行程序(如python
、git
)。
二、常见环境变量
变量名 | 作用 |
---|---|
PATH | 定义可执行文件的搜索路径,多个路径用分隔符(: 或; )分隔。 |
HOME | 当前用户的主目录路径(如 Linux 的 /home/user )。 |
USER /USERNAME | 当前登录的用户名。 |
TEMP /TMP | 临时文件目录路径(如 Windows 的 C:\Temp )。 |
LANG | 系统语言和编码(如 en_US.UTF-8 )。 |
JAVA_HOME | 指向 Java 安装路径,供开发工具(如 Maven)使用。 |
SHELL | 当前Shell,它的值通常是/bin/bash。 |
三、查看环境变量方法
我们可以通过echo命令来查看环境变量,方式如下:
echo $NAME //NAME为待查看的环境变量名称
例如,查看环境变量PATH:
echo $PATH
四、测试PATH
1、对比执行:./project
和直接执行project
的区别
如下,你是否想过,为什么直接输入ls
就能运行,而运行自己编译的程序却必须加上./
前缀?
2、思考:为何某些命令可直接执行而无需路径,但我们的二进制程序却需要指定完整路径?
系统默认能直接执行某些命令(如ls)而无需添加路径,是因为这些命令的存储位置已被记录在环境变量PATH中。当用户输入命令时,系统会自动搜索PATH中指定的目录来查找对应程序。
但对于用户自己创建的可执行程序,由于不在PATH包含的目录中,必须通过添加./前缀明确指明程序位于当前目录。我们可以通过查看PATH环境变量来确认系统默认的搜索路径:
环境变量PATH包含多个由冒号分隔的路径。执行ls命令时,Shell会按照从左到右的顺序依次在这些路径中进行查找。
3、PATH 解析
各目录的作用
目录 | 用途 | 适用场景 |
---|---|---|
/usr/local/bin | 手动安装的第三方软件 | make install 、手动编译的程序 |
/usr/bin | 系统核心命令和包管理器安装的软件 | ls 、grep 、python3 (系统自带) |
/usr/local/sbin | 手动安装的系统管理命令 | 如手动编译的 nginx 、redis-server |
/usr/sbin | 系统自带的管理员命令 | iptables 、useradd (通常需要 sudo ) |
~/.local/bin | 用户级 Python/Rust/Node.js 工具 | pip install --user 、cargo install |
~/bin | 用户自定义脚本和程序 | 个人使用的 Shell 脚本、手动放置的可执行文件 |
关键点
1. 查找顺序
-
Shell 会从左到右查找命令,第一个匹配到的可执行文件会被执行。
示例:-
如果
/usr/local/bin/python3
和/usr/bin/python3
同时存在,会优先使用/usr/local/bin/python3
。 -
如果
~/bin/ls
用户自定义脚本和程序)存在,它会覆盖系统的/usr/bin/ls(
系统核心命令和包管理器安装的软件)
(不建议这样做)。
-
2. sbin
vs bin
-
bin
(binary):普通用户可用的命令(如ls
、python3
)。 -
sbin
(system binary):系统管理命令,通常需要sudo
(如iptables
、useradd
)。
3. 用户级目录
-
~/.local/bin
-
由
pip install --user
、cargo install
、npm install -g
(部分情况)等工具使用。 -
如果命令无法运行,可能是
~/.local/bin
不在PATH
中(但你的环境已经包含)。
-
-
~/bin
-
用户自定义目录,适合存放个人脚本(如
mybackup.sh
)。 -
如果不存在,可以手动创建:
mkdir -p ~/bin echo 'export PATH=$PATH:~/bin' >> ~/.bashrc source ~/.bashrc
-
由于ls命令本身位于PATH的某个路径下,因此即使不指定完整路径,系统也能成功找到并执行该命令:
如何让我们的可执行程序也能不带路径直接运行?
有两种方法可以实现:
4、方法一:将可执行程序拷贝到环境变量PATH的某一路径下
因为系统会默认搜索PATH中的路径,所以只需把可执行文件放到PATH包含的任意目录下,之后就可以直接输入程序名运行了。
sudo cp project /usr/bin
5、方式二:将可执行程序所在的目录导入到环境变量PATH当中
将可执行程序所在的目录导入到环境变量PATH当中,这样可以当没有指定路径时,系统就会来到该目录下进行查找:
export PATH=$PATH:/home/hmz
将可执行程序所在的目录导入到环境变量PATH当中后(我们对比PATH原来和现在的样子,找出不同点),位于该目录下的可执行程序也就可以在不带路径的情况下执行了:
6、关于export PATH=$PATH:/home/hmz的一些小知识
这个命令的作用是将 /home/hmz
目录添加到系统的 PATH
环境变量中,使得在该目录下的可执行文件可以在任何位置直接运行,而不需要输入完整路径。
具体解析
-
export:
这是一个 shell 命令,用于设置或导出环境变量,使其对当前 shell 及其子进程生效。 -
PATH:PATH
是一个环境变量,存储了一系列目录路径,系统会在这些路径中查找可执行文件。多个路径之间用:
分隔。 -
$PATH:$PATH
表示当前PATH
变量的值。例如,默认情况下,PATH
可能包含/usr/local/bin:/usr/bin:/bin
等路径。 -
:/home/hmz:
这里将/home/hmz
追加到PATH
变量的末尾,用:
分隔。 -
PATH=$PATH:/home/hmz:
这个赋值语句将新的PATH
值设置为原来的PATH
加上:/home/hmz
。 -
export PATH=$PATH:/home/hmz:
最终,export
将这个修改后的PATH
变量导出,使其生效。
这样,当输入一个命令时,shell 会依次在下面的路径中查找可执行文件。
注意事项(重要!!!)
-
临时生效:这种方式只在当前 shell 会话中有效,关闭终端或新开终端后,
PATH
会恢复默认值。
如果要永久生效,可以将其添加到~/.bashrc
、~/.bash_profile
或/etc/environment
(系统级)中。 -
路径顺序:
PATH
的查找顺序是从左到右的。如果/home/hmz
下有与系统命令同名的可执行文件,可能会导致预期外的行为,所以最好不要有与系统命令同名的可执行文件。 -
安全性:不建议将当前目录
.
或普通用户目录加入PATH
,以免恶意程序覆盖系统命令,造成系统污染。
永久生效的方法(以 ~/.bashrc
为例):
echo 'export PATH=$PATH:/home/hmz' >> ~/.bashrc
source ~/.bashrc # 使更改立即生效
这样每次打开终端时,PATH
都会自动包含 /home/hmz
。
之后有空再探讨其他无需指定路径即可运行程序的方法,这个超纲了,现在我先学好要学的先,可以先看一下这些方法的表格:
方法 | 适用场景 | 持久性 | 是否需要修改 PATH |
---|---|---|---|
符号链接 | 单文件程序 | 永久 | 否(需链接到 PATH 目录) |
别名 | 简化长命令 | 需写入配置文件 | 否 |
函数 | 需要参数处理的复杂命令 | 需写入配置文件 | 否 |
桌面快捷方式 | GUI 程序 | 永久 | 否 |
环境变量扩展 | 临时替代长路径 | 可选 | 否 |
包管理器安装 | 系统级工具 | 永久 | 自动配置 |
标准化命名 | 个人脚本 | 永久 | 需 PATH 包含目录 |
exec 命令 | 包装脚本(无子进程开销) | 永久 | 需 PATH 包含目录 |
五、测试HOME
每个用户登录系统时都会自动进入自己的主工作目录(家目录),该路径存储在HOME环境变量中。
echo $HOME
1、分别以root和普通用户执行echo $HOME
,对比输出差异
普通用户:
超级用户:
2、执行cd ~; pwd
,理解~
与$HOME
的关系
在Linux操作系统中,~
(波浪号)和$HOME
环境变量之间存在紧密关联,它们都指向当前用户的家目录(Home Directory)。以下是具体解释和示例:
1. ~
的作用
-
~
是一个Shell扩展符号(称为Tilde Expansion
),默认代表当前用户的家目录路径。-
例如:
cd ~
等同于cd /home/username
(假设用户名是username
)。 -
若在
~
后接用户名(如~otheruser
),则指向其他用户的家目录(如/home/otheruser
)。(之前在cd命令的博客中没有提及到,现在补充上)
-
2. $HOME
的作用
-
$HOME
是一个环境变量,存储当前用户家目录的绝对路径。-
可通过
echo $HOME
查看其值,通常输出如/home/username
。 -
在脚本或命令中直接使用
$HOME
时,Shell会将其替换为实际路径。
-
3. ~
与 $HOME
的关系
-
等价性:在大多数情况下,
~
和$HOME
指向同一路径。-
执行
cd ~; pwd
和cd $HOME; pwd
的输出结果相同。
-
-
差异:
-
~
是Shell提供的快捷方式,由Shell在解析命令时直接扩展。 -
$HOME
是环境变量,通过变量替换机制实现。 -
某些场景下(如字符串中未引用的
~
),Shell可能不会展开~
,而$HOME
始终作为变量处理。
-
4. 示例验证
脚本内容 (compare_home_tilde.sh
)
#!/bin/bash# 比较 ~ 和 $HOME 是否相同
if [ ~ = "$HOME" ]; thenecho "They are identical: ~ points to $HOME"
elseecho "They are different: ~ is '~', but HOME is '$HOME'"
fi
关于脚本语言,我会在后续学习中更新,毕竟学会了命令而不会脚本语言有点缺失灵魂的感觉!!!
使用方法
-
将脚本保存为
compare_home_tilde.sh
-
赋予执行权限并运行:
chmod +x compare_home_tilde.sh ./compare_home_tilde.sh
5. 注意事项
-
引号的影响:
-
双引号内
$HOME
会被扩展,但~
不会。例如:echo "~" # 输出 ~ echo "$HOME" # 输出 /home/username
-
-
脚本可移植性:
-
在脚本中优先使用
$HOME
,因为其行为更明确;~
的展开可能受Shell配置影响。
-
总结
~
和$HOME
本质上是同一目标(用户家目录)的不同表示方式。理解它们的异同有助于在命令行或脚本中更灵活地操作路径。
六、测试SHELL
在Linux操作系统中,我们输入的各种命令需要通过命令行解释器执行。Linux支持多种命令行解释器(如bash、sh),可以通过查看环境变量SHELL来确认当前使用的解释器类型。
echo $SHELL
该命令行解释器实际上是系统内置的一条命令,当其运行成为进程后,便能提供命令行解释功能。
七、环境变量相关命令
1、echo:显示指定环境变量的值
2、export:设置新的环境变量
1️⃣ 设置临时环境变量
export MY_TEST="Hello World"
2️⃣ 查看变量值
echo $MY_TEST
输出:
3️⃣ 验证是环境变量(对子进程可见)
bash -c 'echo $MY_TEST'
输出:
命令分解解释
-
bash:
启动一个新的 Bash 子 Shell(子进程)。 -
-c:
让 Bash 执行后面跟着的命令字符串,而不是进入交互模式。 -
'echo $MY_TEST':
在新启动的 Bash 子进程中运行echo $MY_TEST
,即打印MY_TEST
变量的值。
为什么用这个命令测试 export
?
-
环境变量的核心特性:
使用export
设置的变量会传递给子进程(如新启动的 Shell、脚本或其他程序)。
如果直接echo $MY_TEST
,只能证明变量在当前 Shell 存在,但无法确认它是否真的是环境变量(即是否能被子进程继承)。 -
这个测试的作用:
通过bash -c
启动一个全新的子 Shell,检查MY_TEST
是否能被子进程访问:-
如果输出
Hello World
→ 说明export
成功(变量是环境变量)。 -
如果无输出 → 说明变量未被
export
(只是当前 Shell 的局部变量)。
-
4️⃣ 删除环境变量(下面会介绍)
unset MY_TEST
5️⃣ 验证已删除
echo $MY_TEST
(无输出,变量已不存在)
3、env:显示所有环境变量
环境变量说明
环境变量名称 | 作用描述 |
---|---|
XDG_SESSION_ID | 当前用户会话的唯一标识符,通常由系统管理工具(如 systemd)分配 |
HOSTNAME | 当前系统的主机名 |
TERM | 终端类型(如 xterm),用于控制终端的行为和显示方式 |
SHELL | 当前用户使用的默认 Shell 程序路径(如 /bin/bash) |
HISTSIZE | Shell 历史记录中保存的命令数量上限(如 10000) |
SSH_CLIENT | SSH 客户端连接信息,格式为 IP 源端口 目标端口(如 183.20.145.59 61461 22) |
SSH_TTY | SSH 连接的终端设备文件路径(如 /dev/pts/1) |
USER | 当前登录的用户名(如 hmz) |
LD_LIBRARY_PATH | 动态链接库(.so 文件)的额外搜索路径,优先级高于系统默认路径 |
LS_COLORS | 定义 ls 命令输出中不同文件类型的显示颜色(如目录、压缩包、图片等) |
当前用户的邮件存储路径(如 /var/spool/mail/hmz) | |
PATH | 系统查找可执行文件的路径列表,用冒号分隔 |
PWD | 当前工作目录的路径(如 /home/hmz) |
LANG | 系统语言和字符编码设置(如 en_US.UTF-8) |
HISTCONTROL | 控制 Shell 历史记录的存储方式(如 ignoredups 表示忽略重复命令) |
SHLVL | Shell 层级,表示当前 Shell 嵌套深度(初始为 1,每启动一个子 Shell 加 1) |
HOME | 当前用户的主目录路径(如 /home/hmz) |
LOGNAME | 登录用户名(通常与 USER 相同) |
SSH_CONNECTION | SSH 连接的详细信息,包括客户端和服务端的 IP 及端口 |
LESSOPEN | 指定 less 命令的预处理脚本(如 /usr/bin/lesspipe.sh %s ) |
XDG_RUNTIME_DIR | 用户运行时文件(如套接字、PID 文件)的存储目录(如 /run/user/1000) |
HISTTIMEFORMAT | 定义 Shell 历史记录中时间戳的格式(如 %F %T hmz 表示年-月-日 时:分:秒) |
4、unset:删除环境变量
1. 添加测试环境变量
export TEST_VAR="Hello, this is a test variable"
2. 验证变量是否存在
echo $TEST_VAR
输出:
3. 使用 unset
清除变量
unset TEST_VAR
4. 再次验证变量
echo $TEST_VAR
输出:(空,因为变量已被删除)
5. 检查是否从环境变量中移除
env | grep TEST_VAR
输出:(空,表示环境变量列表中已不存在)
关键说明:
-
export
将变量提升为环境变量(对子进程可见) -
unset
彻底删除变量(无论是普通变量还是环境变量) -
测试时建议用新变量名(如
TEST_VAR
),避免误删重要变量
5、set:显示本地定义的Shell变量和环境变量
set
命令不能直接添加环境变量,它的主要用途是 设置Shell内部选项 或 显示所有变量(包括环境变量和局部变量)。
1. 查看所有变量(快速测试)
# 设置一个普通变量
TEST_VAR="hello"# 用 set 查看变量(过滤显示 TEST_VAR)
set | grep TEST_VAR
输出:
2. 开启/关闭调试模式
# 开启调试(显示每条执行的命令)
set -x# 执行一个命令(会看到详细输出)
echo "This is a test"# 关闭调试
set +x
输出:
📌 总结
-
set
直接使用 → 显示所有变量(内容很多,建议用grep
过滤)。 -
set -x
→ 调试脚本时显示命令执行过程。 -
set +x
→ 关闭调试。
八、环境变量的组织方式
在系统当中,环境变量的组织方式如下:
每个程序都会接收一个环境变量表,该表是一个字符指针数组,每个指针指向一个以'\0'结尾的环境字符串,最后一个字符指针为空。
九、main函数的参数(超重点!!!)
在C和C++编程中,main
函数是程序的入口点,它可以接收来自命令行的参数。main
函数的参数通常有两种形式:
1、无参数形式
int main(void)
{// 程序代码return 0;
}
这种形式表示 main
函数不接受任何参数。
2、带参数形式
int main(int argc, char *argv[])
{// 程序代码return 0;
}
或等价的:
int main(int argc, char **argv)
{// 程序代码return 0;
}
参数解析:
-
argc
(Argument Count)-
表示命令行参数的数量(包括程序名本身)。
-
类型是
int
。 -
例如:运行
./program arg1 arg2
,argc
的值为 3(./program
、arg1
、arg2
)。
-
-
argv
(Argument Vector)-
是一个指向字符串数组的指针,存储了所有的命令行参数。
-
argv[0]
是程序名称(可能包含路径)。 -
argv[1]
到argv[argc-1]
是用户输入的参数。 -
argv[argc]
是NULL
(C标准保证)。
-
示例代码:
#include <stdio.h>int main(int argc, char *argv[])
{printf("程序名称: %s\n", argv[0]);printf("参数个数: %d\n", argc - 1);for (int i = 1; i < argc; i++) {printf("参数 %d: %s\n", i, argv[i]);}return 0;
}
运行示例:
假设编译后的程序名为 demo
,运行:
./demo hello world 123
输出:
图中的参数(argv
数组)是由 Shell 和操作系统 在程序启动时自动切分的。具体过程如下:
1. 参数切分的参与者
-
Shell(命令行解释器):
当用户在终端输入命令(如./demo hello world 123
)时,(父进程)Shell 会按空格将字符串拆分成多个单词(./demo
、hello
、world
、123
)。 -
操作系统(内核):
当 Shell 调用execve()
等系统调用启动程序时,内核会将拆分后的参数以char* argv[]
数组的形式传递给新程序的入口(如main
函数)。
2. 关键细节
-
空格作为分隔符:
Shell 默认按空格切分参数。若参数包含空格,需用引号包裹(如./demo "hello world"
)。 -
argv[0]
的特殊性:
通常是程序名(./demo
),但可通过execve()
修改(如符号链接调用时可能不同)。 -
NULL
结尾:
方便遍历时确定参数列表的结束(类似 C 字符串的\0
)。
3、扩展:envp
(环境变量,非标准)
某些编译器(如GCC)支持第三个参数 envp
,用于访问环境变量:
#include<stdio.h>int main(int argc, char *argv[], char *envp[])
{int i = 0;// envp 存储环境变量,以NULL结尾for (; envp[i] != NULL; i++) {printf("环境变量 %d: %s\n", i, envp[i]);}return 0;
}
这段代码用于打印当前进程的所有环境变量:
但更可移植的方法是使用 getenv
函数(<stdlib.h>
)。(后面会讲解)
注意事项:
-
参数顺序:
argc
必须在argv
之前。 -
参数名称:
argc
和argv
是约定俗成的命名,但可以自定义(如int main(int count, char **args)
)。 -
返回值:通常返回
0
表示成功,非零值表示错误(由操作系统或调用者处理)。
通过 main
函数的参数,程序可以灵活地接收外部输入,实现命令行交互功能。
4、编写命令选项选择
我们可以编写一段简单的代码,根据用户选择的选项显示不同的提示信息:
#include <stdio.h>
#include <string.h>int main(int argc, char *argv[], char* envp[])
{if(argc > 1){if(strcmp(argv[1], "-a") == 0){printf("you used -a option...\n");}else if(strcmp(argv[1], "-b") == 0){printf("you used -b option...\n");}else{printf("you used unrecognizable option...\n");}}else{printf("you did not use any option...\n");}return 0;
}
代码运行结果如下:
由此,我们可以得到一个结论:
指令选项的实现原理:main的命令行参数,是实现程序不同子功能的方法!!!
十、_start函数与main函数的关系
在C/C++程序中,_start
和 main
函数的关系是程序执行流程的关键环节。
1、_start:程序的真正入口
-
系统级入口:当操作系统加载程序后,首先执行的是
_start
(由C运行时库或链接器默认提供),而非直接跳转到main
。它是二进制程序的实际入口点(可通过链接脚本或编译器选项修改)。 -
职责:
-
初始化全局变量、堆栈、内存管理等运行时环境。
-
设置命令行参数(
argc
,argv
)和环境变量。 -
调用
main
函数,并将参数传递给它。
-
2、main:开发者定义的入口
-
用户级入口:
main
是程序员编写的程序逻辑起点,但它的调用是由_start
发起的。 -
执行流程:
_start
→ 初始化代码 →main(argc, argv, envp)
→ 程序逻辑 → 返回到_start
→ 调用exit
结束进程。
3、关键区别
特性 | _start | main |
---|---|---|
定义者 | 编译器/运行时库(如glibc的crt0 ) | 开发者 |
可见性 | 隐藏(通常无需手动实现) | 必须显式定义 |
返回值 | 直接调用系统退出(如exit ) | 返回值由 _start 传递给系统 |
4、示例流程
// 1. _start (隐藏的初始化)
void _start() {// 初始化环境int argc = ...;char **argv = ...;// 调用mainint ret = main(argc, argv);// 退出程序exit(ret);
}// 2. 用户编写的main
int main(int argc, char **argv) {printf("Hello, World!\n");return 0;
}
5. 特殊情况
-
嵌入式系统:可能直接由汇编代码跳转到
main
,跳过部分初始化。 -
绕过main:可通过修改链接参数指定其他入口函数(如
_start
调用my_main
而非main
)。
6、总结
_start
是系统与用户代码之间的桥梁,负责准备执行环境并启动 main
;main
则是开发者控制程序逻辑的入口。两者共同构成程序的完整生命周期。
十一、通过代码获取环境变量
方法一:使用命令行第三个参数
#include <stdio.h>int main(int argc, char *argv[], char *env[])
{for(int i = 0; env[i]; i++) {printf("%s\n", env[i]);}return 0;
}
方法二:通过第三方变量environ获取
#include <stdio.h>int main(int argc, char *argv[])
{extern char **environ;int i = 0;for(; environ[i]; i++) {printf("%s\n", environ[i]);}return 0;
}
注意:
libc中定义的全局变量environ指向环境变量表。由于environ未包含在任何头文件中,使用时需要用extern声明。
十二、通过系统调用获取或设置环境变量
除了通过main函数的第三个参数和environ全局变量获取环境变量外,系统还提供了getenv函数来查询环境变量。
getenv
和 putenv
函数解析
在 C 语言中,getenv
和 putenv
是用于操作环境变量的两个标准库函数(定义在 <stdlib.h>
中)。它们的作用不同,但都与环境变量的读取和修改相关。
1. getenv
函数(获取环境变量)
函数原型
char *getenv(const char *name);
功能
-
用于获取指定名称(
name
)的环境变量的值。 -
如果环境变量存在,返回其值的字符串指针(
char*
)。 -
如果环境变量不存在,返回
NULL
。
示例
#include <stdio.h>
#include <stdlib.h>int main()
{const char *path = getenv("PATH");if (path != NULL) {printf("PATH 的值是: %s\n", path);} else {printf("PATH 未设置\n");}return 0;
}
输出示例
注意事项
-
返回的字符串是只读的,不能直接修改(否则可能导致未定义行为)。
-
如果环境变量不存在,
getenv
返回NULL
,因此使用前应检查返回值。
2. putenv
函数(设置/修改环境变量)
函数原型
int putenv(char *string);
功能
-
用于设置或修改环境变量。
-
参数
string
必须是"NAME=value"
形式的字符串。 -
成功返回
0
,失败返回非0
(具体实现可能不同)。
示例
#include <stdio.h>
#include <stdlib.h>int main()
{// 设置或修改环境变量if (putenv("MY_VAR=HelloWorld") != 0) {perror("putenv 失败");return 1;}// 使用 getenv 获取新设置的环境变量const char *my_var = getenv("MY_VAR");if (my_var != NULL) {printf("MY_VAR 的值是: %s\n", my_var);} else {printf("MY_VAR 未设置\n");}return 0;
}
输出示例
注意事项
-
putenv
的参数格式必须是"NAME=value"
,否则可能不会生效。 -
修改后的环境变量会影响当前进程及其子进程,但不会影响父进程(如 Shell)。
-
在 Windows 上,类似的函数是
_putenv
(兼容性问题)。
3. 主要区别
函数 | 作用 | 参数格式 | 返回值 | 是否修改环境变量 |
---|---|---|---|---|
getenv | 获取环境变量的值 | "VAR_NAME" | char* (值)或 NULL | ❌ 只读 |
putenv | 设置/修改环境变量 | "VAR_NAME=value" | 0 (成功)或非 0 (失败) | ✅ 修改 |
4. 综合示例
#include <stdio.h>
#include <stdlib.h>int main()
{// 获取当前 PATHconst char *old_path = getenv("PATH");printf("原 PATH: %s\n", old_path);// 修改 PATH(追加一个新路径)putenv("PATH=/new/path:/usr/local/bin");// 再次获取 PATHconst char *new_path = getenv("PATH");printf("新 PATH: %s\n", new_path);return 0;
}
输出示例
5. 安全注意事项
-
getenv
返回值不可修改char *path = getenv("PATH"); // path[0] = 'X'; // 错误!可能导致程序崩溃
-
putenv
的字符串必须是持久存储的char env_str[] = "TMP_VAR=123"; putenv(env_str); // 可以 // 但如果 env_str 是局部变量,函数返回后可能失效
-
跨平台兼容性
-
Windows 使用
_putenv
而不是putenv
(需#include <stdlib.h>
)。 -
更安全的替代方案:
setenv
和unsetenv
(POSIX 标准,但 Windows 不支持)。
-
6. 替代方案(POSIX)
如果运行在 Linux/macOS(POSIX 环境),可以使用更安全的:
-
setenv("NAME", "value", 1)
:设置环境变量(1
表示覆盖,0
表示不覆盖)。 -
unsetenv("NAME")
:删除环境变量。
示例
#include <stdlib.h>
#include <stdio.h>int main()
{setenv("MY_VAR", "Hello", 1); // 设置或覆盖printf("MY_VAR = %s\n", getenv("MY_VAR"));unsetenv("MY_VAR"); // 删除printf("MY_VAR = %s\n", getenv("MY_VAR") ? getenv("MY_VAR") : "(null)");return 0;
}
输出
7、总结
操作 | 推荐函数 | 说明 |
---|---|---|
获取环境变量 | getenv | 返回 char* ,需检查 NULL |
设置/修改环境变量 | putenv (Windows: _putenv ) | 格式 "NAME=value" |
更安全的设置(POSIX) | setenv | 可控制是否覆盖 |
删除环境变量(POSIX) | unsetenv | Windows 无直接等效函数 |
十三、环境变量通常具备全局属性
1、核心知识点
环境变量的全局特性
-
定义:环境变量通过
export
导出后,具备全局性,可被当前Shell及其所有子进程(如运行的脚本、编译的程序)继承。 -
关键规则:
-
子进程会复制父进程的环境变量表(
environ
),但无法反向修改父进程的环境。
-
环境变量具有全局特性,可以被子进程继承,通过下面的代码编译出来的程序可以验证:
#include <stdio.h>
#include <stdlib.h>int main()
{char *env = getenv("MYENV");if(env) {printf("%s\n", env);}return 0;
}
(1) 初次运行程序无输出
原因:未通过 export
设置 MYENV
,该环境变量不存在于当前Shell及其子进程的环境中。
(2) 导出环境变量
export MYENV="hello world" # 设置为全局环境变量
作用:MYENV
被添加到Shell的环境变量表中,后续所有子进程均可继承。
(3) 再次运行程序输出结果
原因:子进程(a.out
)继承了父Shell(Bash)的环境变量表,getenv("MYENV")
成功读取到值。
2、思考现象背后的原理:为什么会出现这种现象?
进程环境变量继承机制
-
父进程传递:当父进程(如Bash)调用子进程(如C程序)时,会将自己的环境变量表(键值对数组)复制给子进程。
-
C程序获取方式:
-
getenv("VAR")
:从子进程的环境变量表中查找指定变量。 -
extern char **environ
:直接访问环境变量表(全局变量)。
-
为什么需要 export
?
普通变量(未 export
)仅存在于Shell进程内部,不会写入环境变量表,因此子进程无法通过 getenv()
获取。
十四、环境变量和本地变量
在Bash中,变量分为环境变量和本地变量(也称为Shell变量),它们的核心区别在于作用域和是否被子进程继承。以下是详细对比:
1、作用范围
类型 | 作用域 | 是否全局 |
---|---|---|
环境变量 | 当前Shell及其所有子进程 | 全局有效(跨进程) |
本地变量 | 仅当前Shell进程内部 | 局部有效(不跨进程) |
2、定义与导出方式
环境变量
-
通过
export
命令导出,子进程(如脚本、其他程序)可继承:export MY_ENV_VAR="value" # 定义为环境变量
-
或分两步:
MY_ENV_VAR="value" export MY_ENV_VAR
本地变量
-
直接赋值,仅当前Shell可用:
MY_LOCAL_VAR="local_value" # 定义为本地变量
3、子进程继承性
类型 | 子进程是否可见 | 示例验证 |
---|---|---|
环境变量 | ✅ 是 | bash -c 'echo $MY_ENV_VAR' |
本地变量 | ❌ 否 | bash -c 'echo $MY_LOCAL_VAR' |
示例:
export ENV_VAR="global"
LOCAL_VAR="local"# 子进程(新bash)测试
bash -c 'echo "子进程环境变量: $ENV_VAR"; echo "子进程本地变量: $LOCAL_VAR"'
输出:
4、常见用途
-
环境变量:
-
配置程序运行环境(如
PATH
,HOME
)。 -
在脚本间传递参数。
-
-
本地变量:
-
临时存储脚本内部使用的数据。
-
避免污染子进程环境(如循环计数器)。
-
5、查看与删除
-
查看所有变量:
set # 显示所有变量(包括本地和环境) env # 仅显示环境变量
-
删除变量:
unset MY_VAR # 删除变量(无论环境或本地)
6、扩展知识
-
持久化环境变量:
若需永久生效,需写入Shell配置文件(如~/.bashrc
或~/.profile
)。 -
函数中的局部变量:
在函数内使用local
声明变量,作用域仅限于函数:func() {local LOCAL_IN_FUNC="secret"echo $LOCAL_IN_FUNC }
7、总结
-
环境变量是“全局护照”,子进程可继承;本地变量是“临时便签”,仅限当前Shell。
-
通过
export
控制变量的可见性,合理使用两者能有效管理脚本的执行环境。
十五、关于内置命令的必知知识
在 Shell 中,内置命令(Built-in Commands)是直接集成在 Shell 解释器(如 Bash、Zsh 等)中的命令,无需调用外部程序或创建子进程即可执行。
1、内置命令的特点
-
不创建子进程
直接由当前 Shell 进程执行,效率更高(例如cd
需要改变当前 Shell 的工作目录,若通过子进程执行则无法生效)。 -
操作本地变量和环境变量
可以直接读取或修改当前 Shell 的变量(如set
,export
,read
)。 -
无需外部二进制文件
不依赖PATH
环境变量,即使系统文件损坏也能使用。 -
优先级高于外部命令
当内置命令与外部命令同名时,优先执行内置命令(可通过command
或完整路径调用外部命令)。
2、常见内置命令分类
1. 基础操作
命令 | 功能描述 | 示例 |
---|---|---|
cd | 切换工作目录(必须内置,否则无法影响当前 Shell) | cd /tmp |
echo | 输出字符串(支持 -e 解析转义符等特性) | echo -e "Line1\nLine2" |
pwd | 打印当前目录(内置版本可避免符号链接问题) | pwd -P (显示物理路径) |
read | 从标准输入读取数据并赋值给变量 | read -p "Name: " username |
2. 变量管理
命令 | 功能描述 | 示例 |
---|---|---|
set | 设置/显示 Shell 变量和选项(如 set -x 开启调试模式) | set -o vi (启用 vi 编辑模式) |
unset | 删除变量或函数 | unset PATH |
export | 将变量提升为环境变量(供子进程使用) | export VAR=value |
local | 在函数内定义局部变量(仅限函数作用域) | local var="temp" |
3. 流程控制
命令 | 功能描述 | 示例 |
---|---|---|
exit | 退出当前 Shell 或脚本(可指定退出状态码) | exit 1 (异常退出) |
return | 从函数中返回(可带状态码) | return 0 (函数成功返回) |
source /. | 在当前 Shell 执行脚本(不创建子进程,影响当前环境) | source ~/.bashrc |
alias | 定义命令别名 | alias ll='ls -l' |
unalias | 删除别名 | unalias ll |
4. 作业控制
命令 | 功能描述 | 示例 |
---|---|---|
jobs | 查看后台运行的作业列表 | jobs -l (显示 PID) |
fg | 将后台作业切换到前台继续运行 | fg %1 (恢复作业 1) |
bg | 将暂停的作业放到后台运行 | bg %2 (后台运行作业 2) |
wait | 等待指定作业或所有后台作业完成 | wait %1 (等待作业 1 完成) |
3、如何判断命令是否为内置?
使用 type
命令检测:
type cd # 输出: cd is a shell builtin
type ls # 输出: ls is /usr/bin/ls (外部命令)
4、与外部命令的区别
特性 | 内置命令 | 外部命令 |
---|---|---|
执行方式 | 由 Shell 直接解释 | 需创建子进程,通过 PATH 查找二进制文件 |
变量操作 | 可修改当前 Shell 环境 | 仅继承环境变量,无法修改父 Shell 变量 |
速度 | 更快(无进程创建开销) | 相对较慢 |
依赖项 | 不依赖外部文件 | 依赖系统二进制文件 |
5、注意事项
-
特殊的内置命令
某些命令既有内置实现也有外部实现(如echo
、printf
)。内置版本可能支持扩展功能,而外部版本更符合 POSIX 标准。强制调用外部版本:/bin/echo "Hello" # 显式调用外部命令 command echo "Hello" # 绕过别名和内置命令
-
脚本可移植性
内置命令的行为可能因 Shell 不同(如 Bash 和 Dash)而有差异,需测试兼容性。 -
性能敏感场景
在循环中优先使用内置命令(如(( i++ ))
替代expr i + 1
)。