shell变量和环境变量在用法上有什么区别?


16

我实际上不知道可以从命令行访问两种不同类型的变量。我所知道的是,我可以像这样声明变量:

foo="my dear friends"
bar[0]="one"
bar[1]="two"
bar[2]="three"

或使用$符号访问它们,例如:

echo $foo
echo ${bar[1]}

或使用内置变量,例如:

echo $PWD
PATH=$PATH:"/usr/bin/myProg"

现在,我听说有两种(至少?)变量类型:外壳变量和环境变量。

  • 拥有两种不同类型的目的是什么?
  • 我怎么知道变量是哪种类型?
  • 每种的典型用法是什么?


Answers:


14

环境变量是name=value成对存在的一对列表,无论程序是什么(shell,应用程序,守护程序……)。它们通常由子进程继承(由fork/ exec序列创建):子进程获得自己的父变量副本。

Shell变量确实仅存在于Shell上下文中。它们仅在子shell中继承(即,在不进行任何exec操作的情况下分叉shell )。根据外壳的功能,变量不仅可以是简单字符串,例如环境变量,还可以是数组,复合变量,类型化变量,例如整数或浮点数等。

当shell启动时,它从其父级继承的所有环境变量也将变为shell变量(除非它们作为shell变量无效,并且在其他极端情况下(如IFS某些shell重置)无效),但这些继承的变量被标记为export 1。这意味着它们将对子进程保持可用状态,这些进程具有由Shell设置的潜在更新值。对于在外壳下创建并标记为使用export关键字导出的变量的情况也是如此。

除非可以将数组和其他复杂类型的变量的名称和值转换为name=value模式,或者存在特定于外壳的机制,否则无法导出数组和其他复杂类型的变量(例如:bash导出环境中的函数以及一些特殊的非POSIX Shell,例如rc并且es可以导出数组) )。

因此,环境变量和shell变量之间的主要区别在于它们的范围:环境变量是全局的,而未导出的shell变量是脚本的局部变量。

还要注意,现代shell(至少kshbash)支持第三个shell变量范围。与功能创建变量typeset的关键字是局部的功能(函数声明的方式,启用/禁用下此功能ksh,并且持续性的行为之间是不同的bashksh)。参见/unix//a/28349/2594

1 这适用于现代的炮弹一样kshdashbash和类似的。遗留的Bourne shell和非Bourne语法shell csh都有不同的行为。


1
子进程将所有内容都继承下来,因为子进程将作为其父进程的fork(精确副本)创建。环境变量的意义在于它们被传递给execve()系统调用,因此(通常)用于在执行其他命令(在同一进程中)时持久存储数据。
斯特凡Chazelas

并非所有环境变量都转换为外壳变量。仅那些作为外壳程序变量名称有效的变量(以及IFS某些外壳程序中的一些例外情况)。
斯特凡Chazelas

贝壳喜欢rces可以导出使用即席编码阵列。bash并且rc还可以使用环境变量(再次使用特殊编码)导出函数。
斯特凡Chazelas

ksh93typeset仅在使用function foo { ...; }语法声明的函数中限制作用域,而不是在Bourne(foo() cmd)语法中限制作用域(并且它是静态作用域,而不像其他shell中那样是动态的)。
斯特凡Chazelas

@StéphaneChazelas感谢您的审查!回复已更新,以考虑您的评论。
jlliagre

17

外壳变量

Shell变量是范围在当前Shell会话中的变量,例如在交互式Shell会话或脚本中。

您可以通过为未使用的名称分配值来创建shell变量:

var="hello"

Shell变量的使用是为了跟踪当前会话中的数据。Shell变量通常使用小写字母的名称。

环境变量

环境变量是已导出的外壳程序变量。这意味着它将不仅作为创建它的shell会话中的变量,而且对于从该会话启动的任何进程(不仅是shell)都可见。

VAR="hello"  # shell variable created
export VAR   # variable now part of the environment

要么

export VAR="hello"

导出外壳变量后,它将一直保持导出状态,直到未设置它或删除其“导出属性”(带有export -nin bash),因此通常无需重新导出它。使用取消设置变量unset会删除它(无论它是否是环境变量)。

bash不能将数组和关联散列以及其他外壳中的内容导出为环境变量。环境变量必须是简单变量,其值是字符串,并且它们的名称通常由大写字母组成。

使用环境变量不仅可以跟踪当前Shell会话中的数据,还可以使任何启动的进程都可以使用该数据。这种情况的典型情况是PATH环境变量,可以在外壳程序中设置环境变量,以后任何想要启动程序而无需指定它们完整路径的程序都可以使用它。

流程中的环境变量的集合通常称为“流程环境”。每个过程都有其自己的环境。

环境变量只能被“转发”,即子进程永远不能在其父进程中更改环境变量,除了在启动子进程时为其设置环境之外,父进程可能不会更改子进程的现有环境。子进程。

环境变量可以列出env(不带任何参数)。除此之外,它们在Shell会话中看起来与未导出的Shell变量相同。这对于s​​hell来说有点特殊,因为大多数其他编程语言通常不会将“普通”变量与环境变量混合在一起(请参见下文)。

env 还可用于在流程环境中设置一个或多个环境变量的值,而无需在当前会话中进行设置:

env CC=clang CXX=clang++ make

这将启动make与环境变量CC设置为值clangCXX设置为clang++

它也可以用于清除流程的环境:

env -i bash

这会开始,bash但不会将当前环境转移到新bash进程中(它将仍然具有从其外壳初始化脚本中创建新环境变量环境变量)。

