当前位置: 首页 > news >正文

深入解析环境变量:从基础概念到系统级应用

目录

一、基本概念及其核心作用

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 后可直接执行程序(如 pythongit)。


二、常见环境变量

变量名作用
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系统核心命令和包管理器安装的软件lsgreppython3(系统自带)
/usr/local/sbin手动安装的系统管理命令如手动编译的 nginxredis-server
/usr/sbin系统自带的管理员命令iptablesuseradd(通常需要 sudo
~/.local/bin用户级 Python/Rust/Node.js 工具pip install --usercargo 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):普通用户可用的命令(如 lspython3)。

  • sbin(system binary):系统管理命令,通常需要 sudo(如 iptablesuseradd)。

3. 用户级目录

  • ~/.local/bin

    • 由 pip install --usercargo installnpm 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 环境变量中,使得在该目录下的可执行文件可以在任何位置直接运行,而不需要输入完整路径。

具体解析

  1. export:这是一个 shell 命令,用于设置或导出环境变量,使其对当前 shell 及其子进程生效。

  2. PATH:PATH 是一个环境变量,存储了一系列目录路径,系统会在这些路径中查找可执行文件。多个路径之间用 : 分隔。

  3. $PATH:$PATH 表示当前 PATH 变量的值。例如,默认情况下,PATH 可能包含 /usr/local/bin:/usr/bin:/bin 等路径。

  4. :/home/hmz:这里将 /home/hmz 追加到 PATH 变量的末尾,用 : 分隔。

  5. PATH=$PATH:/home/hmz:这个赋值语句将新的 PATH 值设置为原来的 PATH 加上 :/home/hmz

  6. 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

        关于脚本语言,我会在后续学习中更新,毕竟学会了命令而不会脚本语言有点缺失灵魂的感觉!!!

使用方法

  1. 将脚本保存为 compare_home_tilde.sh

  2. 赋予执行权限并运行:

    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'

输出:

命令分解解释

  1. bash:启动一个新的 Bash 子 Shell(子进程)。

  2. -c:让 Bash 执行后面跟着的命令字符串,而不是进入交互模式。

  3. '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)
HISTSIZEShell 历史记录中保存的命令数量上限(如 10000)
SSH_CLIENTSSH 客户端连接信息,格式为 IP 源端口 目标端口(如 183.20.145.59 61461 22)
SSH_TTYSSH 连接的终端设备文件路径(如 /dev/pts/1)
USER当前登录的用户名(如 hmz)
LD_LIBRARY_PATH动态链接库(.so 文件)的额外搜索路径,优先级高于系统默认路径
LS_COLORS定义 ls 命令输出中不同文件类型的显示颜色(如目录、压缩包、图片等)
MAIL当前用户的邮件存储路径(如 /var/spool/mail/hmz)
PATH系统查找可执行文件的路径列表,用冒号分隔
PWD当前工作目录的路径(如 /home/hmz)
LANG系统语言和字符编码设置(如 en_US.UTF-8)
HISTCONTROL控制 Shell 历史记录的存储方式(如 ignoredups 表示忽略重复命令)
SHLVLShell 层级,表示当前 Shell 嵌套深度(初始为 1,每启动一个子 Shell 加 1)
HOME当前用户的主目录路径(如 /home/hmz)
LOGNAME登录用户名(通常与 USER 相同)
SSH_CONNECTIONSSH 连接的详细信息,包括客户端和服务端的 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;
}

参数解析:

  1. argc(Argument Count)

    • 表示命令行参数的数量(包括程序名本身)。

    • 类型是 int

    • 例如:运行 ./program arg1 arg2argc 的值为 3(./programarg1arg2)。

  2. 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 会按空格将字符串拆分成多个单词(./demohelloworld123)。

  • 操作系统(内核)
    当 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>)。(后面会讲解)

注意事项:

  1. 参数顺序argc 必须在 argv 之前。

  2. 参数名称argc 和 argv 是约定俗成的命名,但可以自定义(如 int main(int count, char **args))。

  3. 返回值:通常返回 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。它是二进制程序的实际入口点(可通过链接脚本或编译器选项修改)。

  • 职责

    • 初始化全局变量、堆栈、内存管理等运行时环境。

    • 设置命令行参数(argcargv)和环境变量。

    • 调用 main 函数,并将参数传递给它。

2、main:开发者定义的入口

  • 用户级入口main 是程序员编写的程序逻辑起点,但它的调用是由 _start 发起的。

  • 执行流程
    _start → 初始化代码 → main(argc, argv, envp) → 程序逻辑 → 返回到 _start → 调用 exit 结束进程。

3、关键区别

特性_startmain
定义者编译器/运行时库(如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 是系统与用户代码之间的桥梁,负责准备执行环境并启动 mainmain 则是开发者控制程序逻辑的入口。两者共同构成程序的完整生命周期。


十一、通过代码获取环境变量

方法一:使用命令行第三个参数

#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. 安全注意事项

  1. getenv 返回值不可修改

    char *path = getenv("PATH");
    // path[0] = 'X';  // 错误!可能导致程序崩溃
  2. putenv 的字符串必须是持久存储的

    char env_str[] = "TMP_VAR=123";
    putenv(env_str);  // 可以
    // 但如果 env_str 是局部变量,函数返回后可能失效
  3. 跨平台兼容性

    • 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)unsetenvWindows 无直接等效函数

