前言
不管是日常开发还是使用电脑的过程中,我们几乎无法避免与环境变量打交道,这是因为环境变量在系统的运行中发挥着不可或缺的作用,它作为系统的运行时参数,保存了一些进程运行时所需的信息。环境变量顾名思义是一个变量,作为变量所以它是用来存取数据的。但也是并不是什么格式的数据都能存,一般使用键值对的形式来存储字符串,但是一般存储的字符串也不会太长。所以从某种角度讲,说它是一种数据库也不过分。作为开发者,应该没有比对PATH变量更熟悉的了吧。安装一个新的软件并且希望它随处运行,这时候就会想到把它加入到环境变量。毕竟没有谁愿意反复的输入那么长的文件名吧。
一、环境变量的分类
1.系统环境变量
区别于用户环境变量,它是全局性的,影响范围更大,设置生效后能对系统的所有用户生效。所以不建议随意更改系统环境变量,因为它是共享的。这个程序修改了可能就影响到了另外的程序,引起不确定的问题发生,一般是使用文件进行存储。
以windows系统为例,系统环境变量存储在注册表位置为HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SessionManager\Environment,见下图
linux系统以Centos为例,系统环境变量PATH可以通过修改 /etc/profile文件或者/etc/environment文件来实现
使用 vi /etc/profile 命令打开/etc/profile文件 后在文件末尾空行追加
export PATH=/app/root2:$PATH
然后可以使用 source /etc/profile 使之立即生效
2.用户环境变量
区别于系统环境变量,他是相对局部的,独立的,影响范围更小,设置生效后只对当前用户生效。如果多个进程共享相同的环境变量,修改环境变量可能也会引起一些问题。以windows系统为例,系统环境变量存储在注册表位置为
HKEY_CURRENT_USER\Environment
linux系统以Centos为例,
用户环境变量PATH 可以通过修改 /.bashrc 文件来实现
下面使用vi ~/.bashrc命令打开~/.bashrc文件并且在文件末尾追加了一行
export PATH=/app/root:$PATH
修改完成之后可以使用 source ~/.bashrc使其立即生效,关闭shell之后重新开启一个shell窗口也会生效,下图使用 echo $PATH查看环境变量
要注意的是在shell中切换登录用户时会追加重复的路径
3.进程环境变量
区别于系统环境变量和用户环境变量,它的作用域更小,它只在进程内有效。所以它的隔离性相对更好,可以认为它的变量是存储在内存中的。
linux系统以Centos为例,进程环境变量可以在shell中设置
设置完成后立即生效
二、环境变量在主流编程语言中的用法以及特点
C#:
获取所有环境变量: Environment.GetEnvironmentVariables();
分别获取系统环境变量、用户环境变量、进程环境变量
使用Environment.GetEnvironmentVariable
如上图所示,当系统环境变量、用户环境变量、进程环境变量中存在相同名称的环境变量时该如何取舍呢,很显然,进程环境变量会覆盖用户环境变量,用户环境变量会覆盖系统环境变量。
分别设置系统环境变量、用户环境变量、进程环境变量
使用Environment.SetEnvironmentVariable
Java:
根据名称获取单个环境变量可以使用 System.getenv,遗憾的是没有设置环境变量的方法。
如果确实要获取环境变量,只能调用JNI了。kernel32.dll中的
SetEnvironmentVariableA 和 GetEnvironmentVariableA 两个Windows API函数 可以用来设置和获取环境变量的值
调用GetEnvironmentVariableA获取环境变量path的值
调用 SetEnvironmentVariableA 设置环境变量path的值
环境变量的继承性
因为进程是以树状结构存在,所以存在父子关系。一般的,父进程在创建子进程的时候,如果不指定环境变量,那么子进程将继承父进程的环境变量。
下面的例子验证了这一点
上面的例子在父进程中设置了一个进程内变量inheritvar,然后启动了一个基于自身镜像的子进程,子进程的命令行多了一个参数args0,待子进程启动后在子进程中读取进程进程内变量inheritvar
运行结果如下
说明子进程默认继承了父进程的进程变量
三、环境变量的应用
1.环境变量与批处理
环境变量在命令行和批处理中可以说使用是最广泛的了,而且在命令行和批处理中添加修改环境变量都很方便,因为命令行最常用的功能就是创建一个子进程
使用set命令设置或者查看环境变量,当然这个环境变量属于进程内环境变量
set 环境变量名可以查看环境变量
set 环境变量名=环境变量值 可以设置环境变量,比如我们可以通过设置path环境变量,使程序
ConsoleProcessFormat.exe可以在其它工作目录快捷运行
我们在设置的时候使用了 %path%,这个是拓展环境变量,通过这个方式可以引用已经存在的环境变量path原有的值。所以在命令行我们有两种方式可以查看环境变量的值,一种是使用set 环境变量名,另外一种是使用拓展环境变量
拓展环境变量在C#可以使用方法取值
2. java 中的 java.library.path 与 path 环境变量关系
java中加载dll一般是通过System.load和System.loadLibrary来实现的,这两个函数在加载动态库时
会查看系统属性 java.library.path 和 sun.boot.library.path,查看ClassLoader.loadLibrary可知
那么在命令行参数中没有显性指定 java.library.path 系统属性的时候,我们可以发现java.library.path取的是 path 环境变量的值
那么要注意的是 如果我们显性的指定了 java.library.path的时候,如果没有把原有的path加进去,很可能导致系统一些基础的依赖库找不到。所以手动指定java.library.path的时候最好把原有的path环境变量的值追加上去。
进程内设置java.library.path属性不生效的原因
有的时候我们想在进程内通过 System.setProperty 来设置 java.library.path 的值 来动态追加dll文件的搜索路径会发现不会生效。追本溯源,发现是缓存的原因。
System.load 会调用 ClassLoader类的 loadLibrary方法,loadlibrary方法部分代码如下
java进程启动后,ClassLoader类的静态属性sys_paths 已经被赋值了,所以默认usr_paths取的是默认的 java.library.path 属性值
所以要想自己设置的java.library.path生效,需要重置sys_paths的值为null或者 直接修改usr_paths变量追加相应的path,这两种方式都需要反射来完成。
下图中在修改sys_paths变量前后打印出loadlibrary加载XBootLib.dll的结果
注意的是System.loadLibrary加载DLL不带后缀名,使用System.load加载DLL则需要指定dll文件全路径