Linux中环境变量的获取和设置还是很方便的,在命令行中敲env
命令就可以打印出当前的所有环境变量。
flushhip@flushhip-virtual-machine:~$ env | grep $PATH
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
flushhip@flushhip-virtual-machine:~$
我这里选取了环境变量中的PATH
打印出来,env
还可以设置环境变量。
flushhip@flushhip-virtual-machine:~$ env TEST=TEST | grep TEST
TEST=TEST
flushhip@flushhip-virtual-machine:~$
更多用法可以运行命令man env
去看下帮助手册,当然,设置环境变量还有其他的方法,这里就不一一赘述。
通过打印出来的环境变量可以看到,环境变量是以name=value
这种形式存在的。设置一个新的name
只需要在环境变量列表中多加一串字符串name=value
。那么环境变量列表是什么呢?
命令行来一句man environ
,可以看到下面的东西:
#include <unistd.h>
extern char **environ;
环境变量列表就是environ
,这是一个指向字符指针的指针,其实就是一个字符串数组,且这个字符串数组以NULL
结尾。
我们可以写一个程序来输出它。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
extern char **environ;
int main()
{
for (char **ptr = environ; *ptr != NULL; ++ptr)
puts(*ptr);
return 0;
}
这个程序的输出和env
的输出是一样的。
那么我们在Linux环境中编程中如何获取和设置环境变量呢?用库函数API
关于设置环境变量的API有以下几个常用的:
getenv
setenv
unsetenv
putenv
获取环境变量的只有getenv
,而设置环境变量的有putenv
和setenv
、unsetenv
。
获取环境变量
#include <stdlib.h>
char *getenv(const char *name);
getenv("PATH")
返回的就是PATH
的值,不过要注意到getenv
的返回值是char *
,这个指针直接指向environ
,也就是说我们可以修改这个值,但是修改这个值后会直接破坏用户程序的环境变量。
As typically implemented, getenv() returns a pointer to a string within the environment list. The caller must take care not to modify this string, since that would change the environment of the process.
设置环境变量
#include <stdlib.h>
int putenv(char *string);
int setenv(const char *name, const char *value, int overwrite);
int unsetenv(const char *name);
关于各个参数和返回值,请直接man
它们。
putenv
和setenv
都可以设置环境变量,那么这两个API有什么不同点呢
- 使用方式不一样
putenv
要保证string
的声明全局周期或局部静态周期,而setenv
不需要。
第二个不同点是我修复一个框架的bug时才发现的。一个一个来说吧。
使用方式不一样
putenv
需要手动构建name=value
的string
形式。
这在C++中还好办,用string::append
函数可以方便搞定,但是在C语言中,就要自己开数组然后用strcat
把name
,=
,value
拼接起来。很麻烦;
setenv
就很方便了,你把name
和value
直接传进去就行了。
这个使用方式的不一样很容易导致bug。
putenv
要保证string
的声明全局周期或局部静态周期
这是什么意思呢?先来看一个程序。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{
[] () {
char a[] = "TEST=TEST";
putenv(a);
}();
puts(getenv("TEST"));
return 0;
}
这个程序不能正常运行,在Ubuntu下会直接报段错误,而在Centos下输出来的东西不是预期的。
而我们把这个程序改一下就能正常运行了。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{
char a[] = "TEST=TEST";
putenv(a);
puts(getenv("TEST"));
return 0;
}
我们看下putenv
的man
手册,有这么一句话The string pointed to by string becomes part of the environment
,它说我们传给putenv
的这个string
指针会直接成为environ
的一部分。
也就是说,我们以后在读取environ
时会读取string
指针所指向的内容。
那么,现在就知道了为什么第一个程序会出错或者不能输出正确的值,而第二个程序可以,因为第一个程序中的a[]
的作用与是整个
表达式,当执行完这个
表达式后,a[]
的空间就会释放,从而在main
函数中用getenv
会读取到一块非法内存;而第二个程序不会存在这个问题。
你可能会说谁写程序会像第一个程序中那样写,会的!当你写一个框架工具的时候,你一定会再次封装putenv
这个接口,而一不小心你就会忽视putenv(string)
中string
指向空间的作用域。
解决这个bug的方法有两种:
用map
来存储name
和string
,并把map
声明为全局的或局部静态的
这里我把putenv
再次封装一下,如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <map>
#include <string>
namespace TOOL {
using namespace std;
void setEnvironmentVarible(const string &name, const string &value = "") {
static map< string, string > mp;
string str(name);
if (0 != value.size()) {
str.append("=");
str.append(value);
mp[name] = str;
}
putenv((char *)mp[name].c_str());
if (0 == value.size() && mp.end() != mp.find(name))
mp.erase(name);
}
};
int main()
{
[] () {
TOOL::setEnvironmentVarible("TEST", "TEST");
} ();
puts(getenv("TEST"));
return 0;
}
static
的map
有两个好处:
- 保持了字符串的生命周期在整个程序执行期有效
map
可以非常方便实现删除,且最重要的一点就是map
是关联容器(想想vector
来代替map
行不行,不考虑效率)
用setenv
上面一种解决方法还需要自己去处理封装,而setenv
就不会有putenv
那样的副作用,且作用就相当于上面了TOOL::setEnvironmentVarible
,因此下面这个程序是可以正常工作的。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{
[] () {
setenv("TEST", "TEST", 1);
} ();
puts(getenv("TEST"));
return 0;
}
在man setenv
中可以看到这么一句话:
This function makes copies of the strings pointed to by name and value (by contrast with putenv(3))
意思就是setenv
的实现和TOOL::setEnvironmentVarible
类似,都是复制字符串,而不是像putenv
那样把字符串的指针直接放入environ
。
总结
没事多man
一下。附赠一张man
的类型表。