首页 > 编程笔记 > Linux命令 阅读:12

Shell变量的用法(非常详细)

如果学习过其他编程语言,应该对变量不陌生,变量说得直白一些就是用来存放各种类型的数据。

在 Shell 中,定义变量的时候通常不需要指定数据的类型,直接赋值就可以了。因为 Shell 属于弱类型编程语言,弱类型编程语言最显著的特点就是在声明变量时不用声明数据类型,它定义的每个变量都可以赋予不同数据类型的值,在脚本运行时解释器会根据变量的类型自动转换,大家可以理解为边解释边执行。

和弱类型编程语言相对的,叫做强类型编程语言,例如 Java、C/C++ 等,最显著的特点就是要求变量的使用要严格符合定义,所有的变量都必须先定义后使用,而且一旦某个变量被指定了数据类型后,如果不经过强制转换,那么它就永远都是这个数据类型。以 Java 为例,在 Java 中,数据类型被分类成整数、小数、字符串、布尔类型等多种类型,所以在定义一个变量时需要指定这个变量中只能存放什么类型的数据。

Shell 支持以下 3 种定义变量的方式:
variable=value
variable='value'
variable="value"
variable 表示变量名,value 表示赋给变量的值。如果 value 中不包含任何空白符(例如空格、Tab 等),可以不使用引号;如果 value 中包含了空白符,就必须使用引号给引起来,使用单引号和使用双引号也是有区别的,这一点下文会详细说明。

还有一个要特别注意的地方,赋值号“=”的两侧不能有空格,这可能和其他大部分编程语言都不一样。

Shell 变量的命名规范和大部分编程语言类似:
下面是几个定义变量的案例:
[root@linux opt]# name=clinux
[root@linux opt]# name1='biancheng.net'
[root@linux opt]# name_2="biancheng.net"
[root@linux opt]# _name3=biancheng.net
使用一个已定义过的变量的方法也特别简单:
$变量名
${变量名}
变量名外面的大括号“{ }”是可选的,加不加都行,加大括号的作用是帮助解释器识别变量名的边界,比如下面这种情况:
[root@linux opt]# name="bian"          # 定义一个变量 name,赋值字符串 "biancheng"
[root@linux opt]# echo "I like to watch the c.$namecheng.net website! " # 错误使用变量方式
I like to watch the c..net website!
[root@linux opt]# echo "I like to watch the c.${name}cheng.net website! " # 正确使用变量方式
I like to watch the c.biancheng.net website!
如果不给变量 name 加大括号,写成 echo "I like to watch the c.$namecheng.net website!"的形式,Shell 解释器就会把 $namecheng 当成一个变量(变量本身不存在,为空),导致代码执行的结果脱离不是我们的预期。笔者建议大家在使用变量的过程中给所有的变量名都加上大括号,养成良好的编程习惯。

以上是使用已定义好的变量,接下来看看如何修改变量的值,相当于重新给变量赋值。
[root@linux ~]# name="c.biancheng.net"  # 定义一个变量并赋值
[root@linux ~]# echo $name
c.biancheng.net
[root@linux ~]# name="www.biancheng.net"   # 修改变量的值,相当于重新给变量赋值
[root@linux ~]# echo $name
www.biancheng.net
[root@linux ~]# name="biancheng.net"      # 可以进行多次修改
[root@linux ~]# echo $name
biancheng.net
在修改变量时要注意,对变量重新赋值时不用在变量名前加“$”,只有在使用变量时才会使用“$”,这个一定要注意。

回过头来看前面演示的示例,容易发现,在给定义的变量赋值时有的加了双引号、有的加了单引号、还有的不加任何引号,那它们之间会不会有区别?会不会使变量产生一些不同的效果?

接下来就介绍变量加单引号、双引号、反引号的区别:
【实例】单引号与双引号之间的区别。
[root@linux opt]# vim a.sh  # 写一个Shell脚本,分别用单引号与双引号展示变量的值
#!/bin/bash
url="https://c.biancheng.net"
web1='单引号显示url变量:${url}'
web2="双引号显示url变量:${url}"
echo $web1
echo $web2
[root@linux opt]# bash a.sh
单引号显示url变量:${url}
双引号显示url变量:https://c.biancheng.net
通过案例演示可以发现:

在这里给大家一个小建议:如果变量的内容是数字,可以不用加引号,如果需要原样输出就需要加上单引号。其他没有特别要求的字符串最好都加上双引号,定义变量的时候加双引号是最常见的。

