对于任何编程语言,第一步往往是变量的读写(第零步是向屏幕上打印hello world,这里我们直接略过,相信读完这一节大家就知道怎么做到了‘_’),让我们先从变量开始。
变量声明、定义和读取
变量,是值可以变化的量(与之相对的是常量,其值不允许改变)。我们先定义一个变量a
[ken@Dell-Desktop ~]$ a=中国
[ken@Dell-Desktop ~]$ echo $a
中国
我们先给a赋值为字符串“中国”,然后在屏幕上打印它($a后面介绍)。这里要注意,与大多数语言不同,中间=号的两侧不可以有空白字符(空白字符包括空格和Tab等),否则Bash将不认为这是个变量赋值表达式。
[ken@Dell-Desktop ~]$ a = 3
-bash: a: command not found
假如我们希望声明一个变量a,但不给它赋任何值,可以吗?
[ken@Dell-Desktop ~]$ a
-bash: a: command not found
看来这样子行不通。Bash中声明一个变量需要用到Bash内置命令declare,演示如下
点击查看代码
[ken@Dell-Desktop ~]$ declare b
[ken@Dell-Desktop ~]$ echo $b
[ken@Dell-Desktop ~]$ declare b2=3
[ken@Dell-Desktop ~]$ echo $b2
3
这样子首先声明了一个变量b,且没有赋值给它,接着声明了第二个变量b2并赋值为3。
常量的声明和定义
假如我们希望定义一个常量,也可以借助declare来做到。
点击查看代码
[ken@Dell-Desktop ~]$ declare -r c1=3
[ken@Dell-Desktop ~]$ echo $c1
3
[ken@Dell-Desktop ~]$ c1=4
-bash: c1: readonly variable
我们通过declare -r声明了一个只读(readonly)的变量c1,之后如果尝试修改它的话,就会提示失败。
其实这个declare的用法颇有一点复杂,这里稍微进一步介绍下
点击查看代码
[ken@Dell-Desktop ~]$ declare m=3+3
[ken@Dell-Desktop ~]$ echo $m
3+3
[ken@Dell-Desktop ~]$ declare -i n=3+3
[ken@Dell-Desktop ~]$ echo $n
6
首先声明了变量i值为3+3,之后打印它发现值“确实”为3+3。接着我们用declare -i的方式,声明变量j,-i的意思是声明一个整数类型的变量,所以我们发现j的值变成了6。
关于declare的详细用法,我们会在Bash内置命令一节再详细介绍,感兴趣的小伙伴可以先从看一下附在文末的Bash手册参考。
Bash(内置)变量
有一些特殊变量,Bash程序本身会去读/写,下面简单介绍几个
- BASH_VERSION 当前的BASH程序版本
- EPOCHSECONDS 当前Unix时间戳
- PATH 命令程序的查找路径。很重要的变量,它决定了Bash会去哪些目录里查找你想执行的命令
- PS1 命令行提示符
光说不练怎么行,下面我们演示下
点击查看代码
[ken@Dell-Desktop ~]$ echo $BASH_VERSION
5.1.16(1)-release
[ken@Dell-Desktop ~]$ echo $EPOCHSECONDS
1679103775
[ken@Dell-Desktop ~]$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/usr/lib/wsl/lib:/mnt/c/Windows/system32:/mnt/c/Windows:/mnt/c/Windows/System32/Wbem:/mnt/c/Windows/System32/WindowsPowerShell/v1.0/:/mnt/c/Windows/System32/OpenSSH/:/mnt/c/Program Files/dotnet/:/mnt/c/Users/endea/AppData/Local/Microsoft/WindowsApps:/mnt/d/Programs/Microsoft VS Code/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl
[ken@Dell-Desktop ~]$ echo $PS1
[\u@\h \W]\$
[ken@Dell-Desktop ~]$

