实际上我不知道可以从命令行访问两种不同类型的变量。我所知道的是,我可以声明如下变量:
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"
现在,我听说有两种(至少?)类型的变量:shell 变量和环境变量。
- 拥有两种不同类型的目的是什么?
- 我如何知道变量是什么类型?
- 每一种的典型用途是什么?
答案1
外壳变量
Shell 变量是范围在当前 shell 会话中的变量,例如在交互式 shell 会话或脚本中。
您可以通过将值分配给未使用的名称来创建 shell 变量:
var="hello"
使用 shell 变量是为了跟踪当前会话中的数据。shell 变量的名称通常为小写字母。
环境变量
环境变量是已导出的 shell 变量。这意味着它将作为变量可见,不仅在创建它的 shell 会话中可见,而且对于从该会话启动的任何进程(不仅仅是 shell)也可见。
VAR="hello" # shell variable created
export VAR # variable now part of the environment
或者
export VAR="hello"
一旦 shell 变量被导出,它就会保持导出状态,直到它被取消设置,或者直到它的“导出属性”被删除(使用export -n
in bash
),因此通常不需要重新导出它。取消设置变量会unset
删除它(无论它是否是环境变量)。
和其他 shell中的数组和关联哈希值bash
可能无法导出为环境变量。环境变量必须是简单变量,其值为字符串,并且它们的名称通常由大写字母组成。
环境变量的使用是为了跟踪当前 shell 会话中的数据,同时也允许任何已启动的进程获取该数据的一部分。典型的情况是PATH
环境变量,它可以在 shell 中设置,然后由任何想要启动程序而无需指定程序的完整路径的程序使用。
进程中环境变量的集合通常称为“进程的环境”。每个进程都有自己的环境。
环境变量只能“转发”,即子进程可以绝不更改父进程中的环境变量,除了在启动子进程时为其设置环境之外,父进程不能更改子进程的现有环境。
env
环境变量可以用(不带任何参数)列出。除此之外,它们与 shell 会话中的非导出 shell 变量相同。这对于 shell 来说有点特殊,因为大多数其他编程语言通常不会将“普通”变量与环境变量混合在一起(见下文)。
env
也可用于设置进程环境中的一个或多个环境变量的值,而无需在当前会话中设置它们:
env CC=clang CXX=clang++ make
首先make
将环境变量CC
设置为值clang
并CXX
设置为clang++
。
它也可用于清除流程的环境:
env -i bash
这会启动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
其他语言
大多数编程语言中都有允许获取和设置环境变量的库函数。请注意,由于环境变量存储为简单的键值关系,因此它们通常不是语言的“变量”。程序可以获取与键(环境变量的名称)相对应的值(始终是字符串),但随后必须将其转换为整数或语言期望该值具有的任何数据类型。
在 C 中,可以使用getenv()
、setenv()
和putenv()
来访问环境变量unsetenv()
。 C 程序启动的任何进程都会以相同的方式继承使用这些例程创建的变量。
其他语言可能有特殊的数据结构来完成同样的事情,比如%ENV
Perl 中的哈希,或者ENVIRON
大多数awk
.
答案2
环境变量是name=value
无论程序是什么(shell、应用程序、守护进程......)都存在的成对列表。它们通常由子进程继承(由fork
/exec
序列创建):子进程获取自己的父变量副本。
Shell 变量确实仅存在于 shell 上下文中。它们仅在子 shell 中继承(即,当 shell 在没有exec
操作的情况下分叉时)。根据 shell 功能,变量可能不仅是简单的字符串(如环境字符串),还可能是数组、复合变量、类型变量(如整数或浮点)等。
当 shell 启动时,它从其父级继承的所有环境变量也成为 shell 变量(除非它们作为 shell 变量无效以及其他极端情况(例如IFS
由某些 shell 重置)),但这些继承的变量被标记为导出1。这意味着它们将继续可供子进程使用,并具有 shell 设置的可能更新的值。在 shell 下创建并使用关键字标记为导出的变量也是如此export
。
数组和其他复杂类型变量不能导出,除非它们的名称和值可以转换为模式name=value
,或者当 shell 特定机制到位时(例如:bash
导出环境中的函数和一些外来的非 POSIX shell,例如rc
andes
可以导出数组) )。
因此,环境变量和 shell 变量之间的主要区别在于它们的范围:环境变量是全局的,而非导出的 shell 变量是脚本的本地变量。
另请注意,现代 shell(至少ksh
和bash
)支持第三个 shell 变量范围。使用typeset
关键字在函数中创建的变量是该函数的本地变量(声明函数的方式在 下启用/禁用此功能,并且和ksh
之间的持久性行为不同)。看bash
ksh
https://unix.stackexchange.com/a/28349/2594
1这适用于现代 shell,如ksh
、dash
、bash
和类似的。旧版 Bourne shell 和非 Bourne 语法 shellcsh
具有不同的行为。
答案3
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
运行第二个环境条目,会发生什么?你好PATH
或LD_RUN_PATH
利用。是你的sudo
(和其他一切?)修补了那个洞?安全漏洞既不是调用程序中的“轶事差异”,也不仅仅是“错误”。
答案4
一个环境变量就像一个外壳变量,但它并不特定于壳。所有进程均开启Unix系统有环境变量存储。 环境变量和 shell 变量之间的主要区别是: 认为操作系统通过了您的所有shell的环境变量到 shell 运行的程序,而 shell 变量无法在您运行的命令中访问。
env –
该命令允许您在自定义环境中运行另一个程序,而无需修改当前程序。当不带参数使用时,它将打印当前环境变量的列表。
printenv –
该命令打印所有或指定的环境变量。
set –
该命令设置或取消设置 shell 变量。当不带参数使用时,它将打印所有变量的列表,包括环境和 shell 变量以及 shell 函数。
unset –
该命令删除 shell 和环境变量。
export –
该命令设置环境变量