本篇开始,介绍 shell 脚本编程,更确切的说是 bash 脚本编程 (版本:4.2.46(1)-release)。我们从变量开始。
和所有的编程语言一样,bash 也提供变量,变量是一些用来指代数据并支持数据操作的名称。
类型
环境变量
概念
当我们通过 ssh 等工具登录系统时,便获得一个 shell(一个 bash 进程),bash 在启动过程中会加载一系列的配置文件,这些配置文件的作用就是为用户准备好 bash 环境,大部分 环境变量
都是在这些文件中被设置的。
登录 shell
(login shell) 是指需要通过输入用户名、密码登录之后获得的 shell(或者通过选项 “--login
“ 生成的 shell)。登录 shell 的进程名为 -bash
,非登录 shell(比如在桌面环境下通过打开一个 “终端
“ 窗口程序而获得的 shell) 的进程名为 bash
。
|
|
交互式 shell
(interactive shell) 是指 shell 与用户进行交互,shell 需要等待用户的输入 (键入一条命令后并按下回车键),用户需要等待命令的执行和输出。当把一到多个命令写入一个文件,并通过执行这个文件来执行这些命令时,bash 也会为这些命令初始化一个 shell 环境,这样的 shell 称为非交互式 shell
。
环境变量 - 中存储了当前 shell 的选项标志,其中如果包含字符 i
则表示此 shell 是交互式 shell:
|
|
通常,一个登录 shell
(包括交互式登录 shell 和使用 “–login” 选项的非交互 shell) 首先读取并执行文件 /etc/profile
(此文件会在结尾处判断并执行 /etc/profile.d/
中所有以. sh 结尾的文件);然后按顺序搜索用户家目录下的~/.bash_profile
、~/.bash_login
和~/.profile
,并执行找到的第一个可读文件 (在 centos7 系统中是文件~/.bash_profile
,此文件会进一步判断并执行文件~/.bashrc,然后再进一步判断并执行文件 /etc/bashrc
)。当一个登录 shell 登出时 (exit),会执行文件~/.bash_logout
和 /etc/bash.bash_logout
(如果文件存在的话)。
交互式非登录 shell
启动时,bash 会读取并执行文件~/.bashrc
。
非交互式 shell
启动时 (如脚本中),会继承派生出此 shell 的父 shell 的环境变量并执行环境变量 BASH_ENV
的值中所指代的文件。
作用
环境变量的作用主要是影响 shell 的行为,在整个 bash 进程的生命周期中,会多次使用到环境变量。每个由当前 bash 进程派生出的子进程 (包括子 shell),都会继承当前 bash 的环境变量 (除非子进程对继承的环境变量进行了重新赋值,否则它们的值将和父进程相同)。下面列出部分常用环境变量及其作用:
PATH
其值是一个以冒号分隔的目录列表,定义了 shell 命令的搜索路径。
|
|
PS1
首要命令提示符。
|
|
PS2
连续性 交互式提示符。当输入的命令分为好几行时,新行前出现的字符串即为 PS2 变量的值。
|
|
PS3
shell 脚本中 select 关键字提示符
PS4
shell 调试模式下的提示符
HOME
当前用户的家目录
PWD
当前工作目录
OLDPWD
前一个工作目录
|
|
RANDOM
每次引用此变量,都会生成一个 0 到 32767 之间的随机数字
BASH_VERSION
其值为当前 bash 版本号
|
|
IFS
域分隔符,用来分隔单词。默认值为 空格键 TAB 键 回车键产生的字符
|
|
本系列中在涉及到具体环境变量的时候还有更详细的解释和用法描述。
自定义变量
普通变量
bash 除了在初始化时自动设置的变量外,用户还可以根据需要手动设置变量。普通变量赋值语句写法:
|
|
其中 name
为变量名,变量名必须以英文字母 ([a-zA-Z]
) 或下划线 (_
) 开头,其余字符可以是英文字母、下划线或数字 ([0-9]
)。变量名是大小写敏感的。在给变量赋值时,等号两边不能有任何空白字符。等号后的值(value
) 可以省略,如果省略,则变量的值为空字符串(null
)。
数组变量
bash 提供一维的索引和关联数组变量,索引数组
是以数字为下标的数组,关联数组
是以字符串为下标的数组 (类似其他语言中的 map 或 dict)。
数组赋值语句写法:
|
|
其中每一个 value
都是类似以 [subscript]=string
的格式,索引数组赋值时可以省略 [subscript]=
,关联数组不能省略。
|
|
所谓内置命令
,是指由 bash 自身实现的命令,它们的执行就相当于执行 bash 的一个函数,并不需要派生出新的子进程。
外部命令
是指那些不是由 bash 自身实现的命令 (如环境变量 PATH 目录内的命令)。原则上所有命令都应该外部实现 (避免臃肿及和其他系统耦合度过高),但是,外部命令的执行,意味着创建子进程,而子进程对环境变量等的更改是无法影响父进程的。bash 想要更改自身的一些状态时,就得靠内置命令
来实现。例如,改变工作目录命令 cd
,就是一个典型的例子 (cd 命令会更改当前所处目录,并更新环境变量 PWD
和 OLDPWD
,如果此功能由外部实现,更改目录的目的就无法实现了)。
特殊变量
bash 中还支持一些表示特殊意义的变量,这些变量不能使用上述语句进行赋值。
|
|
声明/定义及赋值
通常 bash 的变量是不需要提前声明的,可以直接进行赋值。变量的值均被视为字符串
(在一些情况下也可以视为数字)。当对变量有特殊需要时,也可以先声明变量 (如前面关联数组的声明)。
bash 提供了几个和变量声明及赋值相关的内置命令,这些命令即可以和赋值语句写在同一行 (表示声明及赋值),也可以只跟变量名 (表示声明)。
[]
表示可选:
|
|
这是两个起同样作用的命令,用来声明变量;
|
|
以上选项可以使用命令 declare +OPTION name
撤销变量 name 的属性 (只读变量除外)
内置命令 export
作用于赋值语句时,和 declare -x
类似表示导出变量为环境变量 (临时有效,重启系统后这些环境变量消失;如需设置永久环境变量,需要将 export
语句写入前面所述的 bash 配置文件中)。
内置命令 readonly
作用于赋值语句时,和 declare -r
类似表示标记变量为只读:
|
|
内置命令 read 作用是从标准输入读入一行数据赋值给变量
|
|
当有多个变量名时,环境变量 IFS
用来将输入分隔成单词。当单词数大于变量数时,剩余的单词和分隔符会被赋值给最后一个变量。当单词数小于变量数时,剩余的变量被赋空值。
|
|
选项 -a
表示将读入的数据赋值给索引数组
|
|
选项 -p string
表示在等待输入时显示提示符字符串 string
|
|
选项 -d
表示指定分隔符 (原分隔符为 \n)
|
|
内置命令 readarray
和 mapfile
表示从标准输入中读入数据并赋值给索引数组,每行赋给一个数组元素:
|
|
变量有一个状态 set/unset
:只要变量被赋过值,就称变量是 set
的状态 (即使变量的值为空 null
);否则,则称变量是 unset
的状态 (即使变量被 declare
或其他内置命令声明过)。
可以使用内置命令 unset
对变量进行撤销 (特殊变量和只读变量除外)。
|
|
对变量进行赋值时,可以使用操作符 +=
表示对值的追加:
|
|
|
|
变量取值/扩展
bash 使用符号 $
对变量进行取值,并使用大括号 {}
对变量名的起始和结束进行界定,在不引起混淆的情况下,大括号可以省略。
在命令的执行过程中,变量被其值所替换,在替换的过程中能够对应于各种变换。bash 称对变量进行取值的过程为变量替换
或变量扩展
。
直接取值
|
|
间接引用
在对变量进行取值时,变量名前的符号 !
表示对变量的间接引用:
|
|
取长度
在变量名前使用符号 #
表示取长度,普通变量表示变量值的字符数,数组变量表示数组参数的个数
|
|
判断状态
对于变量 parameter
的状态 (set
或 unset
) 和值是否为空(null
),bash 提供四种方式扩展:
这里的 word
会经过 波浪号扩展 (~ 替换为用户家目录)、变量扩展、命令替换、数学扩展 (以后的文章中会对后两种作详细描述)
${parameter:-word}
如果变量状态为 unset 或值为空,返回 word
的结果值,否则返回变量的值。
|
|
${parameter:=word}
如果变量状态为 unset 或值为空,word
的结果会赋值给变量,然后返回变量值。特殊变量 ($n $$$ $@ $#
等) 不能用这种方式进行赋值。
|
|
${parameter:?word}
如果变量状态为 unset 或值为空,word
的结果值会被输出到标准错误,如果 shell 是非交互的 (如脚本中) 则退出(exit);否则展开为变量的值。
|
|
${parameter:+word}
如果变量状态为 unset 或值为空,什么也不返回,否则返回 word
的结果值。
|
|
以上四种判断变量的方式中,如果省略了冒号 :
,则表示只判断 unset
的情况。
|
|
取子串
bash 支持使用 ${parameter:offset:length}
的格式对变量取部分值,其中 offset
和 length
都是数学表达式,分别代表位置和长度。
parameter
为普通变量时,表示从第 offset 个字符 (首字符是第 0 个) 开始,取 length 个字符,如果 :length
省略,表示从第 offset 个字符开始,取到变量值的结尾。
|
|
如果 offset
的结果小于 0,则表示从后往前取子串。
|
|
如果 length
的结果小于 0,则它表示距离最后一个字符往前 length
个字符的位置,和 offset
位置一起作用,变量替换的结果就是两个位置
之间的值。
|
|
parameter
是 @
或使用 @
或 *
作为下标的数组时,则 offset
和 length
计算的是元素个数而不是字符数,并且 length 的结果不能为负。
|
|
删除
bash 提供两种方式分别删除变量值的前缀或后缀:
${parameter#word}
和 ${parameter##word}
表示删除前缀。word
扩展后的结果会作为模式匹配 (通配符匹配,见这里) 变量的值,一个 #表示删除最短匹配前缀,##
表示删除最长匹配前缀:
|
|
${parameter%word}
和 ${parameter%%word}
表示删除后缀。
|
|
如果 parameter
是 @
或 *
或以 @
或 *
作为下标的数组变量,删除操作将作用于每个位置变量或数组的每个参数
|
|
替换
${parameter/pattern/string}
的形式表示用 pattern
对变量 parameter
进行匹配 (通配符匹配),并使用 string
的结果值替换匹配(最长匹配) 的部分。
|
|
如果 pattern
以字符 /
开头,则所有被匹配的结果都被替换
|
|
如果 pattern
以字符 #
开头,匹配的前缀被替换
|
|
如果 pattern
以字符 %
开头,匹配的后缀被替换
|
|
使用 @
和 *
的情况和前述一样,替换将作用于每个参数
|
|
大小写转换
${parameter^pattern}
、${parameter^^pattern}
、${parameter,pattern}
、${parameter,,pattern}
大小写字母转换,如果 parameter
值的首字母匹配模式 pattern
(通配符匹配,只能是一个字符,可以是 ? * [...]
或一个英文字母,多个字符不起作用。pattern
省略则表示使用 ?
),则 ^
将首字母转换成大写,^^
将所有匹配字母转换成大写;, 将首字母转换成小写,,,
将所有匹配字母转换成小写。
|
|
使用 @
和 *
的情况和前述相同,大小写转换将作用于每个参数
由于 bash 变量赋值的随意性,自定义变量起名时不要和原有变量 (尤其是环境变量) 相冲突,撤销时也要注意不要将环境变量撤销掉(虽然撤销自定义变量并不是必须的)。
|
|
作用域
bash 变量的作用域分为多种:
- 写入到 bash 配置文件并用
export
导出的环境变量。影响每个启动时加载相应配置文件的 bash 进程及其子进程。 - 当前 shell 中自定义并通过内置命令
export
导出的环境变量。影响当前 bash 进程及其子进程。 - 当前 shell 中自定义但未导出的变量。影响当前 bash 进程及其子进程 (不包括需要重新初始化 shell 的进程)。
- 当前 shell 中某个函数中通过内置命令
local
自定义的局部变量。只影响此函数及嵌套调用的函数和命令。 - 当前 shell 中某个命令中的临时变量。只影响此命令。
bash变量作用域涉及到子shell和函数的用法,这里暂时不作举例说明,后续文章中会详细叙述。