
命令行参数与环境变量
友情专栏:【把Linux"聊"明白】
文章目录
- 命令行参数与环境变量
- 前言
- 一、命令行参数
- 二、环境变量
-
- [2-1 基本概念](#2-1 基本概念)
- [2-2 常见环境变量](#2-2 常见环境变量)
- [2-3 查看环境变量方法及探索环境变量的的本质(实验)](#2-3 查看环境变量方法及探索环境变量的的本质(实验))
- [2-4 认识更多的环境变量](#2-4 认识更多的环境变量)
- [2-5 和环境变量相关的命令](#2-5 和环境变量相关的命令)
- [2-6 环境变量的组织方式](#2-6 环境变量的组织方式)
- [2-7 通过代码如何获取环境变量](#2-7 通过代码如何获取环境变量)
- [2-8 通过系统调用获取或设置环境变量](#2-8 通过系统调用获取或设置环境变量)
- [2-9 环境变量通常是具有全局属性的](#2-9 环境变量通常是具有全局属性的)
- [2-10 环境变量的来源](#2-10 环境变量的来源)
前言
Linux中,为什么 ls 命令能直接运行,而我们自己的程序却要用 ./hello?这背后是环境变量在起作用。环境变量就像系统的"记忆卡片",记录了各种程序运行需要的信息。
本文将用简单例子带你搞懂环境变量的核心,从基础操作到底层原理,让你真正掌握这个实用技能。
一、命令行参数
在我们的代码中,对于main函数都是不带参数的,那main函数真的没有参数吗?
不是的,其实main函数是可以有参数的。
这是标准规定的两种参数的形式:
c
int main(int argc, char *argv[])
对于三种参数的main函数,大多数编译器也是支持的:
c
int main(int argc, char *argv[], char *envp[])
但是不代表我们写的无参的main函数,是错误的,因为main函数有参数是后续的标准规定的,它要具有向前兼容性。
两种参数的main函数,我们先来写个代码看看这么个事:
c
#include <stdio.h>
int main(int argc, char *argv[])
{
for (int i = 0; i < argc; i++)
{
printf("%s ", argv[i]);
}
printf("\n ");
return 0;
}
输出:
可见,bash把我们的命令行输入的命令给我们拆分了(按照空格),然后传给main函数的参数,其中,argc为参数总数,argv为每个参数。
然后再给我们再想想,前面的
ls -a -l.......什么原理?
ls它也是二进程文件,和我们的可执行文件是一样的,同样,也是有main函数的,那他是怎么辨别lsls -als -a -l...呢?显然,是命令行参数。当然,我们可以基于我的上述的代码来模拟一下main命令行参数是如何实现此等功能的(我只是简单的提下思想)。
c
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
if (argc == 1)
{
printf("%s\n ", argv[0]);
}
else if (argc == 2)
{
if (strcmp(argv[1], "a"))
printf("%s %s\n ", argv[0], argv[1]);
else if (strcmp(argv[1], "b"))
printf("%s %s\n ", argv[0], argv[1]);
else
{ //...
}
}
else
{
//...
}
return 0;
}
可见,main命令行参数就是程序实现不同子功能的方法!
基于上述我们会发现,在我们的进程中存在一张表,我们称之为命令行参数表argv。可以用来支持选项功能。
对于三种参数的main函数,在环境变量中会说。
二、环境变量
2-1 基本概念
环境变量(environmentvariables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。
如:我们在执行ls命令的时候,我们从来不知道ls的二进制可执行文件在哪里,但是照样可以执行成功原因就是有相关环境变量帮助我们进行查找。
环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性。
2-2 常见环境变量
PATH:指定命令的搜索路径
HOME:指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
SHELL:当前Shell,它的值通常是/bin/bash
2-3 查看环境变量方法及探索环境变量的的本质(实验)
echo $NAME
注:NAME为环境变量名
我们可以直接来写个hello.c来测试一下,编译后生成可执行文件hello:
c
#include <stdio.h>
int main()
{
printf("hello world\n");
return 0;
}
为什么./hello可以执行和而hello不可以执行:
平时我们都在用./XXX来执行我们的二进制程序,而XXX却不可以执行我们自己的二进制程序,为什么?我们的二进制程序是带了路径 的,我们在执行
ls ...为什么可以直接执行,不需要带路径呢?此时就不得不提到我们的环境变量PATH !!!
可以先来查看一下
echo $PATH:
我们平时使用的ls...大多数指令,它们也是要有路径的,只是它们的路径来自于PATH,我们可以这样来执行:
此时,聪明的你就要说了,那我们可不可以把我们的二进制程序放到
/usr/bin下呢?可不可以把我们的当前路径加入到PATH呢?当然可以,都可以实现!!!
- 把我们的二进制程序放到
/usr/bin下
- 将我们的程序所在路径加入环境变量PATH当中:
export PATH=$PATH:hello程序所在路径(命令下面会说)
其实到此,对于环境变量,至少PATH环境变量,我们可以理解了。其它的环境变量我们下面会说的。
2-4 认识更多的环境变量
下面我们来看一下其它的环境变量,可以用命令env显示所有环境变量(由于某种原因,以下是用ai生成的):
shell
$ env
USER=alice
LOGNAME=alice
HOME=/Users/alice
SHELL=/bin/zsh
PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
TERM=xterm-256color
TMPDIR=/var/folders/.../
LANG=en_US.UTF-8
XPC_FLAGS=0x0
XPC_SERVICE_NAME=0
SHLVL=1
PWD=/Users/alice
OLDPWD=/Users/alice/Downloads
__CF_USER_TEXT_ENCODING=0x1F5:0x0:0x0
_=/usr/bin/env
其中的PATH我们前面已经见过了;其它:
USER(当前用户名)、PWD(当前工作目录)......
我们可以自己在本地用用。
2-5 和环境变量相关的命令
- echo:显示某个变量值(本地变量或者环境变量)
- export: 设置一个新的环境变量
export会覆盖掉已经存在的环境变量,所以前面我会
export PATH=$PATH:hello程序所在路径写,
- env: 显示所有环境变量
- unset: 清除环境变量
- set: 显示本地定义的shell变量和环境变量
set 显示当前 shell 中定义的所有变量,包括只有当前 shell 能用的本地变量 ,以及会传给子进程的环境变量 。
此时,我们可以用
export来将本地变量 i 提到环境变量中。
那两者的区别在何处呢?环境变量是可以被进程继承下去的(下面会说)。
2-6 环境变量的组织方式
对于前面的命令行参数,我们说bash有一个命令行参数表;
此时,对于环境变量来说,bash也有一个环境变量表,环境表其实就是⼀个字符指针数组,也是以NULL指针结尾。
environ解释:
environ 是一个全局变量指针,指向环境变量表的起始位置。
c
// 标准声明
extern char **environ;
environ是libc中定义的全局变量,没有包含在任何头文件中,所以在使用时要用extern声明。
2-7 通过代码如何获取环境变量
- ** 命令行第三个参数**
前面我们说过命令行也可以有第三个参数,即环境变量表,看代码:
c
#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获取
c
#include <stdio.h>
extern char **environ;
int main(int argc, char *argv[])
{
int i = 0;
for (; environ[i]; i++)
{
printf("%s\n", environ[i]);
}
return 0;
}
上面两种方法的最后的输出结果都是一样的,自行测试。
2-8 通过系统调用获取或设置环境变量
putenv 与 getenv:
显然,putenv是设置/修改环境变量, getenv是获取环境变量的值。
我们来用一下getenv。
c
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("%s\n", getenv("PATH"));
return 0;
}
输出:
此时,就有个比较好玩的,我们要写一个只允许在自己执行的程序该怎么写呢?可结合2-4 认识更多的环境变量,有兴趣可自行实现。
2-9 环境变量通常是具有全局属性的
变量通常具有全局属性,可以被子进程继承下去
我们可以用代码来测试一下:
c
#include <stdio.h>
#include <stdlib.h>
int main()
{
char *env = getenv("MYENV");
if (env)
{
printf("%s\n", env);
}
else
{
printf("env:NULL\n");
}
return 0;
}
此时,我bash的环境变量中并没有MYENV环境变量,执行代码看结果:
我们在bash执行export MYENV=helloworld后直接执行程序:
我并没有重新编译程序,况且与重新编译程序无关,我们可以得到子进程确实继承了环境变量。
2-10 环境变量的来源
那我们最初的环境变量来自哪里呢?
答案是:配置文件。~/.bash_profile与 ~/.bashrc(不同系统略有差别 ,我这里使用的是CentOS7)。
每个用户的家目录下,都存在 .bash_profile与.bashrc。
我们来看下里面的主要内容吧。
shell
# .bash profile
# Get the aliases and functions
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
# User specific environment and startup programs
PATH=$PATH:%HOME/.local/bin:%HOME/bin
export PATH
~/.bash_profile会加载~/.bashrc 。
shell
#bashrc
# Source global definitions
if [ -f /etc/bashrc ]; then
. /etc/bashrc
fi
# Uncomment the following line if you don't like systemctl's auto-paging feature:
# export SYSTEMD_PAGER=
# User specific aliases and functions
alias vim='/home/lin/.VimForCpp/nvim'
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:~/.VimForCpp/vim/bundle/YCM.so/e17.x86_64
~/.bashrc会加载/etc/bashrc。
所以,我们最终得到的结论就是环境变量是由配置文件加载而来的。
那就有个小问题,如果今天我们有10个用户登录会有多少张环境变量表?
10个,因为每个用户登录的时候,系统会为每个用户分配一个bash,每个bash都有自己对应的环境变量表。
注:
export是内建命令,不需要创建子进程,而是让bash自己亲自执行,bash自己调用函数,或者系统调用完成!
正因如此,export才能修改bash自身环境变量。
如果本文对您有启发:
✅ 点赞 - 让更多人看到这篇硬核技术解析 !
✅ 收藏 - 实战代码随时复现
✅ 关注 - 获取Linux系列深度更新
您的每一个[三连]都是我们持续创作的动力!✨
















