Bash 脚本教程(五)变量

文章目录

  • 1. Shell变量的作用域:Shell环境变量、全局变量和局部变量
    • 1.1 Shell 局部变量
    • 1.2 Shell 全局变量
    • 1.3 Shell 环境变量
  • 2. 变量查看
  • 3. 定义变量
  • 4. 读取变量
  • 5. 删除变量
  • 6. 特殊变量
    • 6.1 $?
    • 6.2 $$
    • 6.3 $_
    • 6.4 $!
    • 6.5 $-
    • 6.6 $n (n为数字)
    • 6.7 $#
    • 6.8 $* 和 $@
  • 7. 变量的默认值
  • 8. declare 命令
  • 9. readonly 命令
  • 10. let 命令

1. Shell变量的作用域:Shell环境变量、全局变量和局部变量

Shell 变量的作用域可以分为三种:

  • 有的变量还可以在子Shell进程中使用,这叫做环境变量(environment variable)。
  • 有的变量只可可以在当前 Shell 进程中使用,这叫做全局变量(global variable);
  • 有的变量只能在函数内部使用,这叫做局部变量(local variable);

1.1 Shell 局部变量

Shell 也支持自定义函数,但是 Shell 函数和 C++、Java、C# 等其他编程语言函数的一个不同点就是:在 Shell 函数中定义的变量默认也是全局变量,它和在函数外部定义变量拥有一样的效果。请看下面的代码:

#!/bin/bash

#定义函数
function func(){
    a=99
}

#调用函数
func

#输出函数内部的变量
echo $a

输出结果:

99

a 是在函数内部定义的,但是在函数外部也可以得到它的值,证明它的作用域是全局的,而不是仅限于函数内部。


要想变量的作用域仅限于函数内部,可以在定义时加上local命令,此时该变量就成了局部变量。请看下面的代码:

#!/bin/bash

#定义函数
function func(){
    local a=99
}

#调用函数
func

#输出函数内部的变量
echo $a

输出结果为空,表明变量 a 在函数外部无效,是一个局部变量。

Shell 变量的这个特性和 JavaScript 中的变量是类似的。在 JavaScript 函数内部定义的变量,默认也是全局变量,只有加上var关键字,它才会变成局部变量。

1.2 Shell 全局变量

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

想要实际演示全局变量在不同 Shell 进程中的互不相关性,可在图形界面下同时打开两个 Shell,或使用两个终端远程连接到服务器(SSH)。

首先打开一个 Shell 窗口,定义一个变量 a 并赋值为 99,然后打印,这时在同一个 Shell 窗口中是可正确打印变量 a 的值的。然后再打开一个新的 Shell 窗口,同样打印变量 a 的值,但结果却为空,如图 1 所示。

Bash 脚本教程(五)变量_第1张图片

这说明全局变量 a 仅仅在定义它的第一个 Shell 进程中有效,对新的 Shell 进程没有影响。这很好理解,就像小王家和小徐家都有一部电视机(变量名相同),但是同一时刻小王家和小徐家的电视中播放的节目可以是不同的(变量值不同)。

需要强调的是,全局变量的作用范围是当前的 Shell 进程,而不是当前的 Shell 脚本文件,它们是不同的概念。打开一个 Shell 窗口就创建了一个 Shell 进程,打开多个 Shell 窗口就创建了多个 Shell 进程,每个 Shell 进程都是独立的,拥有不同的进程 ID。在一个 Shell 进程中可以使用 source 命令执行多个 Shell 脚本文件,此时全局变量在这些脚本文件中都有效。

例如,现在有两个 Shell 脚本文件,分别是 a.sh 和 b.sh。a.sh 的代码如下:

#!/bin/bash
echo $a
b=200

b.sh 的代码如下:

#!/bin/bash
echo $b

打开一个 Shell 窗口,输入以下命令:

[root@VM-4-17-centos ~]# vim a.sh
[root@VM-4-17-centos ~]# vim b.sh
[root@VM-4-17-centos ~]# a=99
[root@VM-4-17-centos ~]# . ./a.sh
99
[root@VM-4-17-centos ~]# . ./b.sh
200

这三条命令都是在一个进程中执行的,从输出结果可以发现,在 Shell 窗口中以命令行的形式定义的变量 a,在 a.sh 中有效;在 a.sh 中定义的变量 b,在 b.sh 中也有效,变量 b 的作用范围已经超越了 a.sh

1.3 Shell 环境变量

全局变量只在当前 Shell 进程中有效,对其它 Shell 进程和子进程都无效。如果使用export命令将全局变量导出,那么它就在所有的子进程中也有效了,这称为“环境变量”。

