9.环境列表

环境列表

  • 每一个进程都有与其相关的称之为环境列表(environment list)的字符串数组,或简称为环境(environment)。其中每个字符串都以名称=值(name=value)形式定义。因此,环境是“名称-值”的成对集合,可存储任何信息。常将列表中的名称称为环境变量(environment variables)。
  • 新进程在创建之时,会继承其父进程的环境副本。这种传递是单项的、一次性的。子进程、父进程均可更改各自的环境变量。

命令行访问环境变量

添加与撤销

大多数shell使用export命令向环境中添加变量值。

$SHELL = /bin/bash		/*Create a shell varible*/
$export SHELL	/*Put variable into shell process's environment*/

bash shell或者Korn shell 中,可以简写为:

$export SHELL=/bin/bash

在C shell 中,使用的则是setenv命令:

%setenv SHELL /bin/bash

上述命令把一个值永久地添加到shell环境中,此后这个shell创建的所有子进程都将继承此环境。在任一时刻,可以使用unset命令撤销一个环境变量(在C shell中则使用unsetenv命令)。

Bourne shell和其衍生shell(如bash shell 和 Korn shell)中,可使用下列语法向执行某应用程序的环境中添加一个变量值,而不影响其父shell(和后续命令):

$NAME=value program

此命令仅向执行特定程序的子进程环境添加了一个(环境变量)定义。如果希望(多个变量对该程序有效),可以在program前放置多对赋值(以空格分隔)。

补充

env命令在运行程序时使用了一份经过修改的shell环境列表副本。可同时为shell环境列表副本增加和移除环境变量定义,以修改此环境列表。详细内容请参阅env(1)手册。

显示

printenv命令显示当前的环境列表,此处是其输出的一例:

$printenv
SHELL=/bin/bash
SESSION_MANAGER=local/sixqaq:@/tmp/.ICE-unix/1798,unix/sixqaq:/tmp/.ICE-unix/1798
QT_ACCESSIBILITY=1
COLORTERM=truecolor
...

注意,环境列表的排列是无序的,列表中的字符串顺序不过是最易于实现的排列形式。一般而言,无序的环境列表不是问题,因为通常都是访问单个的环境变量,而非环境列表中按序排列的一串。

通过Linux专有的/proc/PID/environ文件检查任一进程的环境列表,每一个”NAME=value“ 对都以空字节终止。(实测我的Ubuntu-desktop-20.04 LTS上并不存在。。。。)

从程序中访问环境

可用的全局变量

  1. GNU C语言库提供有两个全局变量,可在程序内任意位置使用以获取调用该程序时的程序名称(即命令行的第一个参数)。第一个全局变量program_invocation_name,提供了用于调用该程序的完整路径名。第二个全局变量program_invocation_short_name,提供了不含目录的程序名称,即路径名的基本名称(basename)部分。

定义_GNU_SOURCE宏后即可从<errno.h>中获取对这两个全局变量的声明。

/*virtual.c*/
#include <stdio.h>
#include <errno.h>
#define _GNU_SOURCE
extern char* program_invocation_name;
extern char* program_invocation_short_name;

int main(void)
{
    printf("%s\n", program_invocation_name);
    printf("%s\n",program_invocation_short_name);
}
$gcc -o virtual virtual.c
$./virtual
./virtual
virtual
$cd ..
$./linuxC/virtual
./linuxC/virtual
virtual
  1. argvenviron数组(见下文),以及这些参数最初指向的字符串都驻留在进程栈之上的一个单一、连续的内存区域。此区域可存储的字节数有上限要求,SUSv3规定使用ARG_MAX常量(定义于<limits.h>)或者调用sysconf(_SC_ARG_MAX)函数以确定该上限值,并且SUSv3还要求ARG_MAX常量的下限为_POSIX_ARG_MAX(4096)个字节,而大多数UNIX实现的限制都远高于次
  2. 。但SUSv3并未规定对ARG_MAX限制的实现中是否要将一些开销字节计算在内(比如终止空字符、字节对齐、argv和environ指针数组)。
environ

