Answers:
变量是shell中参数的三种不同变体之一。
_
或字母开头,然后是零个或多个字母,数字或_
。$1
,$2
...$0
,他们都是不同的标点字符。set
仅显示外壳程序的变量。
Shell变量的一个子集是环境变量,其值可以在Shell启动时从环境继承,也可以通过将export
属性设置为有效名称来创建。
set
显示所有参数zsh
(不是$ 1,$ 2 ...,而是$ *,$ @)以及bash和bosh中的函数。某些shell(例如ksh93和旧版的dash输出env vars)未映射到shell变量。(env 1=foo ksh -c set
将会打印1=foo
)
在开始讨论$INTEGER
变量类型之前,我们需要了解它们的真正含义以及它们与环境变量的区别。诸如$INTEGER
位置参数之类的变量。在POSIX(便携式操作系统接口)标准的第2.1节(强调我的)中对此进行了描述:
- 外壳程序执行一个函数(请参见“功能定义命令”),内置函数(请参见“特殊内置实用程序”),可执行文件或脚本,并给出参数名称(位置编号为1到n的位置参数)以及命令的名称。 (如果是脚本中的函数,则为脚本名称)作为位置参数编号0(请参见命令搜索和执行)。
相反,诸如$HOME
和$PATH
的变量是环境变量。其定义在标准的第8节中进行了描述:
本章中定义的环境变量会影响多个实用程序,函数和应用程序的操作。还有其他仅适用于特定实用程序的环境变量。仅适用于单个实用程序的环境变量被定义为实用程序描述的一部分。
注意它们的描述。位置参数应出现在命令前面,即command positional_arg_1 positional_arg_2...
。它们应由用户提供以告知命令具体要执行的操作。这样做时echo 'Hello' 'World'
,它将打印出Hello
和World
字符串,因为这些是echo
您要echo
操作的对象的位置参数。并且echo
以这样的方式构建:它将位置参数理解为要打印的字符串(除非它们是可选标记之一-n
)。如果您使用其他命令执行此操作,则可能无法理解,Hello
并且World
是因为它可能期望一个数字。请注意,位置参数不是“继承”的-子进程不知道父级的位置参数,除非显式传递给子进程。通常,您会看到包装器脚本传递了位置参数-那些可能会检查命令的现有实例或将其他位置参数添加到将要调用的实际命令中的脚本。
相反,环境变量是要影响多个程序的。它们是环境变量,因为它们是在程序本身之外设置的(请参见下文)。某些环境变量(例如HOME
或)PATH
具有特定的格式,特定的含义,并且它们对每个程序的含义相同。HOME
变量对于像这样的外部实用程序/usr/bin/find
或您的shell(以及因此对脚本)的含义都相同-这是在其下运行进程的用户名的主目录。请注意,环境变量可用于说明特定的命令行为,例如UID
环境变量可用于检查脚本是否以root特权运行,并相应地跳转到特定操作。环境变量是可继承的-子进程获取父级环境的副本。另请参见如果进程继承了父级的环境,为什么我们需要导出?
简而言之,主要区别是环境变量是在命令外部设置的,通常不打算被更改,而位置参数是命令要处理的事物并且它们会改变。
我从评论中注意到,您正在混合使用终端和外壳,并真的建议您阅读有关真实终端的信息,这些终端曾经是物理设备。如今,我们通常指的是带有黑色背景和绿色文本的窗口的“终端”实际上是一个过程。Terminal是一个运行Shell的程序,而Shell也是一个程序,但是它会读取您键入的内容以执行(即,如果它是交互式Shell;非交互式Shell是脚本和sh -c 'echo foo'
调用类型)。更多关于贝壳的信息。
这是一个重要的区别,但也很重要,要认识到终端是一个程序,因此遵循相同的环境和位置参数规则。您的gnome-terminal
启动时间将查看您的SHELL
环境变量,并为您生成适当的默认外壳程序,除非您使用指定了其他命令-e
。假设我将默认外壳程序更改为ksh
-gnome-terminal,然后生成ksh
而不是bash
。这也是程序如何使用环境的一个示例。如果我明确告诉gnome-terminal
with -e
运行特定的shell,它将执行此操作,但它不会是永久的。相比之下,环境几乎应该保持不变(稍后会详细介绍)。
如您所见,环境变量和位置变量都是进程/命令的属性,而不仅仅是外壳。对于shell脚本,它们还遵循C编程语言设置的模型。以C main
函数为例,
int main(int argc, char **argv)
,其中 argc
是命令行参数的数目,并且argv
是命令行参数的有效数组,然后有environ
功能(在Linux上man -e 7 environ
)访问用户的主目录路径,PATH
可以在其中查找可执行文件的目录列表等内容。 Shell脚本也以类似方式建模。在外壳术语中,我们有位置参数$1
,$2
依此类推,而$#
位置参数是数量。那$0
呢 那就是可执行文件本身的名称,同样也是从C编程语言建模而来的- argv[0]
就是您的C“可执行文件”的名称。对于大多数编程和脚本语言来说,这都是正确的。
我已经暗示过的一件事是交互式和非交互式shell之间的区别。您在其中键入命令的提示-是交互式的,它与用户交互。相反,当您具有Shell脚本或运行bash -c''
该脚本时,它是非交互式的。
这就是区别变得重要的地方。您已经运行的shell是一个进程,该进程是使用位置参数生成的(对于bash
登录shell,它是一个“ ...其参数零的第一个字符是-或以--login选项开头的一个。”(参考) )
相比之下,使用-c
option 启动的脚本和shell 可以利用$1
and $2
参数。例如,
$ bash -c 'echo $1; stat $2' sh 'Hello World' /etc/passwd
Hello World
File: '/etc/passwd'
Size: 2913 Blocks: 8 IO Block: 4096 regular file
Device: 801h/2049d Inode: 6035604 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2017-08-12 14:48:37.125879962 -0600
Modify: 2017-08-12 14:48:37.125879962 -0600
Change: 2017-08-12 14:48:37.137879811 -0600
Birth: -
请注意,我也在这里使用过sh
,因为-c
option的一个小怪癖是采用第一个位置参数并将其分配给,这$0
通常不同于程序的名称。
需要注意的另一件事是位置参数就是我所说的“易碎”。请注意,我们是如何首先bash
使用其自己的位置参数启动的,但是这些位置参数成为echo
和的参数stat
。每个程序都以自己的方式理解它。如果我们给stat
一个字符串,Hello World
而没有文件Hello World
,它将产生一个错误;bash
将其视为简单字符串,但stat
希望该字符串是现有文件名。相反,所有程序都同意环境变量HOME
是目录(除非程序员以不合理的方式对其进行了编码)。
从技术上讲,我们可以两者兼而有之,但是我们不应该随便使用环境变量,而我们经常必须提供位置参数。我们可以在shell的前面加上一个变量来运行命令,例如:
$ hello=world bash -c 'echo $hello'
world
我们还可以简单地export variable=value
从shell或脚本中使用变量到环境中。或者,我们可以使用完全空白的环境运行命令env -c command arg1 arg2
。但是,通常不建议您弄乱环境,尤其是使用大写变量或覆盖已经存在的环境变量。请注意,尽管不是标准建议,但仍建议这样做。
对于位置参数,设置它们的方法是显而易见的,只需将它们放在命令之前,但也可以通过其他方式设置它们,以及通过shift
命令更改这些参数的列表。
总之,这两个目的不同,并且存在是有原因的。我希望人们从这个答案中获得一些见识,并且阅读它就像我写这个答案一样很有趣。
set
根据手册,命令的行为如下所示(从bash手册开始,强调了):
如果没有选项,则每个shell变量的名称和值都以一种格式显示,该格式可以重新用作设置或重置当前设置的变量的输入。
换句话说,set
查看特定于shell的变量,例如其中一些恰好在环境中HOME
。相比之下,命令like env
和则printenv
查看与命令一起运行的实际环境变量。另请参见本。
1="foo"
但后来我发现,通过POSIX定义,“单词”(即变量或函数等对象的名称)无法启动用数字(请参阅我在该主题上发布的问题)。位置参数显然是该规则的例外。
$1
,$2
等等,实际上,这是通过set
命令完成的,通常可以解决/bin/sh
没有数组的限制。感谢您引起我的注意。我还将在接下来的几天中编辑答案,因为它需要一些额外的修饰和更新。
conda
,当你运行source conda/bin/activate
,它会检查是否$1
,$2
等等,都设置,以确定它是否是运行与参数或没有脚本。由于某种原因,这最终破坏了在交互式环境中设置了系统的系统。我希望找出这种非标准行为是在交互式环境中设置这些变量的系统中的缺陷,还是在使用它们确定是否作为脚本运行的程序上存在缺陷。
conda
开发人员或该脚本的原始作者提交错误报告,因为检查${N}
参数绝对是错误的方法。这里和这里都存在关于同一主题的问题,或多或少的可移植方式是检查是否${0}
与脚本名称相同,而bash
实际上为此目的具有环境变量
这些$1, $2, $3, ..., ${10}, ${11}
变量称为位置参数,在bash手册部分中有介绍。3.4.1
3.4.1位置参数
位置参数是用一位或多位数字表示的参数,而不是一位数字0。位置参数是在调用Shell时从shell的参数中分配的,可以使用set builtin命令重新分配。位置参数N可以引用为$ {N},或者当N由单个数字组成时引用为$ N。位置参数不能与赋值语句一起赋值。set和shift内置插件用于设置和取消设置它们(请参见Shell Builtin命令)。当执行Shell函数时,位置参数将被临时替换(请参见Shell函数)。
扩展由多个位数组成的位置参数时,必须将其括在大括号中。
至于$?
和$0
,这些特殊参数将在下一部分中介绍3.4.2
3.4.2特殊参数
外壳专门处理几个参数。这些参数只能被引用;不允许分配给他们。
...
?
($?)扩展为最近执行的前台管道的退出状态。
0
($ 0)扩展为shell或shell脚本的名称。这是在外壳初始化时设置的。如果使用命令文件调用Bash(请参见Shell脚本),则将$ 0设置为该文件的名称。如果Bash以-c选项启动(请参阅调用Bash),则将$ 0设置为要执行的字符串之后的第一个参数(如果存在)。否则,将其设置为用于调用Bash的文件名,如参数0所示。
$1
,$2
...是位置参数,不是变量,更不用说环境变量了。
在类似Bourne壳术语,$something
被称为参数膨胀(也盖${something#pattern}
和更多的像一些壳${array[x]}
,${param:offset}
,${x:|y}
和许多更多的扩展运算符)。
有不同种类的参数:
$foo
,$PATH
$1
,$2
...您的脚本收到的参数)$0
,$-
,$#
,$*
,$@
,$$
,$!
,$?
...像shell一样的Bourne中的变量名必须以一个字母字符(任何语言环境可以识别,或者取决于shell限制为a-zA-Z)和下划线开头,后跟零个或多个字母数字字符或下划线。
根据外壳的不同,变量可以具有不同的类型(标量,数组,哈希)或具有特定的属性(只读,导出,小写...)。
其中有些变量是由外壳产生或有特殊含义的外壳(例如$OPTIND
,$IFS
,$_
...)
具有export属性的Shell变量会自动作为环境变量导出到Shell执行的命令。
环境变量是与Shell变量分开的概念。导出shell变量并不是将环境变量传递给命令执行的唯一方法。
VAR=foo
export VAR
printenv VAR
会将VAR
环境变量传递给printenv
命令(我们要告诉它打印其内容),但是您也可以执行以下操作:
env VAR=foo printenv VAR
要么:
perl -e '$ENV{VAR}="foo"; exec "printenv", "VAR"'
例如。
环境变量可以具有任何名称(可以包含任何字符,但=
甚至可以为空)。给环境变量指定一个不像Bourne的shell变量名称那样的名字,这不是一个好主意,但是可以:
$ env '#+%=whatever' printenv '#+%'
whatever
Shell只会将名称为有效Shell变量的那些环境变量映射到它们收到的环境变量(并且在某些Shell中忽略某些特殊变量,例如$IFS
)。
因此,尽管您可以将1
环境变量传递给命令:
$ env '1=whatever' printenv 1
whatever
这并不意味着使用该环境变量调用shell会设置$1
参数的值:
$ env '1=whatever' sh -c 'echo "$1"' script-name foo bar
foo
不,这些是脚本的参数。例如,如果您以如下方式调用脚本:
mynicescript.sh one two three
然后在脚本中,这些参数将作为
$1 = one
$2 = two
$3 = three
$ 0是脚本本身的名称。
因此,当您不在脚本之外时,这些变量将不可用($ 0除外,它显示/ bin / bash-shell本身)。
$0
将指向您当前的终端进程(可能是bash),$?
并且仅仅是最后一个进程的退出代码。
gnome-terminal
使用参数(gnome-terminal Hello World
)运行。我能看到$0
,但我看不出$1
和$2
。
export 3
将其$3
转换为环境变量。你不能unset 3
; 并且您不能使用分配$3
新值3=val
。