环境变量被创建时所处的 Shell 进程称为父进程,如果在父进程中再创建一个新的进程来执行 Shell 命令,那么这个新的进程被称作 Shell 子进程。当 Shell 子进程产生时,它会继承父进程的环境变量为自己所用,所以说环境变量可从父进程传给子进程。不难理解,环境变量还可以传递给孙进程。

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

创建 Shell 子进程最简单的方式是运行 bash 命令,如图 2 所示。

Bash 脚本教程(五)变量_第2张图片
通过exit命令可以一层一层地退出 Shell。

下面演示一下环境变量的使用:

[c.biancheng.net]$ a=22       #定义一个全局变量
[c.biancheng.net]$ echo $a    #在当前Shell中输出a,成功
22
[c.biancheng.net]$ bash       #进入Shell子进程
[c.biancheng.net]$ echo $a    #在子进程中输出a,失败

[c.biancheng.net]$ exit       #退出Shell子进程,返回上一级Shell
exit
[c.biancheng.net]$ export a   #将a导出为环境变量
[c.biancheng.net]$ bash       #重新进入Shell子进程
[c.biancheng.net]$ echo $a    #在子进程中再次输出a,成功
22
[c.biancheng.net]$ exit       #退出Shell子进程
exit
[c.biancheng.net]$ exit       #退出父进程,结束整个Shell会话

可以发现,默认情况下,a 在 Shell 子进程中是无效的;使用 export 将 a 导出为环境变量后,在子进程中就可以使用了。

export a这种形式是在定义变量 a 以后再将它导出为环境变量,如果想在定义的同时导出为环境变量,可以写作export a=22。

我们一直强调的是环境变量在 Shell 子进程中有效,并没有说它在所有的 Shell 进程中都有效;如果你通过终端创建了一个新的 Shell 窗口,那它就不是当前 Shell 的子进程,环境变量对这个新的 Shell 进程仍然是无效的。请看下图:

Bash 脚本教程(五)变量_第3张图片
第一个窗口中的环境变量 a 在第二个窗口中就无效。


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

有读者可能会问,如果我想让一个变量在所有 Shell 进程中都有效,不管它们之间是否存在父子关系,该怎么办呢?

只有将变量写入 Shell 配置文件中才能达到这个目的!Shell 进程每次启动时都会执行配置文件中的代码做一些初始化工作,如果将变量放在配置文件中,那么每次启动进程都会定义这个变量。

2. 变量查看

  • env命令或printenv命令,可以显示所有环境变量。
$ env
# 或者
$ printenv
  • set命令可以显示所有变量(包括环境变量和自定义变量),以及所有的 Bash 函数。
[root@VM-4-17-centos ~]# set | less
ABRT_DEBUG_LOG=/dev/null
BASH=/bin/bash
BASHOPTS=checkwinsize:cmdhist:expand_aliases:extglob:extquote:force_fignore:histappend:interactive_comments:login_shell:progcomp:promptvars:sourcepath
BASH_ALIASES=()
BASH_ARGC=()
BASH_ARGV=()
BASH_CMDS=()
BASH_COMPLETION_COMPAT_DIR=/etc/bash_completion.d
BASH_LINENO=()
BASH_SOURCE=()
BASH_VERSINFO=([0]="4" [1]="2" [2]="46" [3]="2" [4]="release" [5]="x86_64-redhat-linux-gnu")
BASH_VERSION='4.2.46(2)-release'
COLUMNS=210
DIRSTACK=()
EUID=0
....... 略

3. 定义变量

Shell 支持以下三种定义变量的方式:

  • variable=value
  • variable=‘value’
  • variable=“value”

Bash 没有数据类型的概念,所有的变量值都是字符串。variable 是变量名,value 是赋给变量的值。如果 value 不包含任何空白符(例如空格、Tab 缩进等),那么可以不使用引号;如果 value 包含了空白符,那么就必须使用引号包围起来。使用单引号和使用双引号也是有区别的,稍后我们会详细说明。

注意,赋值号=的周围不能有空格,这可能和你熟悉的大部分编程语言都不一样。


Shell 变量的命名规范和大部分编程语言都一样:

  • 变量名由数字、字母、下划线组成;
  • 必须以字母或者下划线开头;
  • 不能使用 Shell 里的关键字(通过 help 命令可以查看保留关键字)。

下面是一些自定义变量的例子。

a=z                     # 变量 a 赋值为字符串 z
b="a string"            # 变量值包含空格,就必须放在引号里面
c="a string and $b"     # 变量值可以引用其他变量的值
d="\t\ta string\n"      # 变量值可以使用转义字符
e=$(ls -l foo.txt)      # 变量值可以是命令的执行结果
f=$((5 * 7))            # 变量值可以是数学运算的结果