在C语言程序中,可以使用全局变量char **environ访问环境列表。(C运行时启动代码定义了该变量并以环境列表位置为其赋值。)environargv参数类似,都指向一个以NULL结尾的指针列表,每个指针又指向一个以空字节终止的字符串。

如:

#include <stdio.h>
#include <stdlib.h>
extern char** environ;
int main(int argc, char** argv)
{
    char** ptr;
    for( ptr = environ; *ptr!=NULL ;++ptr)
        puts(*ptr);
    exit(EXIT_SUCCESS);
}

另外,还可以通过声明main()函数中的第三个参数来访问环境列表:

int main(int argc, char *argv[], char *envp[]);

该参数即可被视为environ变量来使用。不同的时,该参数的作用需在main()函数内。虽然,UNIX系统普遍实现了这一特性,但还是要避免使用,因为除了局限于作用限制外,该特性也不在SUSv3规范之列。

检索环境中的值

getenv()函数能够从进程中检索,单个值。

#include <stdlib.h>
char *getenv(const char* name);
				Return pointer to (value)string, or NULL if no such variable

getenv()函数提供环境变量名称,该函数会返回相应的字符串指针。因此,就前面所示的环境(列表)示例来看,如果指定SHELL为参数name,那么将返回/bin/bash。如果不存在指定名称的环境变量,那么getenv()函数将返回NULL

getenv()使用时可移植性方面的注意事项
  • SUSv3规定应用程序不应修改getenv()函数返回的字符串,这是由于(在大多数UNIX实现中)该字符串实际上时属于环境的一部分(即name=value字符串中的value部分)。若需要改变一个环境变量的值,可以使用setenv()putenv()函数(见下文)。
  • SUSv3允许getenv()函数的实现使用静态分配的缓冲区返回执行结果,后续对getenv()setenv()putenv()或者unsetenv()的函数调用可以重写该缓冲区。虽然glibc库的getenv()函数实现并未这样使用静态缓冲区,但是具备可移植性的程序如需保留getenv()调用返回的字符串,就应先将返回字符串复制到其他位置,之后方可对上述函数发调用

修改环境

对进程来说,修改其环境很有用。其一是因为修改一个进程环境对后续创建的所有子进程均可见。另一个可能的原因在与于设定某一变量,以求对于将要载入进程内存的新程序(“execed”)可见。

从这个意义上讲,环境不仅是一种进程间通信的形式,还是程序间通信的方法。(后续还将解释在同一进程中exec()函数如何使当前程序被一新程序所替代。)

putenv()

putenv()函数向调用进程的环境中添加一个新变量,或者修改一个已经存在的变量值。

#include <stdlib.h>
int putenv(char* string);
					Returns 0 on  success, or nonzero on error

参数string是一指针,指向name=value形式的字符串。调用putenv()函数后,该字符串就成为环境的一部分,换言之,putenv函数将设定environ变量中某一元素的指向与斯特日嗯参数的指向位置相同,而非string参数所指向字符串的复制副本。因此,如果随后修改string参数所指向的内容。这将影响该进程的环境。出于这一原因,string参数不应为自动变量(即在栈中分配的字符数组),因为定义此变量的函数一旦返回,就有可能重写这块内存区域。

注意,putenv()函数调用失败将返回非0值,而非-1

扩展

putenv()glibc实现还提供了一个非标准扩展。如果string参数内容不包括一个等号(=),那么将从环境列表中移除以string参数命名的环境变量。

setenv()
#include <stdlib.h>
int setenv(const char* name, const char* value, int overwrite);
					Returns 0 on success, or -1 on error

setenv()函数为形如name=value的字符串分配一块缓冲区,并将name和alue所指向的字符串复制到此缓冲区,以此来创建一个新的环境变量。注意,不需要(实际上是绝对不要)在name的结尾处或者value的的开始出提供一个等号字符,因为setenv()函数会在向环境添加新变量时添加等号字符。

若以name标识的变量在环境中已经存在,且参数overwrite的值为0,则不该变环境,如果overwrite为非0,则setenv()函数总是改变环境。