差异示例

$ var="hello"   # create shell variable "var"
$ bash          # start _new_ bash session
$ echo "$var"   # no output
$ exit          # back to original shell session
$ echo "$var"   # "hello" is outputted
$ unset var     # remove variable

$ export VAR="hello"  # create environment variable "VAR"
$ bash
$ echo "$VAR"         # "hello" is outputted since it's exported
$ exit                # back to original shell session
$ unset VAR           # remove variable

$ ( export VAR="hello"; echo "$VAR" )  # set env. var "VAR" to "hello" in subshell and echo it
$ echo "$VAR"         # no output since a subshell has its own environment

其他语言

大多数编程语言中都有库函数,这些函数允许获取和设置环境变量。请注意,由于环境变量是作为简单的键值关系存储的,因此它们通常不是语言的“变量”。程序可以获取与键(环境变量的名称)相对应的值(始终是字符串),但随后必须将其转换为整数或语言期望该值具有的任何数据类型。

在C,环境变量可以使用访问getenv()setenv()putenv()unsetenv()。C程序启动的任何进程都以相同的方式继承用这些例程创建的变量。

其他语言可能具有用于完成同一任务的特殊数据结构,例如%ENVPerl中的哈希,或ENVIRON大多数实现中的关联数组awk


thx,非常清楚的解释。因此,环境就像一个广阔的领域,其他程序可以在其中生存并查看每个环境变量。有些程序有其私有变量,只有它们自己可以看到它们,例如shell。但是有一种机制可以使私有变量被所有人称为“导出”。如果可以理解,那么我唯一不确定的就是是否可以同时存在多个环境?
sharkant

@sharkant每个正在运行的进程都有自己的环境。该环境是从启动它的过程继承的。不同进程的环境之间永远不会发生“串扰”。更改流程中环境变量的唯一方法是流程本身对其进行修改。
库沙兰丹

感谢我的理解。每条鱼都在自己的鱼缸中。产生其他进程的进程怎么样?进程及其子进程是全部在一个环境中还是具有各自的环境?
sharkant

1
@sharkant大多数语言都有库函数,这些函数允许获取和设置环境变量。在C语言中,这是通过做getenv()setenv()putenv()unsetenv()。C程序启动的任何进程都以相同的方式继承用这些例程创建的变量。其他语言可能对同一事物具有特殊的数据结构,例如%ENV在Perl中。
库萨兰达

1
FWIW:exec*()函数系列还可以为正在执行的进程设置环境。
聪桂

5

Shell变量很难复制。

$ FOO=bar
$ FOO=zot
$ echo $FOO
zot
$ 

但是,环境变量可以重复;它们只是一个列表,一个列表可以包含重复的条目。就是envdup.c这样做。

#include <err.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

extern char **environ;

int main(int argc, char *argv[]) {
    char **newenv;
    int envcount = 0;

    if (argc < 2) errx(64, "Usage: envdup command [args ..]");

    newenv = environ;
    while (*newenv++ != NULL) envcount++;

    newenv = malloc(sizeof(char *) * (envcount + 3));
    if (newenv == NULL) err(1, "malloc failed");
    memcpy(newenv, environ, sizeof(char *) * envcount);
    newenv[envcount]   = "FOO=bar";
    newenv[envcount+1] = "FOO=zot";
    newenv[envcount+2] = NULL;

    environ = newenv;
    argv++;
    execvp(*argv, argv);
    err(1, "exec failed '%s'", *argv);
}

我们可以编译并运行告诉envdup然后运行env以向我们展示设置了哪些环境变量...

$ make envdup
cc     envdup.c   -o envdup
$ unset FOO
$ ./envdup env | grep FOO
FOO=bar
FOO=zot
$ 

这可能仅对发现程序处理方式中的错误或其他异常有用**environ

$ unset FOO
$ ./envdup perl -e 'exec "env"' | grep FOO
FOO=bar
$ ./envdup python3 -c 'import os;os.execvp("env",["env"])' | grep FOO
FOO=bar
FOO=zot
$ 

看起来这里的Python 3.6盲目地通过了重复项(泄漏抽象),而Perl 5.24却没有。贝壳呢?

$ ./envdup bash -c 'echo $FOO; exec env' | egrep 'bar|zot'
zot
FOO=zot
$ ./envdup zsh -c 'echo $FOO; exec env' | egrep 'bar|zot' 
bar
FOO=bar
$ 

天哪,如果sudo只对第一个环境条目进行消毒,然后bash与第二个环境条目一起运行,会发生什么?您好PATHLD_RUN_PATH利用。您sudo(和其他所有东西是否已为该洞打了补丁?安全漏洞既不是调用程序中的“轶事区别”,也不只是“错误”。


1
没错,但这是一个轶事,而且可以说是程序设置重复变量的错误。
jlliagre


0

一个环境变量就像是一个shell变量,但它不特定的Unix系统上的所有进程都具有环境变量存储环境和shell变量之间的主要区别是:操作系统的系统通过了所有你的shell的环境变量,以使外壳运行,而shell变量不能在命令来访问你运行的程序。

env –该命令允许您在自定义环境中运行另一个程序,而无需修改当前程序。当不带参数使用时,它将打印当前环境变量的列表。 printenv –该命令显示所有或指定的环境变量。 set –该命令设置或取消设置外壳变量。当不带参数使用时,它将打印所有变量的列表,包括环境和shell变量以及shell函数。 unset –该命令删除外壳程序和环境变量。 export –该命令设置环境变量

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.