再介绍一个实用的操作,就是如何将命令的结果赋值给变量,其实就是反引号的作用。

Shell 也支持将命令的执行结果赋值给变量,常见的方式有下面两种:
variable=`command`
variable=$(command)
第一种方式是把命令用反引号` `括起来,但是反引号容易与单引号混淆,不推荐使用这种方式。第二种方式是把命令用$()括起来,区分更加明显,推荐使用。

【实例】创建了一个名为 log.txt 的文本文件,使用 cat 命令将 log.txt 的内容读取出来,并将读出来的内容赋值给一个变量,再使用 echo 命令将变量的值输出到屏幕上。
[root@linux opt]# cat log.txt
c.biancheng.net
[root@linux opt]# log=`cat log.txt`  # 第一种将命令的结果赋值给变量的方式
[root@linux opt]# echo $log
c.biancheng.net
[root@linux opt]# log2=$(cat log.txt)  # 第二种将命令的结果赋值给变量的方式
[root@linux opt]# echo $log2
c.biancheng.net

可以把上面的操作写成 Shell 脚本:
[root@linux opt]# vim readfile.sh  # 编写 Shell 脚本
#!/bin/bash
echo "c.biancheng.net" > ./log.txt
log=`cat ./log.txt`
echo "Value of variable-log:$log"
log2=$(cat ./log.txt)
echo "Value of variable-log2:$log2"
[root@linux opt]# bash readfile.sh  # 执行 Shell 脚本
Value of variable-log:c.biancheng.net
Value of variable-log2:c.biancheng.net

使用 readonly 命令可以将变量定义为只读变量,只读变量的意义说白了就是变量的值不能被改变。下面我们尝试更改一下只读变量的值。
[root@linux opt]# vim read-only.sh
#!/bin/bash
myUrl="https://c.biancheng.net"
readonly myUrl
myUrl="http://www.baidu.com"
echo $myUrl
[root@linux opt]# bash read-only.sh
read-only.sh:行 4: myUrl: 只读变量
https://c.biancheng.net
在执行 Shell 脚本时,结果会报错,因为只读变量的值是无法修改的,但是我们在示例中修改了只读变量,最终的结果就是只读变量无法被修改,Shell 脚本执行报错。

下面介绍最后一个知识点,就是删除变量,使用 unset 命令就可以删除变量,语法格式为
unset variable_name
变量被删除后就不能再次使用了,有一点要注意的是,unset 命令不能删除只读变量!

【实例】使用 unset 命令删除变量。
[root@linux opt]# vim unset.sh

#!/bin/sh
myUrl="https://c.biancheng.net/"
unset myUrl
echo $myUrl
[root@linux opt]# bash unset.sh
结果就是什么都没有,因为这个变量已经被删除了,无法再进行调用。

Shell变量的作用域

Shell 变量的作用域(Scope)就是 Shell 变量的有效范围,也可以理解为 Shell 变量可以使用的范围。

在不同的作用域中,相同名称的变量不会相互干涉,谁也不会妨碍谁。比如 A 班有个叫小洲的同学,B 班也有个叫小洲的同学,虽然他们都叫小洲(变量名),但是由于所在的班级(作用域)不同,所以不会造成混乱。但是如果在同一个班级中有两个叫小洲的同学,就必须用类似于“大小洲”“小小洲”等这样的命名方式来区分他们。

作用域类型分为以下3种:
Shell 函数和 C++、Java 和 C# 等其他编程语言函数的一个不同点在于,在 Shell 函数中定义的变量默认是全局变量,它和在函数外部定义的变量效果一样。我们可以通过以下示例佐证一下。
[root@linux opt]# vim func1.sh
#!/bin/bash

# 定义一个函数
function func1(){
    a=10  # 定义一个变量 a
    echo "func 函数内部变量: $a"
}

# 调用函数
func1
# 在函数外部再次尝试调用函数内部的变量
echo "在函数外部调用变量: $a"

[root@linux opt]# bash func1.sh  # 执行 Shell 脚本
func 函数内部变量: 10
在函数外部调用变量: 10
示例中首先在函数内部定义了一个变量 a,这个变量 a 在 Shell 脚本中被调用了两次,一次是在函数内部使用,一次是在函数外部使用。执行此脚本产生的结果就是在函数外部也可以得到变量 a 的值,这就证明变量 a 的作用域是全局的,而不是仅限于函数内部使用。