这一事实——setenv()函数复制其参数(到环境中)——意味着与putenv()函数不同,之后对name和value所指字符串内容的修改将不会影响环境。此外i,使用自动变量作为setenv()函数的参数也不会有任何问题。

unsetenv()

unsetenv()函数从环境中移除由name参数标识的变量。

#include <stdlib.h>
int unsetenv(const char* name);
					Returns 0 on success, or -1 on error.

setenv()函数意义,参数name不应包含等号字符。

setenv()unsetenv()均来自BSD,不如putenv()函数使用普遍。尽管起初的POSIX.1标准和SUSv2并未定义这两个函数,但SUSv3已将其纳入规范。

clearenv()

有时需要清除整个环境,然后以所选值进行重建。例如,为了以安全方式执行set-user-ID程序,就需要这样做。可以通过将environ变量赋值为NULL来清除环境。
environ = NULL
这也正是clearenv()库函数的工作内容。

#define _BSD_SOURCE	/*Or: #define _SVID_SOURCE*/
#include <stdlib.h>

int clearenv(void);
					Returns 0 on success, or a nonzero on error
clearenv()内存泄露问题

在某些情况下,使用setenv()函数和clearenv()函数可能会导致程序内存泄露。前面已然提及:setenv()函数所分配的一块内存缓冲区,随之会策划唵尾进程空间的一部分。而调用clearenv()时则没有释放该缓冲区(clearenv()函数并不知晓该缓冲区的存在,故而也无法将其释放)。反复调用这两个函数的程序,会不断产生内存泄露。实际上,这不大可能成为一个问题,因为程序通常仅在启动时调用clearenv()函数一次,用于移除其继承自其父进程(即调用exec()函数来启动当前程序的程序)环境中的所有条目

许多UNIX实现都支持clearenv()函数,但是SUSv3没有对此函数进行规范SUSv3规定如果应用程序直接修改environ变量,正如clearenv()函数所做的那样,则不对setenv()unsetenv()getenv()的行为进行定义。(这一做法的根本原因在于禁止符合SUSv3标准的应用程序直接修改环境,意在使UNIX实现能完全控制其环境变量时所采用的结构。)SUSv3允许应用程序清空自身环境的唯一方法时首先获取所有环境变量的列表(通过environ变量获得所有环境变量的名称),然后逐一调用unsetenv()移除每个环境变量。

程序访问示例

environ为NULL的情况

如果将environ参数赋值为NULL(也就是clearenv()函数的所作所为),那么可以预见如下形式的循环将失败,因为*environ是无效的。

for( ep = environ; *ep!=NULL ; ++ep )
		puts(*ep);

然而,如果setenv()函数和putenv()函数发现environ为NULL,则会创建一个新的环境列表,并使environ参数指向此列表,结果上面的循环又将正确运行。

程序示例

/*env.c*/
#define _GNU_SOURCE /*to get various declarations from <stdlib.h>*/
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <errno.h>

extern char** environ;
void errQuit(const char* format, ...);
int main(int argc, char** argv)
{
    int j;
    char** ep;
    clearenv(); /*Erase entire environment*/
    for( j=1; j < argc ; ++j )
        if(putenv(argv[j]) != 0)
            errQuit("putenv: %s", argv[j]);
    if(setenv("GREET", "Hello world", 0) ** -1)
        errQuit("setenv");
    
    unsetenv("BYE");
    for( ep = environ; *ep != NULL ; ++ep )
        puts(*ep);
    exit(EXIT_SUCCESS);
}
void errQuit(const char* format, ...)
{
    va_list argptr;
    va_start(argptr, format);
    char buf [1024];
    vsnprintf(buf, sizeof(buf)-1, format, argptr);
    va_end(argptr);
    printf("%s",buf);
}
$gcc -o env env.c
$./env
GREET=Hello world

$./env "age=9 years" Name=dog
age=9 years
Name=dog
GREET=Hello world

$./env BYE=no
GREET=Hello world

$./env bye=no
bye=no
GREET=Hello world

参考资料:《Linux/UNIX系统编程手册》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

barbyQAQ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值