十三、环境变量通常具备全局属性

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、常见用途

  • 环境变量

    • 配置程序运行环境(如 PATHHOME)。

    • 在脚本间传递参数。

  • 本地变量

    • 临时存储脚本内部使用的数据。

    • 避免污染子进程环境(如循环计数器)。

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、内置命令的特点

  1. 不创建子进程
    直接由当前 Shell 进程执行,效率更高(例如 cd 需要改变当前 Shell 的工作目录,若通过子进程执行则无法生效)

  2. 操作本地变量和环境变量
    可以直接读取或修改当前 Shell 的变量(如 setexportread)。

  3. 无需外部二进制文件
    不依赖 PATH 环境变量,即使系统文件损坏也能使用。

  4. 优先级高于外部命令
    当内置命令与外部命令同名时,优先执行内置命令(可通过 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、注意事项

  1. 特殊的内置命令
    某些命令既有内置实现也有外部实现(如 echoprintf)。内置版本可能支持扩展功能,而外部版本更符合 POSIX 标准。强制调用外部版本:

    /bin/echo "Hello"  # 显式调用外部命令
    command echo "Hello"  # 绕过别名和内置命令
  2. 脚本可移植性
    内置命令的行为可能因 Shell 不同(如 Bash 和 Dash)而有差异,需测试兼容性。

  3. 性能敏感场景
    在循环中优先使用内置命令(如 (( i++ )) 替代 expr i + 1)。


文章转载自:
http://britska.zzgtdz.cn
http://booklore.zzgtdz.cn
http://bitsy.zzgtdz.cn
http://bramley.zzgtdz.cn
http://chaucerian.zzgtdz.cn
http://amphithecium.zzgtdz.cn
http://benignantly.zzgtdz.cn
http://chemistry.zzgtdz.cn
http://beddo.zzgtdz.cn
http://chic.zzgtdz.cn
http://astonishment.zzgtdz.cn
http://artifact.zzgtdz.cn
http://anend.zzgtdz.cn
http://apomorphine.zzgtdz.cn
http://amphithecium.zzgtdz.cn
http://acidly.zzgtdz.cn
http://amethopterin.zzgtdz.cn
http://caretaker.zzgtdz.cn
http://chinchona.zzgtdz.cn
http://bloop.zzgtdz.cn
http://banzai.zzgtdz.cn
http://ageratum.zzgtdz.cn
http://chanteyman.zzgtdz.cn
http://animadvert.zzgtdz.cn
http://barilla.zzgtdz.cn
http://caparison.zzgtdz.cn
http://antifederal.zzgtdz.cn
http://breadbox.zzgtdz.cn
http://carved.zzgtdz.cn
http://ceeb.zzgtdz.cn
http://www.dtcms.com/a/280100.html

相关文章:

  • 用uniapp开发鸿蒙应用(暂停更新-根据项目更新,现在项目未开始)
  • QT简介和QT环境搭建
  • JVM——JVM 的内存区域是如何划分的?
  • Go从入门到精通(24) - 一个简单web项目-添加redis缓存
  • 教育培训机构如何为课程视频添加防盗录的强水印?
  • IPM31主板E3300usb键盘鼠标安装成功Sata接口硬盘IDE模式server2003-nt-5.2.3790
  • AI生成代码示例
  • 【自学linux】计算机体系结构和操作系统第二章
  • LangChain面试内容整理-知识点18:Chroma 向量数据库集成
  • 3.1k star!推荐一款开源基于AI实现的浏览器自动化插件工具 !
  • 蓝牙信号强度(RSSI)与链路质量(LQI)的测量与应用:面试高频考点与真题解析
  • GitCode疑难问题诊疗技术文章大纲
  • 3种添加视频水印的加密方式,守护视频安全!
  • 音视频学习(三十九):IDR帧和I帧
  • LeetCode|Day13|88. 合并两个有序数组|Python刷题笔记
  • GaussDB 数据库架构师修炼(四) 备份容量估算
  • SQLite技术架构解析,适用场景有哪些?
  • 邮件伪造漏洞
  • 基于 AI 的大前端安全态势感知与应急响应体系建设
  • 【SVN】设置忽略规则
  • Python Docker SDK库详解:从入门到实战
  • el-table中type=“selection“选中数据如何回显
  • 半导体制造流程深度解析:外观缺陷检测的AI化路径与实践
  • Java 栈和队列
  • 3d max 的快捷键
  • 极限状态下函数开根号的计算理解(含示意图)
  • Flink双流实时对账
  • CPU寄存器、进程上下文与Linux O(1)调度器原理
  • Jfinal+SQLite java工具类复制mysql表数据到 *.sqlite
  • 基于 vue+Cesium 实现军事标绘之钳击箭头绘制实战