要想将变量 a 的作用域控制在仅限于函数内部,可以在定义变量时加上 local 命令,此时该变量 a 就成了一个局部变量。示例如下:
[root@linux opt]# vim func1.sh
#!/bin/bash
# 定义一个函数
function func1(){
    local a=10  # 定义一个局部变量
    echo "func 函数内部变量:$a"
}

# 调用函数
func1
# 在函数外部再次尝试调用函数内部变量
echo "在函数外部调用变量:$a"

[root@linux opt]# bash func1.sh  # 执行 Shell 脚本
func 函数内部变量:10
在函数外部调用变量:
最后输出的结果为空,这就表明变量 a 在函数外部无效,只有在函数内部才是有效的,这是一个局部变量。只需要记住一点,在函数内部有效、离开函数内部就无效的变量,就是一个局部变量。

所谓全局变量,就是变量在当前的整个 Shell 进程中都是有效的。每个 Shell 进程都有自己的作用域,彼此之间互不影响。在 Shell 中定义的变量,默认都是全局变量。

想要演示出全局变量在不同 Shell 进程中的互不相关性,可以在图形界面中同时打开两个命令行终端窗口,如下图所示:


图 11  演示全局变量在不同Shell进程中的互不相关性

在图形界面打开一个命令行终端窗口,定义一个变量 a 并赋值为 10,输出变量值来验证此变量是否有效,这时另外再打开一个新的命令行终端窗口,同样尝试输出变量 a 的值,但结果却为空。

这说明全局变量 a 仅仅在定义它的第一个命令行终端(Shell 进程)有效,对新打开的命令行终端(Shell 进程)没有影响。这很好理解,就像小孙家和小刘家都有一台电视机(变量名相同),但是小孙家和小刘家的电视机中播放的节目是不同的(变量值不同)。

需要强调的是,全局变量的作用范围是当前的 Shell 进程,而不是当前的 Shell 脚本文件,它们是两个不同的概念。打开一个命令行终端就创建了一个 Shell 进程,打开多个命令行终端就创建了多个 Shell 进程,每个 Shell 进程都是独立的,拥有不同的进程 ID(PID)。在一个 Shell 进程中可以使用 source 命令执行多个 Shell 脚本,那这些 Shell 脚本中的所有全局变量在这个 Shell 进程中都是有效的。我们验证一下:
[root@linux opt]# vim OV1.sh  # 写一个 Shell 脚本,定义一个全局变量 a 并输出变量值
#!/bin/bash
a=10
echo "输出变量 a 的值:$a"

[root@linux opt]# vim OV2.sh  # 写另外一个 Shell 脚本,使用 OV1.sh 脚本中定义的全局变量 a
#!/bin/bash
echo "输出 OV1.sh 脚本中变量 a 的值:$a"

[root@linux opt]# source OV1.sh
输出变量 a 的值:10

[root@linux opt]# source OV2.sh
输出 OV1.sh 脚本中变量 a 的值:10
由示例可见,第一个 Shell 脚本 OV1.sh 中定义的全局变量 a 可以被第二个 Shell 脚本 OV2.sh 所使用,那是因为我们使用的是 source 命令,source 命令是在当前 Shell 进程中运行脚本,所以两个脚本的运行都是在同一个 Shell 进程中,这样就能佐证在多个脚本中定义的全局变量在整个Shell进程中是都有效的,都可以被使用。

接下来介绍最后一个作用域,也就是环境变量,前面我们演示全局变量示例的时候已经看到,全局变量只在当前 Shell 进程中有效,对其他 Shell 进程和子进程都无效。如果使用 export 命令(语法格式:export 变量名)将全局变量导出,那么它就在所有的子进程中也生效了,这就是所谓的“环境变量”。

环境变量在被创建的时候所处的 Shell 进程称为父进程,若在父进程中再创建一个新的Shell进程来执行命令,那这个新的进程称为子进程。Shell 子进程会继承父进程的环境变量为自己所用,因此环境变量可以由父进程传递给子进程,还可以继续传给子进程的子进程,“子子孙孙”地往下一直传递下去。

需要注意的是,两个没有父子关系的 Shell 进程之间是不能传递环境变量的,并且环境变量只能向下传递而不能向上传递,即所谓的“传子不传父”。

在演示环境变量案例之前先介绍一个命令——pstree,此命令可以以树状图的方式展现进程之间的派生关系,显示效果比较直观,因为 Linux 操作系统中进程较多,所以直接使用 pstree 命令会出现很长的树状图,这里我们可以用 grep 命令和 pstree 命令搭配使用,例如,pstree-p|grep bash这条命令专门用于查看 bash 进程的关系树状图。