变量可以重复赋值,后面的赋值会覆盖前面的赋值。

$ foo=1
$ foo=2
$ echo $foo
2

上面例子中,变量foo的第二次赋值会覆盖第一次赋值。

4. 读取变量

读取变量的时候,直接在变量名前加上$就可以了。

$ foo=bar
$ echo $foo
bar

每当 Shell 看到以$开头的单词时,就会尝试读取这个变量名对应的值。

如果变量不存在,Bash 不会报错,而会输出空字符


读取变量的时候,变量名也可以使用花括号{}包围,比如$a也可以写成${a}。这种写法可以用于变量名与其他字符连用的情况。

$ a=foo
$ echo $a_file

$ echo ${a}_file
foo_file

上面代码中,变量名a_file不会有任何输出,因为 Bash 将其整个解释为变量,而这个变量是不存在的。只有用花括号区分$a,Bash 才能正确解读。

事实上,读取变量的语法$foo,可以看作是${foo}的简写形式。


如果变量的值本身也是变量,可以使用${!varname}的语法,读取最终的值。

[root@VM-4-17-centos ~]# myvar=USER
[root@VM-4-17-centos ~]# echo ${!myvar}
root

上面的例子中,变量myvar的值是USER${!myvar}的写法将其展开成最终的值。


如果变量值包含连续空格(或制表符和换行符),最好放在双引号里面读取。

$ a="1 2  3"
$ echo $a
1 2 3
$ echo "$a"
1 2  3

上面示例中,变量a的值包含两个连续空格。如果直接读取,Shell 会将连续空格合并成一个。只有放在双引号里面读取,才能保持原来的格式。

5. 删除变量

unset命令用来删除一个变量。

unset NAME

这个命令不是很有用。因为不存在的 Bash 变量一律等于空字符串,所以即使unset命令删除了变量,还是可以读取这个变量,值为空字符串。

所以,删除一个变量,也可以将这个变量设成空字符串。

$ foo=''
$ foo=

上面两种写法,都是删除了变量foo。由于不存在的值默认为空字符串,所以后一种写法可以在等号右边不写任何值。

6. 特殊变量

6.1 $?

$?(功能描述:最后一次执行的命令的返回状态。如果这个变量的值为0,证明上一个命令正确执行;如果这个变量的值为非0(具体是哪个数,由命令自己来决定),则证明上一个命令执行不正确了。)

测试:

[root@VM-4-17-centos shell_program]# echo $?
0

6.2 $$

$$表示当前Shell进程ID。对于 Shell 脚本,就是这些脚本所在的进程ID。

[root@VM-4-17-centos shell-program]# echo $$
10606

6.3 $_

$_为上一个命令的最后一个参数。

$ grep dictionary /usr/share/dict/words
dictionary

$ echo $_
/usr/share/dict/words

6.4 $!

$!为最近一个后台执行的异步命令的进程 ID。

$ firefox &
[1] 11064

$ echo $!
11064

上面例子中,firefox是后台运行的命令,$!返回该命令的进程 ID。

6.5 $-

$-为当前 Shell 的启动参数。

$ echo $-
himBHs

6.6 $n (n为数字)

$n(功能描述: n为数字,$0代表该脚本名称,$1-$9代表第一到第九个参数,十以上的参数,十以上的参数需要用大括号包含,如${10})

parameter.sh如下:

#! /bin/bash

echo '===========$n============' #注意:如果使用的是双引号,$n会被识别为变量
echo script name: $0
echo 1st parameter: $1
echo 2nd parameter: $2

测试:

[root@VM-4-17-centos shell_program]# ./parameter.sh 
===========$n============
script name: ./parameter.sh
1st parameter:
2nd parameter:
[root@VM-4-17-centos shell_program]# ./parameter.sh zhx yxy
===========$n============
script name: ./parameter.sh
1st parameter: zhx
2nd parameter: yxy

6.7 $#

$#(功能描述:获取所有输入参数个数,常用于循环,判断参数的个数是否正确以及加强脚本的健壮性)。

parameter.sh如下:

#! /bin/bash

echo '===========$n============' #注意:如果使用的是双引号,$n会被识别为变量
echo script name: $0
echo 1st parameter: $1
echo 2nd parameter: $2
echo '===========$#============'
echo parameter numbers: $#

测试:

[root@VM-4-17-centos shell_program]# ./parameter.sh zhx yxy
===========$n============
script name: ./parameter.sh
1st parameter: zhx
2nd parameter: yxy
===========$#============
parameter numbers: 2

6.8 $* 和 $@

