Linux与设置环境变量相关的库函数API

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,而设置环境变量的有putenvsetenvunsetenv

获取环境变量

#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它们。

putenvsetenv都可以设置环境变量,那么这两个API有什么不同点呢

  1. 使用方式不一样
  2. putenv要保证string的声明全局周期或局部静态周期,而setenv不需要。

第二个不同点是我修复一个框架的bug时才发现的。一个一个来说吧。

使用方式不一样

putenv需要手动构建name=valuestring形式。

这在C++中还好办,用string::append函数可以方便搞定,但是在C语言中,就要自己开数组然后用strcatname=value拼接起来。很麻烦;

setenv就很方便了,你把namevalue直接传进去就行了。

这个使用方式的不一样很容易导致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;
}

我们看下putenvman手册,有这么一句话The string pointed to by string becomes part of the environment,它说我们传给putenv的这个string指针会直接成为environ的一部分。

也就是说,我们以后在读取environ时会读取string指针所指向的内容。

那么,现在就知道了为什么第一个程序会出错或者不能输出正确的值,而第二个程序可以,因为第一个程序中的a[]的作用与是整个 l a m b d a 表达式,当执行完这个 l a m b d a 表达式后,a[]的空间就会释放,从而在main函数中用getenv会读取到一块非法内存;而第二个程序不会存在这个问题。

你可能会说谁写程序会像第一个程序中那样写,会的!当你写一个框架工具的时候,你一定会再次封装putenv这个接口,而一不小心你就会忽视putenv(string)string指向空间的作用域。

解决这个bug的方法有两种:

map来存储namestring,并把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;
}

staticmap有两个好处:

  • 保持了字符串的生命周期在整个程序执行期有效
  • 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的类型表。

这里写图片描述

猜你喜欢

转载自blog.csdn.net/FlushHip/article/details/81505165