【实例】用全局变量演示父子进程之间变量传递的效果。
[root@linux ~]# pstree -p | grep bash
|--sshd(1115)---sshd(6612)---sshd(6641)---bash(9535)-+-grep(10022)
# 当前的位置处于 PID 为 9535 的 Shell 进程中
[root@linux ~]# a=10
# 定义一个全局变量 a
[root@linux ~]# echo $a
# 在当前 Shell 进程中输出全局变量 a 的值
10
[root@linux ~]# bash
# 现在创建一个 Shell 子进程
[root@linux ~]# pstree -p | grep bash
|--sshd(1115)---sshd(6612)---sshd(6641)---bash(9535)---bash(10109)-+-
grep(10146)
# 可以看到当前的位置已经处于 PID 为 10109 的 Shell 子进程中
# 注:现在 PID 为 9535 的 Shell 进程就变成了 PID 为 10109 进程的父进程
[root@linux ~]# echo $a
# 输出刚才定义的全局变量 a 的值
# 可以明显看到全局变量 a 在子进程中失效了,全局变量 a 输出的值为空
[root@linux ~]# exit
# 使用 exit 命令可以退出子进程,回到父进程
exit
[root@linux ~]# pstree -p | grep bash
|--sshd(1115)---sshd(6612)---sshd(6641)---bash(9535)-+-grep(10241)
# 可以看到现在已经回到了 PID 为 9535 的父进程中,Shell 子进程在退出时就没有了
[root@linux ~]# echo $a
# 输出全局变量 a 的值
10

用环境变量演示父子进程之间变量传递的效果:
[root@linux ~]# pstree -p | grep bash
|--sshd(1115)---sshd(6612)---sshd(6641)---bash(9535)-+-grep(10418)
# 当前的位置处于 PID 为 9535 的 Shell 进程中
[root@linux ~]# export a=20
# 定义一个环境变量 a
[root@linux ~]# echo $a
# 在当前 Shell 进程中输出环境变量 a 的值
20
[root@linux ~]# bash
# 现在创建一个 Shell 子进程
[root@linux ~]# pstree -p | grep bash
|--sshd(1115)---sshd(6612)---sshd(6641)---bash(9535)---bash(10461)-+-
grep(10486)
# 当前的位置处于 PID 为 10461 的 Shell 子进程中
# 注:现在 PID 为 9535 的 Shell 进程就变成了 PID 为 10461 进程的父进程
[root@linux ~]# echo $a
# 输出刚才定义的环境变量 a 的值
20
# 可以看到父进程的环境变量已经传递给了子进程
[root@linux ~]# export b=100
# 在子进程中再定义一个环境变量 b,用来验证环境变量只能向下传递而不能向上传递
[root@linux ~]# echo $b
100
[root@linux ~]# exit
# 退出子进程,回到父进程
exit
[root@linux ~]# pstree -p | grep bash
|--sshd(1115)---sshd(6612)---sshd(6641)---bash(9535)-+-grep(10658)
# 已经回到了 PID 为 9535 的父进程中,子进程没有了
[root@linux ~]# echo $a
# 输出之前在父进程中定义的环境变量 a 的值
20
[root@linux ~]# echo $b
# 输出刚才在子进程中定义的环境变量 b 的值
# 可以看到在子进程中定义的环境变量 b 消失了,输出的值为空
通过上述两个案例可以佐证前面我们讲过的环境变量的“传子不传父”。

使用 export 命令导出的环境变量只对当前 Shell 进程及所有的子进程有效,如果最顶层的父进程被关闭了,那么环境变量也会随着父进程消失,其他的子进程也就无法再使用了,所以说环境变量其实也是临时的。

可能就有读者会问,有没有办法可以让环境变量在所有 Shell 进程中都有效?不管它们之间是不是存在父子关系。

可以的!还记得上文介绍的环境变量文件吗?只要将环境变量写进环境变量文件中就能实现。Shell 进程每次启动时都会执行环境变量文件中的每行配置,完成初始化,我们将环境变量写到环境变量文件中,结果就是每次启动 Shell 进程的时候都会通过环境变量文件自动定义变量,这样就能使得环境变量无论在哪个 Shell 进程中都会生效。

环境变量文件有以下两类:
1) 全局生效:
/etc/profile
/etc/profile.d/*.sh
/etc/bashrc
2) 某用户生效:
~/.bash_profile
~/.bashrc
技术是千变万化的,我们不能停留在理论知识学习上,只有发散思维,尝试模拟不同的场景,不断地实践,才能融会贯通。

相关文章