我们可以看到,ls程序位于目录/usr/sbin下,而PATH变量中包含了/usr/sbin这一个目录,所以我们输入ls时,Bash才能够找到并执行ls。假如我们把PATH里面的目录都去掉呢?这时候Bash就找不到我们的ls程序,就会报错。
[ken@Dell-Desktop ~]$ ls
Devel Documents
[ken@Dell-Desktop ~]$ PATH=""
[ken@Dell-Desktop ~]$ ls
-bash: ls: No such file or directory
最后稍微讲一下这个PS1(并不是索尼的Play Station游戏机,哈)。它是Bash在终端上显示的命令行提示信息,比如我之前的命令行提示总是为[ken@Dell-Desktop ~]$,假如我们现在修改下PS1的值,就会发现命令提示符变了
[ken@Dell-Desktop ~]$PS1="[\u love bash \t]$"
[ken love bash 10:15:38]$
[ken love bash 10:15:39]$
[ken love bash 10:15:41]$
\u表示当前用户的用户名,\t是当前的时间,\W是当前所在的目录名,感兴趣的小伙伴可以去Bash手册(man bash)中搜索PROMPTING这个词,可以获得更详细的信息。
后面我们会使用更简洁的提示符,只保留用户名和当前目录名
[ken love bash 10:15:41]$PS1="[\u \W]$"
[ken ~]$
变量的读取
关于变量的读取,其实上面的示例中已经有所展示——echo $Var。这里echo是Bash的一个内置命令,作用是打印到屏幕上(准确来说应该是标准输出stdout),而$Var就是读取变量Var的值。还可以写为${Var},有时候这样写是可选的,有时候却是必要的,比如说我们有个奇葩的需求,把变量var1和var2的值串联起来并且中间加个下划线_
[ken ~]$var1=hello
[ken ~]$var2=world
[ken ~]$echo $var1_$var2
world
这里var1必须使用大括号包起来,否则Bash会认为变量名称为var1_而不是var1,因为变量var1不存在(不存在的变量值为空),最后只打印出来var2的值。正确的姿势为
[ken ~]$echo ${var1}_${var2}
hello_world
还有一种更常见的情形必须使用{}来获取变量值,就是接下来介绍的数组。
数组
介绍完变量的基本内容,就不得不继续介绍下另一种变量——数组。Bash支持两种数组,一种是所谓索引数组(indexed array),通过下标0,1,2...(跟大多数编程语言一样,下标从零开始,想起了孙燕姿的歌《爱从零开始》~)访问其中的元素,第二种叫做关联数组(associative array),通过Key来访问Value,也就是所谓的键值对(或Map)。我们首先看一下数组的声明和定义(Bash手册中查找关键字Arrays)
索引数组
[ken bash]$ arr1=(c d e f g)
[ken bash]$ echo ${arr1[0]}
c
[ken bash]$ echo ${arr1[2]}
e
[ken bash]$ arr1=([4]=g [3]=f [2]=e [1]=d [0]=c) # 用指定下标的方式赋值
[ken bash]$ echo ${arr1[0]}
c
关联数组
[ken bash]$ declare -A arr2=(k1 v1 k2 v2)
[ken bash]$ echo ${arr2[k1]}
v1
[ken bash]$ declare -A arr2=([k1]=v1 [k2]=v2) # 用指定下标的方式赋值
[ken bash]$ echo ${arr2[k2]}
v2
对于关联数组,我们再次用到了Bash内置命令declare。-A意思是声明的是关联数组,如果不加该选项,bash就无法把后面的赋值正确解释为键值对。
打印数组内容
[ken bash]$echo ${arr1[@]}
c d e f g
[ken bash]$echo ${arr1[*]}
c d e f g
[ken bash]$echo ${arr2[@]}
v1 v2
[ken bash]$echo ${arr2[*]}
v1 v2
这里我们使用了Bash中经典的@和*符号,它们的意思都是数组的全部值(两者的区别我们暂不深究,后面讨论“分词”的时候再说)。如果我们想要获取索引数组的全部下标或者关联数组的全部Key,只需要加一个!
[ken bash]$echo ${!arr1[@]}
0 1 2 3 4
[ken bash]$echo ${!arr1[*]}
0 1 2 3 4
[ken bash]$echo ${!arr2[@]}
k1 k2
[ken bash]$echo ${!arr2[*]}
k1 k2
数组其实还有很多其他实用的操作,比如获取数组长度或元素个数,还有数组元素遍历等,不过让我们把这个留到“参数(变量)扩展”这一节再进行具体演示。
删除变量
最后让我们简单看一下如何删除一个变量。Bash专门提供了一个内置命令unset,用于删除一个变量
unset var
[ken bash]$echo $var
有时候我们为了避免在脚本中打错变量名,可能会希望Bash把访问不存在的变量当成错误。Bash提供了一个命令专门用于这个set -o nounset(或者set -u)
[ken ~]$echo $var
[ken ~]$set -o nounset
[ken ~]$echo $var
bash: var: 未绑定的变量
Bash注释和shabang
关于Bash脚本中的注释,其实一句话就可以描述清楚:一行中#号后面的内容都是注释,不会被Bash执行。咦,刚才不是刚提到#号是取长度吗?Yes,所以这里的例外是,大括号中的#不会当作一行注释的开始(用Bash术语讲,是#如果出现在参数展开(Parameter Expansion)的语义中,就不算是注释。关于参数展开,我们课程后面的部分会详细探讨)。
下面我们看一下,在很多Bash脚本的第一行经常出现的特殊注释——shabang(读作“傻棒”,中文翻译应该是啥?俺也不知道),看下面的示例(摘自Oracle Linux Server发行版上面的/etc/sysconfig/network-scripts/ifup-eth文件)