$* (功能描述:这个变量代表命令行中所有的参数,$*把所有的参数看成一个整体)
$@ (功能描述:这个变量也代表命令行中所有的参数,不过$@把每个参数区分对待);被双引号(" ")包含时,与 $* 稍有不同;

parameter.sh如下:

#! /bin/bash

echo '===========$n============' #注意:如果使用的是双引号,$n会被识别为变量
echo script name: $0
echo 1st parameter: $1
echo 2nd parameter: $2
echo '===========$#============'
echo parameter numbers: $#
echo '===========$*============'
echo $*
echo '===========for i in $*============'
for i in $*
do
        echo $i
done
echo '===========for i in "$*"============'
for i in "$*"
do
        echo $i
done
echo '===========$@============'
echo $@
echo '===========for i in $@============'
for i in $@
do
        echo $i
done
echo '===========for i in "$@"============'
for i in "$@"
do
        echo $i
done



测试:

[root@VM-4-17-centos parameter]# ./parameter.sh zhx yxy
===========$n============
script name: ./parameter.sh
1st parameter: zhx
2nd parameter: yxy
===========$#============
parameter numbers: 2
===========$*============
zhx yxy
===========for i in $*============
zhx
yxy
===========for i in "$*"============
zhx yxy
===========$@============
zhx yxy
===========for i in $@============
zhx
yxy
===========for i in "$@"============
zhx
yxy

7. 变量的默认值

Bash 提供四个特殊语法,跟变量的默认值有关,目的是保证变量不为空。

${varname:-word}

上面语法的含义是,如果变量varname存在且不为空,则返回它的值,否则返回word。它的目的是返回一个默认值,比如**${count:-0}表示变量count不存在时返回0。**

${varname:=word}

上面语法的含义是,如果变量varname存在且不为空,则返回它的值,否则将它设为word,并且返回word。它的目的是设置变量的默认值,比如${count:=0}表示变量count不存在时返回0,且将count设为0。

${varname:+word}

上面语法的含义是,如果varname存在且不为空,则返回word,否则返回空值。它的目的是测试变量是否存在,比如${count:+1}表示变量count存在时返回1(表示true),否则返回空值。

${varname:?message}

上面语法的含义是,**如果变量varname存在且不为空,则返回它的值,否则打印出varname: message,并中断脚本的执行。**如果省略了message,则输出默认的信息“parameter null or not set.”。它的目的是防止变量未定义,比如${count:?"undefined!"}表示变量count未定义时就中断执行,抛出错误,返回给定的报错信息undefined!

上面四种语法如果用在脚本中,变量名的部分可以用数字1到9,表示脚本的参数。

filename=${1:?"filename missing."}

上面代码出现在脚本中,1表示脚本的第一个参数。如果该参数不存在,就退出脚本并报错。

8. declare 命令

declare命令可以声明一些特殊类型的变量,为变量设置一些限制,比如声明只读类型的变量和整数类型的变量。

它的语法形式如下:

declare OPTION VARIABLE=value

declare命令的主要参数(OPTION)如下。

  • -a:声明数组变量。
  • -f:输出所有函数定义。
  • -F:输出所有函数名。
  • -i:声明整数变量。
  • -l:声明变量为小写字母。
  • -p:查看变量信息。
  • -r:声明只读变量。
  • -u:声明变量为大写字母。
  • -x:该变量输出为环境变量。

declare命令如果用在函数中,声明的变量只在函数内部有效,等同于local命令。

不带任何参数时,declare命令输出当前环境的所有变量,包括函数在内,等同于不带有任何参数的set命令。

$ declare

9. readonly 命令

readonly命令等同于declare -r,用来声明只读变量,不能改变变量值,也不能unset变量。

$ readonly foo=1
$ foo=2
bash: foo:只读变量
$ echo $?
1

上面例子中,更改只读变量foo会报错,命令执行失败。

readonly命令有三个参数。

  • -f:声明的变量为函数名。
  • -p:打印出所有的只读变量。
  • -a:声明的变量为数组。

10. let 命令

let命令声明变量时,可以直接执行算术表达式。

$ let foo=1+2
$ echo $foo
3

上面例子中,let命令可以直接计算1 + 2


let命令的参数表达式如果包含空格,就需要使用引号

$ let "foo = 1 + 2"

let可以同时对多个变量赋值,赋值表达式之间使用空格分隔

$ let "v1 = 1" "v2 = v1++"
$ echo $v1,$v2
2,1

上面例子中,let声明了两个变量v1和v2,其中v2等于v1++,表示先返回v1的值,然后v1自增。

你可能感兴趣的:(linux系列,bash,开发语言)