1.3 Linux Shell的变量

你一定想知道,Shell是如何记忆工作目录的改变的?当Shell接收到外部命令时,在庞大的文件系统中,如何迅速定位到命令文件呢?如何设定Linux Shell的环境变量?如何使黑乎乎的命令行看起来更漂亮?这些问题都是本节要解决的问题。

1.3.1 变量

变量(variable)在许多程序设计语言中都有定义,与变量相伴的有使用范围的定义。Linux Shell也不例外。变量,本质上就是一个键值对。例如,str=“hello”,就是将字符串值(value)“hello”赋予键(key)str。在str的使用范围内,我们都可以用str来引用“hello”值,这个操作叫做变量替换。

Shell变量的名称以一个字母或下划线符号开始,后面可以接任意长度的字母、数字或下划线。和许多其他程序设计语言不同的是,Shell变量名称字符并没有长度限制。Linux Shell并不对变量区分类型。一切值都是字符串,并且和变量名一样,值并没有字符长度限制。神奇的是,bash也允许比较操作和整数操作。其中关键因素是:变量中的字符串值是否为数字。例如1.5所示。

例1.5 Linux Shell中的变量

alloy@ubuntu:~/Linux Shell/ch1$ long_str="Linux_Shell_programming"

alloy@ubuntu:~/Linux Shell/ch1$ echo $long_str

Linux_Shell_programming

alloy@ubuntu:~/Linux Shell/ch1$ add_1=100

alloy@ubuntu:~/Linux Shell/ch1$ add_2=200

alloy@ubuntu:~/Linux Shell/ch1$ echo $(($add_1+$add_2))

300

由例1.5可见,虽然Linux Shell中的变量都是字符串类型的,但是同样可以执行比较操作和整数操作,只要变量字符串值是数字。

变量赋值的方式为:变量名称=值,其中“=”两边不要有任何空格。当你想使用变量名称来获得值时,在名称前加上“$”。例如,$long_str。当赋值的内容包含空格时,请加引号,例如:

alloy@ubuntu:~/Linux Shell/ch1$ with_space="this contains spaces."

alloy@ubuntu:~/Linux Shell/ch1$ echo $with_space

This contains spaces

注意:$with_space事实上只是${with_space}的简写形式,在某些上下文中$with_space可能会引起错误, 这时候你就需要用${with_space}了。

当变量“裸体”出现的时候(没有$前缀的时候),变量可能存在如下几种情况:变量被声明或被赋值;变量被unset;或者变量被export。

变量赋值可以使用“=”(比如var=27), 也可以在read命令中或者循环头进行赋值,例如,for var2 in 1 2 3。

被一对双引号("")括起来的变量替换是不会被阻止的。所以双引号被称为部分引用,有时候又被称为“弱引用”。但是如果使用单引号的话(' '),那么变量替换就会被禁止了,变量名只会被解释成字面的意思,不会发生变量替换。所以单引号被称为“全引用”,有时候也被称为“强引用”。例如:

alloy@ubuntu:~/LinuxShell/ch1$var=123

alloy@ubuntu:~/LinuxShell/ch1$ echo '$var'    #此处是单引号

alloy@ubuntu:~/LinuxShell/ch1$ echo "$var"    #此处是双引号

$var

123

在这个例子中,单引号中的$var没有替换成变量值123,也就是说,变量替换被禁止了;而双引号中的$var发生了变量替换。即:单引号为全引用(强应用),双引号为弱引用。

在Shell的世界里,变量值可以是空值(“NULL”值),就是不包含任何字符。这种情况很常见,并且也是合理的。但是在算术操作中,这个未初始化的变量常常看起来是 0。但是这是一个未文档化(并且可能是不可移植)的行为。例如:

alloy@ubuntu:~/LinuxShell/ch1$ echo "$uninit"       #未初始化变量

#此行为空,没有输出

alloy@ubuntu:~/LinuxShell/ch1$ let "uninit+=5"      #未初始化变量加5

alloy@ubuntu:~/LinuxShell/ch1$ echo "$uninit"

5                                #此行输出结果为5

alloy@ubuntu:~/LinuxShell/ch1$

Linux Shell中的变量类型有两种:局部变量和全局变量。

顾名思义,局部变量的可见范围是代码块或函数中。这一点与大部分编程语言是相同的。

但是,局部变量必须明确以local声明,否则即使在代码块中,它也是全局可见的。

环境变量是全局变量的一种。全局变量在全局范围内可见,在声明全局变量时,不需要加任何修饰词。