虽然也是注释,可shabang有几个特殊的地方
- 它必须出现在第一行
#后面必须有一个!
那么shabang究竟有什么特别的呢?下面(用Lua和Python)简单演示下
#!/bin/lua5.3
print("hello world")
把上面的文件保存为shabang.sh,然后给它增加可执行权限(不熟悉linux权限的同学,可从网上搜索下Linux文件读(r)写(w)和执行(x)权限的相关资料)
[ken bash]$chmod u+x shabang.sh
[ken bash]$./shabang.sh
hello world
最后打印出来了我们熟悉的hello world。可是,print并不是bash脚本语言里的内置命令啊,Bash应该用echo。你说得没错,这也正是shabang的特别之处。当脚本文件具有可执行权限并且第一行是个shabang,那么运行的时候Linux会根据shabang后面的路径,去寻找相应的脚本解释程序,并用它来执行脚本,而不是用Bash。如果这么描述还稍微有点抽象的话,让我们再进一步演示下
[ken bash]$ls /bin/lua5.3
/bin/lua5.3
[ken bash]$/bin/lua5.3 shabang.sh
hello world
我的linux上Lua解释器安装在/usr/bin/lua5.3,然后我用它直接去运行刚才的shabang.sh,也得到了同样的hello world输出结果。这下你明白了吗?
PS:脚本解释器是什么?
脚本本质上是个文本文件,与txt并无差别。如果想要运行脚本,我们需要一个脚本解释器程序去解析并执行脚本中里的语句。对于Bash脚本,/bin/bash就是我们的Bash脚本解释器,对于Python脚本,/usr/bin/python就是我们的脚本解释器。
Bash手册中的declare命令
declare [-aAfFgiIlnrtux] [-p] [name[=value] ...]
typeset [-aAfFgiIlnrtux] [-p] [name[=value] ...]
Declare variables and/or give them attributes. If no names are given then display the values of vari‐
ables. The -p option will display the attributes and values of each name. When -p is used with name
arguments, additional options, other than -f and -F, are ignored. When -p is supplied without name ar‐
guments, it will display the attributes and values of all variables having the attributes specified by
the additional options. If no other options are supplied with -p, declare will display the attributes
and values of all shell variables. The -f option will restrict the display to shell functions. The -F
option inhibits the display of function definitions; only the function name and attributes are printed.
If the extdebug shell option is enabled using shopt, the source file name and line number where each
name is defined are displayed as well. The -F option implies -f. The -g option forces variables to be
created or modified at the global scope, even when declare is executed in a shell function. It is ig‐
nored in all other cases. The -I option causes local variables to inherit the attributes (except the
nameref attribute) and value of any existing variable with the same name at a surrounding scope. If
there is no existing variable, the local variable is initially unset. The following options can be
used to restrict output to variables with the specified attribute or to give variables attributes:
-a Each name is an indexed array variable (see Arrays above).
-A Each name is an associative array variable (see Arrays above).
-f Use function names only.
-i The variable is treated as an integer; arithmetic evaluation (see ARITHMETIC EVALUATION above)
is performed when the variable is assigned a value.
-l When the variable is assigned a value, all upper-case characters are converted to lower-case.
The upper-case attribute is disabled.
-n Give each name the nameref attribute, making it a name reference to another variable. That
other variable is defined by the value of name. All references, assignments, and attribute mod‐
ifications to name, except those using or changing the -n attribute itself, are performed on the
variable referenced by name's value. The nameref attribute cannot be applied to array vari‐
ables.
-r Make names readonly. These names cannot then be assigned values by subsequent assignment state‐
ments or unset.
-t Give each name the trace attribute. Traced functions inherit the DEBUG and RETURN traps from
the calling shell. The trace attribute has no special meaning for variables.
-u When the variable is assigned a value, all lower-case characters are converted to upper-case.
The lower-case attribute is disabled.
-x Mark names for export to subsequent commands via the environment.
Using `+' instead of `-' turns off the attribute instead, with the exceptions that +a and +A may not be
used to destroy array variables and +r will not remove the readonly attribute. When used in a func‐
tion, declare and typeset make each name local, as with the local command, unless the -g option is sup‐
plied. If a variable name is followed by =value, the value of the variable is set to value. When us‐
ing -a or -A and the compound assignment syntax to create array variables, additional attributes do not
take effect until subsequent assignments. The return value is 0 unless an invalid option is encoun‐
tered, an attempt is made to define a function using ``-f foo=bar'', an attempt is made to assign a
value to a readonly variable, an attempt is made to assign a value to an array variable without using
the compound assignment syntax (see Arrays above), one of the names is not a valid shell variable name,
an attempt is made to turn off readonly status for a readonly variable, an attempt is made to turn off
array status for an array variable, or an attempt is made to display a non-existent function with -f.

浙公网安备 33010602011771号