4 环境变量
4.1 环境变量的概念
-
Linux的环境变量是系统中用于存储系统配置信息的变量,它们影响系统上运行的程序和进程的行为,环境变量保存了有关系统信息,程序配置,用户配置信息等的数据,并且在用户登录时,程序执行时等上下文中发挥作用(来自Chat_GPT)
-
说简单点,就类似于程序中的全局变量,系统某些操作的逻辑会依据这些全局变量(但是哈,环境变量是在系统层面的,这点别搞错了,而且环境变量一般是字符串)
-
看不明白的话咱们在后面几个小节会举例子的
4.2 main()函数的参数问题
-
了解本小节能加深我们对于环境变量的理解
-
要知道,
main()函数也是有参数的,有两个参数是int argc,char* argv[] -
后者是一个字符指针数组,前者是一个整形
-
前者代表着后者数组的元素个数
-
所以我们可以试着输出一下里面的内容
#include<stdio.h>
int main(int argc, char *argv[])
{
int count = 0;
for(; count != argc; count++)
{
printf("argv[%d]:%s\n", count, argv[count]);
}
return 0;
}
$ ./test
argv[0]:./test
- 接着,我们来试一个有点神奇的内容
- 如果我们在运行程序的指令之后再加一些内容呢?
$ ./test a b c
argv[0]:./test
argv[1]:a
argv[2]:b
argv[3]:c
- 不难发现,这么做是不是有点像命令行指令呢?
- 比方说
ls -a
- 我们再把程序改一改
#include<stdio.h>
#include<string.h>
int main(int argc, char *argv[])
{
if(argc != 2)
{
printf("error, you should use test like: test -a\n");
return 0;
}
if(strcmp(argv[1], "-a") == 0)
printf("using mode a\n");
else if(strcmp(argv[1], "-b") == 0)
printf("using mode b\n");
else if(strcmp(argv[1], "-c") == 0)
printf("using mode c\n");
else
printf("error, you should use test like: test -a\n");
return 0;
}
- 编译并运行
$ ./test -a
using mode a
$ ./test
error, you should use test like: test -a
$ ./test -b
using mode b
-
是不是有那味儿了?
-
我们知道Linux的指令本质上也是可执行程序
-
但是事实是,我们没法直接像用Linux指令一样用它,
-
我们知道,
/usr/bin目录是存放系统指令的地方,我们可以把我们写的程序拷贝到这个目录下试试 -
注意:Linux似乎不允许存在一个叫
test的命令,所以我们需要改一下名字,这里我改成test_program
$ test_program -a
using mode a
$ test_program
error, you should use test like: test -a
- 于是,这个可执行程序就真的可以像命令一样执行了
- (事后别忘了删掉)
4.3 两张表
-
为什么系统可以不用指明目录就可以找到这些命令的可执行程序?
-
因为系统存在一个叫环境变量的东西,如果用户输入了一个指令,这串指令本质上就是一串字符串,于是
shell会把这串字符串拿去分析 -
字符串开头就是需要运行的程序名,后面接着的都是选项
-
然后
shell会在环境变量给出的目录中寻找对应名字的程序,然后调用系统调用接口,并传递程序的选项给系统,由系统启动相关程序 -
意味着,这个环境变量在这里起的作用就是"默认程序启动路径"
-
argv表:其实就是选项表,我们说shell会把这串字符串拿去分析,分析后就会生成一张表,即argv表,程序会根据这张表决定应该执行哪些逻辑 -
环境变量表:
shell会在环境变量给出的目录中寻找对应名字的程序,注意:这里的环境变量不是只有一个,而是有很多个,以Key Value的形式构成一张表,这一点我们后面的小节会提到的 -
这两张表其实本质上都是二维数组
4.4 环境变量的查询
- 查询所有环境变量
$ env
XDG_SESSION_ID=18320
HOSTNAME=iZwz9b2bj2gor4d8h3rlx0Z
TERM=xterm
SHELL=/bin/bash
HISTSIZE=1000
SSH_CLIENT=120.227.54.111 10287 22
SSH_TTY=/dev/pts/0
USER=oldking
# ...
# 数量很多,就不全部展示了
# 这里是不是就像是一张表了呢
- 查询某个环境变量
echo $[environment_variable_name]
- 例如
$ echo $PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/oldking/.local/bin:/home/oldking/bin
-
注意:
echo PATH是错误指令,PATH前必须要加上$,类似于Makefile的语法 -
可以看到,这个环境变量里的东西挺多的,同时也不难看出,这串字符串中包含多个路径,每个路径由
:分隔, -
shell会依次查找这些路径中的可执行程序,直到找到一个对应上的 -
其他常见的环境变量:
$HOME:当前用户的主目录路径。
- 示例:
/home/username - 例如: 执行
cd ~时,实际上就是切换到 $HOME 变量指定的目录。
$USER:当前登录用户的用户名。
- 示例:
username
$SHELL:当前用户使用的shell类型。
- 示例:
/bin/bash,/bin/zsh
$PWD:当前工作目录的绝对路径。
- 示例:
/home/username/projects
$LANG:系统语言和区域设置。
- 示例:
en_US.UTF-8
$EDITOR:系统默认的文本编辑器。
- 示例:
vim,nano
$TERM:当前终端的类型。
- 示例:
xterm,screen
$PS1:命令提示符的格式。
- 示例:
\u@\h:\w\$(比方说:[oldking@oldkingnana_s_pc testdir]$)
$LD_LIBRARY_PATH:用于指定共享库搜索路径。
- 示例:
/usr/local/lib:/usr/lib:/lib
HOSTNAME:主机名
- 示例:
oldkingnana_s_pc
HISTSIZE:历史指令的存储量(比方说上下键最多可以存储的指令数量)
- 示例:
1000
SSH_TTY:终端设备的路径
- 示例:
/dev/pts/0(这个路径在"一切皆文件"提到过)
LOGNAME:登录用户
- 示例:
oldkingnana
OLDPWD:上一次进入的目录
- 示例:
/home(如果没有访问上一个目录则为空,可以用cd -切换到上一个目录)
4.5 环境变量的修改
- 临时修改环境变量
$ PATH=/home/oldking
$ echo $PATH
/home/oldking
$ ls
-bash: ls: command not found
-
好消息:修改成功了
-
坏消息:它把以前的也给覆盖了,导致原本的默认路径都用不了了
-
怎么解决?
-
重新登陆
shell就行 -
为什么重新登陆
shell就可以? -
首先我们得知道,环境变量其实存储在内存中,并且,每个用户的环境变量还可以不一样
-
我们用户在登录
shell的时候,系统会启动一个叫bash的进程,然后bash被启动的时候,就会将已经配置好的环境变量加载进内存中 -
我们更该的环境变量仅仅只是内存级的更改,下次启动又会将已经配置好的环境变量重新加载
-
不难猜到,这个"配置好的环境变量"一定是在磁盘中的某个文件
-
在每个用户的
/home/username的目录下,都会有两个隐藏的配置文件跟环境变量有关
-rw-r--r-- 1 oldking oldking 193 Nov 25 2021 .bash_profile
-rw-r--r-- 1 oldking oldking 355 Oct 19 21:44 .bashrc
- 首先我们可以看一下这两个文件的内容
$ cat .bashrc
# .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/oldking/.VimForCpp/nvim'
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:~/.VimForCpp/vim/bundle/YCM.so/el7.x86_64
$ cat .bash_profile
# .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
-
这两个配置文件中都是
shell脚本,我们不需要完全看懂,不过就看文件里那几个目录咱就明白,.bashrc肯定调用了/etc/bashrc的这个文件,.bash_profile肯定调用了.bashrc这个文件 -
解释一下这两个文件:
.bashrc:这个文件会调用/etc/bashrc,当用户使用非登录的shell时(非登录shell其实就类似于从某个带有图形化界面的Linux的桌面的终端图标启动终端,反正我们已经登陆过了,不必再登陆了,所以就使用.bashrc),bash进程会读取.bashrc,然后.bashrc的内容又是读取/etc/bashrc,然后将读取到的内容拷贝到内存中bash的环境变量表中的PATH中.bash_profile:.bash_profile则是在使用登录shell的时候,进程bash会调用.bash_profile,然后.bash_profile又会调用.bashrc,最后又调到/etc/bashrc
-
这俩文件可以用于
- 配置环境变量
- 为命令取别名(例如
ll) - ...
-
(具体可以查一查)
-
我们使用的是
shell登录,所以我们可以修改.bash_profile
# .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:/home/oldking/testdir
export PATH
- 于是,我们的
PATH就变成了这样
$ echo $PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/oldking/.local/bin:/home/oldking/bin:/home/oldking/testdir
- 现在我们也就可以直接用
/home/oldking/testdir的命令了
$ test_program
error, you should use test like: test -a
- 当然,这只是对当前用户的环境变量的修改,每个不同用户启动的
bash所加载的环境变量的配置文件都不同,当然,还有系统的统一配置文件/etc/bashrc - 即,用户可以以新增环境变量(或其值)的形式自定义自己的环境变量,但同时又必须保留系统统一的环境变量配置
4.6 环境变量的其他相关命令和代码中获取方式
4.6.1 命令
# 新增环境变量
export [env_name]=[content]
# 移除环境变量
unset [env_name]
4.6.2 代码中获取方式
4.6.2.1 通过main()参数获取
main()函数还有第三个参数char *env[]
#include<stdio.h>
#include<string.h>
int main(int argc, char *argv[], char *env[])
{
(void) argc;
(void) argv;
int i = 0;
for(i = 0; env[i]; i++)
{
printf("env[%d]:%s\n", i, env[i]);
}
return 0;
}
- 执行后输出内容为
$ ./test
env[0]:XDG_SESSION_ID=18330
env[1]:HOSTNAME=iZwz9b2bj2gor4d8h3rlx0Z
env[2]:TERM=xterm
env[3]:SHELL=/bin/bash
env[4]:HISTSIZE=1000
# ...
4.6.2.2 通过接口获取
getevn()- 这个接口会搜索当前进程的的环境变量表(环境变量在进程中存在继承动作,意味着父进程会将环境变量继承给子进程,这也是为什么我们什么都不用输入就可以让
main()函数的第三个参数拥有内容的原因),然后寻找和对应参数符合的环境变量,有的话就返回char*,没有就返回NULL
SYNOPSIS
#include <stdlib.h>
char *getenv(const char *name);
RETURN VALUE
The getenv() function returns a pointer to the value in the environ‐
ment, or NULL if there is no match.
- 使用案例
$ cat test.c
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main()
{
char* env = getenv("PATH");
printf("%s\n", env);
return 0;
}
$ ./test
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/oldking/.local/bin:/home/oldking/bin
4.6.2.3 通过全局变量获取
environ是一个全局变量,类型是char ** [],包含在头文件<unistd.h>中
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
extern char** environ;
int main()
{
int i = 0;
for(i = 0; environ[i]; i++)
{
printf("environ[%d]:%s\n", i, environ[i]);
}
return 0;
}
$ ./test
environ[0]:XDG_SESSION_ID=18330
environ[1]:HOSTNAME=iZwz9b2bj2gor4d8h3rlx0Z
environ[2]:TERM=xterm
environ[3]:SHELL=/bin/bash
# ...
4.7 本地变量
-
本地变量和环境变量没啥直接联系,但还是得提一嘴
-
本地变量有点像是
.c文件中的变量,一样可以声明,给值进去
$ i=10
- 可以用
set查询(set会把环境变量一并查出来)
$ set
# ...
XDG_SESSION_ID=18330
_=
colors=/home/oldking/.dircolors
i=10
-
为什么需要本地变量?
-
有一个东西叫
shell脚本,类似于一门简单的编程语言,只不过它是帮助我们批量化干重复的活的,所以需要本地变量 -
本地变量一样存储在
bash进程中,一样在内存中 -
但本地变量不能被子进程继承,只能存在于
bash中 -
unset命令可以删除本地变量,用法和环境变量一致,就不多提了
4.8 内建命令
-
我们知道,环境变量可以由父进程继承给子进程,一个进程很难修改(或者说不能修改)另一个进程的环境变量,因为两个任意进程之间有独立性
-
问题就来了,
export这个进程怎么修改它的父进程bash的环境变量的? -
事实是,
export根本就不是一个进程,而是一个"内建命令",意味着执行操作的不是export这个进程,而是bash亲自执行,bash分析命令确认你需要用export之后,他会执行自己的代码,意味着bash的代码本身就包含export的所有逻辑
- 如有问题或者有想分享的见解欢迎留言讨论