例1.6 测试全局变量和局部变量的适用范围

1 #! /bin/sh

2 # 测试全局变量和局部变量的适用范围

3 num=123

4 func1 ()

5 {

6 num=321             #在代码块中声明的变量

7 echo $num

8 }

9 func2()

10 {

11  local num=456        #声明为局部变量

12 echo $num

13 }

14 echo $num           #显示初始时的num变量

15 func1              #调用func1,在函数体中赋值(声明?)变量

16 echo $num           #测试num变量是否被改变

17 func2              #调用func2,显式声明局部变量

18 echo $num           #测试num变量是否被改变

我们看看例1.5的运行结果:

123         #初始值

321         #func1内被改变

321         #func1内的赋值影响到函数体外

456         #func2内声明局部变量

321         #函数体外的num未改变

例1.6的解释如下。

我们设置了一个变量num,初始值赋值为123。

调用func1,func1中的赋值命令num=321将num的123覆盖。注意,此处虽然位于函数体内,但是还是能够修改全局变量,此处的num变量就是全局环境中的num。

调用func2,func2中定义了局部(local)变量num,并且赋值456。在func2内部,num变量的值为456,此时为局部的;当func2返回后,回到全局作用区,此时num的值并未改变,为321。

1.3.2 用echo输出变量

例1.7:

alloy@ubuntu:~/Linux Shell/ch1$ echo $PATH

/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/arm/4.3.3/bin

alloy@ubuntu:~/Linux Shell/ch1$ echo "hello world!"

Hello world!

在例1.7中,我们展示了echo的使用。echo命令的任务就是输出一行文本。多用于提示用户或产生数据。

我们将在echo的manpage中显示更多选项。

echo

echo [OPTION]... [STRING]...

语法:

描述:

允许在标准输出上显示STRING(s)。

主要选项:

-n不输出行尾的换行符。

行为模式:

echo将各个参数打印到标准输出。参数间以一个空格隔开,在输出结束后,换行。它会解释每个字符串里的转义序列(escape sequences)。转义序列可以用来表示特殊字符,以及控制其行为模式。

警告:

echo命令的-n选项并不被所有Linux版本支持。POSIX标准中并未包含此选项。

转义字符可以表示程序中难以看得见或者难以输入的特殊字符。当echo遇到转义序列时,就会打印相应的字符。echo支持的转义字符如表1-1所示。

表1-1 echo的转义字符序列

1.3.3 环境变量的相关操作

在通常情况下,每个进程都有自己的“环境”,这个环境是由一组变量组成的,这些变量中存有进程可能需要引用的信息。在这种情况下,Shell与一般的进程没什么区别。

每次当一个Shell启动时,它都将创建适合于自己环境变量的Shell变量。更新或者添加一个新的环境变量的话,这个Shell都会立刻更新它自己的环境(换句话说,更改或增加的变量会立即生效),并且所有后继生成的Shell子进程(即这个Shell所执行的命令)都会继承这个环境。

如果一个脚本要设置一个环境变量,那么需要将这些变量“export”出来,也就是需要通知到脚本本地的环境。这是export命令的功能。

一个脚本只能够export变量到这个脚本所产生的子进程,也就是说只能够对这个脚本所产生的命令和进程起作用。如果脚本是从命令行中调用的,那么这个脚本所export的变量是不能影响命令行环境的。也就是说,子进程是不能够export变量来影响产生自己的父进程的环境的。但是,当使用source命令执行脚本时,因为没有子进程的产生,此时脚本中的export命令将会影响父进程的环境。

export

语法:

export [-fnp][变量名称]=[变量设置值]。

描述:

export命令用于设置或显示环境变量。

主要选项:

-f 代表[变量名称]中为函数名称。

-n 删除指定的变量。变量实际上并未删除,只是不会输出到后续指令的执行环境中。

-p 列出所有的Shell赋予程序的环境变量。

行为模式:

export命令修改当前Shell进程的环境变量。若将export命令置于脚本中被调用执行,则export命令对父Shell进程的环境变量没有影响。

警告:

Shell中执行程序时,Shell会提供一组环境变量。export可新增,修改或删除环境变量,供后续执行的程序使用。export的效力仅及于该此登录操作。

export命令用于设置当前进程的环境变量。但是有效期仅维持到当前进程消亡为止。下次重新登录到命令行Shell时,以前对Shell的export设置都无法恢复。如果想要把对环境变量的设置永久保存,则可以将export命令置于Shell登录时执行的启动文件中。例如:

# 设置环境变量PATH

export PATH=/bin:/usr/bin:/usr/X11R6/bin:/usr/local/bin

启动文件包含别名和环境变量,正是这些别名和环境变量才使得Shell可以作为一个用户Shell来运行。当系统初始化之后,这些别名和变量也可被其他的Shell脚本调用。

对于从bash来说,启动文件在表1-2中列出。

表1-2 bash的启动文件/登出文件

① 参见Shell的发展史

注意,此处的$HOME为环境变量,$HOME变量的值是登录者的用户目录。$HOME目录下存放有许多用户个人相关的文件和数据,还有对用户定制的配置文件。这些配置文件往往以“.”开头(隐藏文件)。例如:

alloy@ubuntu:~/Linux Shell/ch1$ echo $HOME #显示环境变量HOME

/home/alloy

对于其他Shell的启动文件,请参阅相关章节。

export命令设置适用于当前Shell的环境变量值。修改后维持不变,直到当前Shell消亡。env命令则可以临时改变环境变量值。

alloy@ubuntu:~/Linux Shell/ch1$ env–i PATH=./:$PATH echo.sh

“-i”选项使Shell在执行echo.sh时,清空所有由父Shell继承来的环境变量,仅仅设置命令中指定的PATH变量(将“./”也添加到命令搜寻路径里)。这样,在执行echo.sh时,就不需要给出完全路径(./echo.sh),直接给出命令文件名,系统就知道在哪里找该命令了。

unset命令从当前Shell中删除函数或变量。删除变量时,使用“-v”选项(默认情况),删除函数时,使用“-f”选项。例如:

alloy@ubuntu:~/Linux Shell/ch1$ echo $vari

123

alloy@ubuntu:~/LinuxShell/ch1$ unset vari       #删除变量(unset–v vari)

alloy@ubuntu:~/LinuxShell/ch1$ echo $vari

#此行为空,因为vari为空

alloy@ubuntu:~/LinuxShell/ch1$ hello() {echo“hello, world!”}   #定义函数

alloy@ubuntu:~/LinuxShell/ch1$ unset–f hello             #删除函数

env

语法:

env [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]

描述:

在重建的环境中运行程序,设置环境中的每个NAME为VALUE,并且运行COMMAND。

主要选项:

-i,--ignore-environment

不带环境变量启动

-u,--unset=NAME

从环境变量中删除一个变量

行为模式:

未提供COMMAND时,显示环境中所有变量的名称和值。提供COMMAND时,根据参数重建环境变量后,在新的环境中运行COMMAND。

unset

语法:

unset [-v] variable…

unset–f function…

描述:

从当前Shell删除变量或函数。

主要选项:

-f

删除指定的函数:

-v

删除指定的变量。在没有提供任何选项的情况下,默认此选项。

行为模式:

如果没有提供任何选项,则默认unset为删除变量(-v选项)。如果使用-f选项,则被视为删除函数操作,参数为函数名称。

NOTE:

Env函数和set函数不同。Env函数显示的是环境变量,而set函数则显示所有的本地变量,包括用户的环境变量。例如,当用户在命令行中设置var=123时,set函数将显示var变量,而env函数则不显示(var此时是本地变量,不是环境变量)。如果使用export var=123命令,则set命令和env命令都可以显示var变量。

1.3.4 Shell中一些常用环境变量

Linux 是一个多用户的操作系统。每个用户登录系统后,都会有一个专用的运行环境。通常用户默认的环境都是相同的,这个默认环境实际上就是一组环境变量的定义。用户可以对自己的运行环境进行定制,其方法就是修改相应的系统环境变量。

表1-3列出了一些常见的环境变量。

表1-3 常见环境变量

在这些变量中,PATH变量中存储有一系列路径,路径中以冒号(:)分隔开。格式例如:

/bin:/usr/bin:/usr/X11R6/bin:/usr/local/bin

一般来说,PATH路径中至少包含/bin和/usr/bin两个目录。大部分时候还有/usr/X11R6/bin等X相关的目录。当Shell接收到一个命令,并且这个命令非内建命令,也没有给出完整路径时,Shell则在PATH变量中依次从左到右搜索目录,直到找到该命令为止。如果一个命令在PATH中两个不同目录下都存在,则位于PATH前端的目录中的命令会被执行。

PS1/PS2变量可以改变Shell的提示符。默认情况下,普通用户的提示符是“$”,root用户的提示符是“#”。可以通过修改这两个变量让Shell的交互界面更友善。