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

sha*_*ant 19 command-line shell environment-variables

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

foo="my dear friends"
bar[0]="one"
bar[1]="two"
bar[2]="three"
Run Code Online (Sandbox Code Playgroud)

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

echo $foo
echo ${bar[1]}
Run Code Online (Sandbox Code Playgroud)

或使用内置变量,例如:

echo $PWD
PATH=$PATH:"/usr/bin/myProg"
Run Code Online (Sandbox Code Playgroud)

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

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

Kus*_*nda 19

外壳变量

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

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

var="hello"
Run Code Online (Sandbox Code Playgroud)

shell 变量的用途是跟踪当前会话中的数据。Shell 变量的名称通常带有小写字母。

环境变量

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

VAR="hello"  # shell variable created
export VAR   # variable now part of the environment
Run Code Online (Sandbox Code Playgroud)

或者

export VAR="hello"
Run Code Online (Sandbox Code Playgroud)

导出 shell 变量后,它会保持导出状态,直到它被取消设置,或者直到它的“导出属性”被删除(使用export -nin bash),因此通常不需要重新导出它。取消设置变量unset会删除它(无论它是否是环境变量)。

bash和其他 shell 中的数组和关联哈希可能不会导出为环境变量。环境变量必须是值是字符串的简单变量,并且它们的名称通常由大写字母组成。

环境变量的用途是跟踪当前 shell 会话中的数据,但也允许任何启动的进程获取该数据的一部分。这种情况的典型情况是PATH环境变量,它可以在 shell 中设置,以后任何想要启动程序而不指定它们的完整路径的程序都会使用它。

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

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

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

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

env CC=clang CXX=clang++ make
Run Code Online (Sandbox Code Playgroud)

这首先make将环境变量CC设置为 valueclangCXX设置为clang++

它还可用于清除进程的环境:

env -i bash
Run Code Online (Sandbox Code Playgroud)

这会启动bash但不会将当前环境转移到新bash进程(它仍然具有环境变量,因为它从其 shell 初始化脚本创建新的)。

差异示例

$ 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
Run Code Online (Sandbox Code Playgroud)

其他语言

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

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

其他语言可能有特殊的数据结构完成同样的事情,就像%ENV在Perl哈希,或ENVIRON在大多数实现关联数组awk


jll*_*gre 16

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

Shell 变量确实仅存在于 Shell 的上下文中。它们仅在子外壳中继承(即当外壳在没有exec操作的情况下分叉时)。根据 shell 的特性,变量可能不仅是像环境字符串这样的简单字符串,还可能是数组、复合、类型变量(如整数或浮点数)等。

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

数组和其他复杂类型变量不能被导出,除非它们的名称和值可以转换为name=value模式,或者当一个特定于 shell 的机制到位时(例如:bash导出环境中的函数和一些奇异的非 POSIX shell,例如rc并且es可以导出数组)。

因此,环境变量和 shell 变量之间的主要区别在于它们的作用域:环境变量是全局的,而非导出的 shell 变量是脚本的本地变量。

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

1这适用于现代的炮弹一样kshdashbash和类似的。传统的 Bourne shell 和非 Bourne 语法 shellcsh具有不同的行为。


thr*_*rig 5

Shell 变量很难复制。

$ FOO=bar
$ FOO=zot
$ echo $FOO
zot
$ 
Run Code Online (Sandbox Code Playgroud)

但是环境变量可以复制;它们只是一个列表,一个列表可以有重复的条目。这就是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);
}
Run Code Online (Sandbox Code Playgroud)

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

$ make envdup
cc     envdup.c   -o envdup
$ unset FOO
$ ./envdup env | grep FOO
FOO=bar
FOO=zot
$ 
Run Code Online (Sandbox Code Playgroud)

这可能仅用于查找程序处理方式中的错误或其他奇怪之处**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
$ 
Run Code Online (Sandbox Code Playgroud)

看起来这里的 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
$ 
Run Code Online (Sandbox Code Playgroud)

天哪,如果sudo只清理第一个环境条目然后bash运行第二个环境会发生什么?你好PATHLD_RUN_PATH利用。您的sudo(以及其他所有东西?)是否为那个洞打了补丁?安全漏洞既不是“轶事差异”,也不是调用程序中的“错误”。