【Shell】LV2-03-Shell脚本编程
摘要:
主要介绍Shell
基本语法。
一、第一个shell
脚本
1.什么是Shell
?
Shell
是一个用 C 语言
编写的程序,它是用户使用 Linux
的桥梁。既是一种命令语言,又是一种程序设计语言。Shell
是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务。
2.什么是Shell
脚本?
shell
脚本语言是解释型语言,本质是**shell
命令的有序集合,Shell
脚本文件的后缀名称是.sh
**。
3.Hello World!
1 | !/bin/bash |
这段代码是一个最简单的Shell
脚本程序,就像C语言
学习室的第一个程序一样。
#!
是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种Shell
。echo
命令用于向命令行窗口输出文本。
4.如何运行Shell
脚本?
- 进入
Shell
脚本所在目录
1 | cd <file_path> |
- 赋予
shell
文件执行权限
1 | chmod permissions file_name.sh |
permissions
为要赋予文件的权限(常用777或者740,保证文件具有可执行权限即可)。
- 执行
Shell
脚本文件
1 | ./file_name.sh |
二、Shell
变量
1.变量的命名
shell
允许用户建立变量存储数据,但不支持数据类型(整型、字符、浮点型),将任何赋给变量的值都解释为一串字符。变量定义格式如下(支持三种形式):
1 | Variable=value # 第一种 |
【注意】
(1)等号两边不允许有空格。
(2)命名只能使用英文字母,数字和下划线,首个字符不能以数字开头,且中间不能有空格。
(3)不能使用标点符号,而且不能使用bash
里的关键字(可用help命令查看保留关键字)。
(4)第二种与第三种的区别:
以单引号' '
包围变量的值时,单引号里面是什么就输出什么,即使内容中有变量和命令(命令需要反引起来)也会把它们原样输出。这种方式比较适合定义显示纯字符串的情况,即不希望解析变量、命令等的情况。
以双引号" "
包围变量的值时,输出时会先解析里面的变量和命令,而不是把双引号中的变量名和命令原样输出。这种方式比较适合字符串中附带有变量和命令并且想将其解析后再输出的变量定义。
2.变量的赋值
在 Bash shell
中,每一个变量的值都是字符串,无论给变量赋值时有没有使用引号,值都会以字符串的形式存储。这就意味着,Bash shell
在默认情况下不会区分变量类型,即使将整数和小数赋值给变量,它们也会被视为字符串。
- 直接赋值
1 | Variable=value |
- 用语句赋值
1 | Variable=`command` |
用语句进行赋值时,语句一定要写在 `` 符号里边,该符号为英文状态下 Esc 按键下边的哪个按键。
或者也可以使用下边的 $() 的方式,这种方式看起来要更直观一些。
使用一个定义过的变量,只要在变量名前面加美元符号($
)即可。
1 | {Variable} |
【注意】
(1)变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界。推荐给所有变量加上花括号。
(2)定义变量和给变量赋值的时候都不需要加$
,使用的时候才需要加上。
4.变量的类型
- 局部变量
在脚本或命令中定义,仅在当前shell
实例中有效,其他shell启动的程序不能访问局部变量。
- 环境变量
所有的程序,包括shell
启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell
脚本也可以定义环境变量。
shell
变量
shell
变量是由shell
程序设置的特殊变量。shell
变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell
的正常运行。
5.Bourne Shell
中的常用的四种变量
5.1用户自定义变量
就是用户自己定义的变量,在shell
编程中,通常使用全大写,例如
1 | COUNT=1 |
5.2位置变量
在 Linux
的命令行中,当一条命令或脚本执行时,后面可以跟多个参数,可以使用位置参数变量来表示这些参数。
$n | n 为数字,$0 代表命令本身,$1-$9 代表第 1-9 个参数,10 以上的参数需要用大括号{}包含, 例如${10},${15} |
$* | 代表命令行中所有的参数,把所有的参数看成一个整体 |
$@ | 代表命令行中所有的参数,不过 $@ 把每个参数区别对待 |
$# | 代表命令行中所有参数的个数 |
【注意】
(1)$n
中,n
大于等于10
时,一定要加上{}
。
(2)$*
与$@
:
当 $*
和 $@
不被双引号" "
包含时,它们之间没有任何区别,都是将接收到的每个参数看做一份数据,彼此之间以空格来分隔,两者都以"$1" "$2" … "$n"
的形式输出所有参数。
当 $*
和 $@
被双引号" "
包含时,"$*"
会将所有的参数从整体上看做一份数据,而不是把每个参数都看做一份数据,以"$1 $2 … $n"
的形式输出所有参数;"$@"
仍然将每个参数都看作一份数据,彼此之间是独立的,以"$1" "$2" … "$n"
的形式输出所有参数。
变量 | 输出形式 | |
被 " " 包含 | 不被 " " 包含 | |
$* | "$1 $2 … $n" | "$1" "$2" … "$n" |
$@ | "$1" "$2" … "$n" | "$1" "$2" … "$n" |
例如:某命令传递了 5
个参数,那么对于"$*"
来说,这 5
个参数会合并到一起形成1
个数据,这一个数据包含了5
个参数,它们之间是无法分割的;而对于"$@"
来说,这 5
个参数是相互独立的,它们是 5
个数据。
5.3预定义变量
预定义变量是在Shell
一开始时就定义的变量,这一点和默认环境变量有些类似。不同的是,预定义变量不能重新定义,用户只能根据 Shell
的定义来使用这些变量。严格来说,位置参数变量也是预定义变量的一种,只是位置参数变量的作用比较统一,所以一般把位置参数变量单独划分为一类变量。
$? | 最后一次执行的命令的返回状态。如果这个变量的值为 0,则证明上一条命令正确执行;如果这个变量的值为非0 (具体是哪个数由命令自己来决定),则证明上一条命令执行错误。 |
$$ | 代表当前进程的进程号(PID)。 |
$! | 代表后台运行的最后一个进程的进程号(PID)。 |
5.4环境变量
环境变量也称为全局变量,可以在创建它们的Shell
及其派生出来的任意子进程Shell
中使用,环境变量又可以分为自定义环境变量和**bash
内置的环境变量**。
环境变量可以在命令行中设置和创建,用户退出命令行时这些变量值就会丢失,那如何永久保存环境变量呢?可以在用户家目录下的.bash_profile(或者是.profile)
或.bashrc(非用户登录模式特有,如:SSH)
文件中,这两个文件设置后是针对特定用户的,也可以在/etc/profile
文件中定义环境变量,该文件设置后将会影响所有的用户,就是说在这三个文件中进行定义的环境变量在用户登录后就会进行初始化。
例如:下图为~/.profile
文件内容。
- 常用环境变量
HOME | /etc/passwd文件中列出的用户主目录 |
PATH | 系统默认的可执行文件搜索路径 |
HISTSIZE | 保留历史命令的数目上限 |
OSTYPE | 系统类型 |
PWD | 当前工作目录 |
TERM | 终端类型,常用的有vt100,ansi,vt200,xterm等 |
PS1, PS2 | 默认提示符($)及换行提示符(>) |
IFS | Internal Field Separator, 默认为空格,tab及换行符 |
6.字符串
在Shell
中,字符串可以用单引号,也可以用双引号,也可以不用引号。
- 单引号
单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的。
单引号字串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但可成对出现,作为字符串拼接使用。
1 | str='string' |
- 双引号
双引号里可以有变量,也可以出现转义字符。
6.1拼接字符串
- 使用单引号拼接
1 | !/bin/bash |
输出结果为:
1 | Hello,World ! |
- 使用双引号拼接
1 | !/bin/bash |
输出结果为:
1 | Hello, World ! |
6.2获取字符串长度
1 | {#<string>} |
string
为要获取长度的目标字符串变量。
例如:
1 | !/bin/bash |
输出结果为:
1 | str1's length=5 |
6.3提取字符串
1 | {<string>:n:len} |
string
为要获取长度的目标字符串变量。n
为起始字符位置,len
为截取字符串的长度,提取出的字符串为n
位置处字符极其之后的len-1
个字符。
【注意】字符串中首个字符的索引为0
。
例如:
1 | !/bin/bash |
输出结果为:
1 | llo |
6.4查找字符串中字符
可以通过expr
实现字符串中特定字符的查找,这种查找方式会查找字符串中首次出现该字符的位置。
1 | Variable=`expr index "${<string>}" <char>` |
string
为要查找的目标字符串变量。char
为要查找的字符。
【注意】
(1)在查找过程中,字符串中首个字符的索引为`1``。
(2)以上脚本中 ` 是反引号,而不是单引号 **’**。
(3)char
可以是多个字符,例如对于ac
,a
,c
两个字符哪个先出现,就会停止查找,返回首先出现的字符在目标字符串中的首次出现的位置。
例如:
1 | !/bin/bash |
输出结果为:
1 | 7 |
7.数组
bash
支持一维数组(不支持多维数组),并且没有限定数组的大小。数组元素的下标由0
开始。获取数组中的元素要用下标,下标可以是整数或算术表达式,其值应大于或等于0。
7.1定义一个数组
在 Shell
中,用括号来表示数组,数组元素用<space>
符号分割开。
- 直接定义
1 | 数组名=(值1 值2 ... 值n) |
例如:
1 | version=(v1 v2 v3) |
或者
1 | version=(v1 |
- 单独定义各个分量
1 | 数组名[0]=值0 |
【注意】可以不使用连续的下标,而且下标的范围没有限制。
7.2引用数组元素
- 获取数组中某个元素
1 | {arrayName[index]} |
【注意】index
从0
开始
- 获取数组中所有元素
1 | {arrayName[@]} |
7.3获取数组长度
1 |
|
例如:
1 | !/bin/bash |
运行结果如下:
1 | a's length=3 |
三、Shell
替换
如果表达式中包含特殊字符,Shell
将会进行替换。
1.变量替换
变量替换可以根据变量的状态(是否为空、是否定义等)来改变它的值。
${Variable} | 变量本来的值 |
${Variable:-word} | 如果变量 Variable 为空或已被删除(unset),那么返回 word,但不改变 Variable 的值。 |
${Variable:=word} | 如果变量 Variable 为空或已被删除(unset),那么返回 word,并将 Variable 的值设置为 word。 |
${Variable:?message} | 如果变量 Variable 为空或已被删除(unset),那么将消息 message 送到标准错误输出,可以用来检测变量 Variable 是否可以被正常赋值。 若此替换出现在Shell脚本中,那么脚本将停止运行。 |
${Variable:+word} | 如果变量 Variable 被定义,那么返回 word,但不改变 Variable 的值。 |
2.命令替换
命令替换是指Shell
先执行命令,将输出结果暂时保存,在适当的地方输出。
1 | `command` # 注意是反引号 ` ,按键位于 Esc下方 |
例如:
1 | !/bin/bash |
运行结果为:
1 | Date is 2022年 02月 09日 星期三 18:27:51 CST |
四、Shell
运算符
Shell
支持很多运算符,包括算数运算符、关系运算符、布尔运算符、字符串运算符和文件测试运算符。原生bash
不支持简单的数学运算,但是可以通过其他命令来实现,例如 awk
和 expr
,expr
最常用,它是一款表达式计算工具,使用它能完成表达式的求值操作。
1.算术运算符
运算符 | 说明 | 实例(设变量a=6, b=3) |
+ | 加法 | `expr $a + $b` 结果为 9 |
- | 减法 | `expr $a - $b` 结果为 3 |
* | 乘法,注意要加 \ | `expr $a \* $b` 结果为 18 |
/ | 除法 | `expr $a / $b` 结果为 2 |
% | 取余 | `expr $b % $a` 结果为 0 |
= | 赋值 | a=$b 把变量 b 的值赋给 a |
== | 相等,比较两个数字,相同则返回 true | [ $a == $b ] 返回 false |
!= | 不相等,比较两个数字,不相同则返回 true | [ $a != $b ] 返回 true |
【注意】
(1)表达式和运算符之间要有空格,例如 $a+$b
是不对的,必须写成 $a + $b
。
(2)使用expr
时,完整的表达式要被 **` ** 包含。
(3)赋值操作时,=
两端不能有空格。
(4)条件表达式要放在方括号之间,并且要有空格,例如: [$a==$b]
是错误的,必须写成 **[ $a == $b ]
**。
(5)乘号(*
)前边必须加反斜杠(\
)才能实现乘法运算。
2.关系运算符
关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
运算符 | 说明 | 实例(设变量a=6, b=3) | |
-eq | 相等 | == | [ $a -eq $b ] 返回 false |
-ne | 不相等 | != | [ $a -ne $b ] 返回 true |
-gt | 大于 | > | [ $a -gt $b ] 返回 true |
-lt | 小于 | < | [ $a -lt $b ] 返回 false |
-ge | 大于等于 | >= | [ $a -ge $b ] 返回 true |
-le | 小于等于 | <= | [ $a -le $b ] 返回 false |
3.布尔运算符
运算符 | 说明 | 实例(设变量a=6, b=3) | |
! | 非 | ! | [ ! false ] 返回 true |
-o | 或 | | | [ $a -lt 7 -o $b -gt 10 ] 返回 true |
-a | 与 | & | [ $a -lt 7 -o $b -gt 10 ] 返回 false |
4.逻辑运算符
运算符 | 说明 | 实例(设变量a=6, b=3) |
&& | 逻辑的 and | [[ $a -lt 10 && $b -gt 10 ]] 返回 false |
|| | 逻辑的 or | [[ $a -lt 10 || $b -gt 10 ]] 返回 true |
运算符 | 说明 | 实例(设变量a="abc", b="abd") |
= | 检测两个字符串是否相等,相等返回 true | [ $a = $b ] 返回 false |
!= | 两个字符串是否不相等,不相等返回 true | [ $a != $b ] 返回 true |
-z | 字符串长度是否为0,为0返回 true | [ -z $a ] 返回 false |
-n | 字符串长度是否不为 0,不为 0 返回 true | [ -n $a ] 返回 true |
$ | 字符串是否为空,不为空返回 true | [ $a ] 返回 true |
6.文件测试运算符
文件测试运算符用于检测 Unix
文件的各种属性
运算符 | 说明 |
-b file | 检测文件是否是块设备文件,如果是,则返回 true。 |
-c file | 检测文件是否是字符设备文件,如果是,则返回 true。 |
-d file | 检测文件是否是目录,如果是,则返回 true。 |
-f file | 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。 |
-g file | 检测文件是否设置了 SGID 位,如果是,则返回 true。 |
-k file | 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。 |
-p file | 检测文件是否是有名管道,如果是,则返回 true。 |
-u file | 检测文件是否设置了 SUID 位,如果是,则返回 true。 |
-r file | 检测文件是否可读,如果是,则返回 true。 |
-w file | 检测文件是否可写,如果是,则返回 true。 |
-x file | 检测文件是否可执行,如果是,则返回 true。 |
-s file | 检测文件是否为空(文件大小是否大于0),不为空返回 true。 |
-e file | 检测文件(包括目录)是否存在,如果是,则返回 true。 |
-L file | 检测测文件是否为符号链接,如果是,则返回 true。 |
file1 -nt file2 | 测试文件 file1 是否比文件 file2 更新。 |
file1 -ot file2 | 测试文件 file1 是否比文件 file2 更旧。 |
五、Shell
语句
shell
脚本程序由零或多条shell
语句构成。 shell
语句包括三类:说明性语句、功能性语句和结构性语句。
1.说明性语句
其实就是注释,以#
号开始到该行结束,不被解释执行。sh
里没有多行注释,只能每一行加一个#号。
1 | -------------------------------------------- |
2.功能性语句
2.1只读变量readonly
1 | readonly Variable # Variable为变量名称 |
使用 readonly
命令可以将变量定义为只读变量,只读变量的值不能被改变。
2.2删除变量unset
1 | unset Variable # Variable为变量名称 |
2.3read
read
用来从标准输入中读取数据并赋值给变量。如果没有进行重定向,默认就是从键盘读取用户输入的数据;如果进行了重定向,那么可以从文件中读取数据。
1 | read [option] [Variables] |
[option] | -a array | 把读取的数据赋值给数组 array,从下标 0 开始 |
-d delimiter | 用字符串 delimiter 指定读取结束的位置,而不是一个换行符(默认结束符号为换行符,读取到的数据不包括 delimiter)。 | |
-n num | 读取 num 个字符就结束,而不是整行字符。如果没有读满 num 个字符就按下回车或遇到换行符,则也会结束读取。 | |
-N num | 严格要求读满 num 个字符才自动结束读取,即使中途按下了回车或遇到了换行符也不结束,其中换行符或回车算一个字符。 | |
-p prompt | 显示提示信息,提示内容为 prompt。默认不支持"\n"换行,要换行需要特殊处理(例如:通过 $'string \n' 就可以实现换行输入) | |
-t timeout | 设置超时时间,单位为秒。如果用户没有在指定时间内输入完成,那么 read 将会返回一个非 0 的退出状态,表示读取失败,即使已经输入了一部分。 | |
-u fd | 使用文件描述符 fd 作为输入源,而不是标准输入,类似于重定向。 | |
-e | 在输入的时候可以使用命令补全功能(Tab键)。 | |
-r | 原样读取(Raw mode),不把反斜杠字符解释为转义字符,这意味着"\"会变成文本的一部分。 | |
-s | 静默模式(Silent mode),不会在屏幕上显示输入的字符。可用于输入密码和其它确认信息的时候。 | |
[Variables] | 变量名称 |
【注意】
(1)可以使用多个参数。
(2)变量读取说明
1 | read [-ers] [-a aname] [-d delim] [-i text] [-n num] [-N num] [-p prompt] [-t timeout] [-u fd] [var_name1 var_name2 ...] |
首先说一下IFS(internal field separator
,为内部字段分隔符,是bash shell
中的环境变量,它定义了bash shell
用作字段分隔符的一系列字符,默认情况下,bash shell
会将下列字符当做字段分隔符:空格、制表符和换行符。
read
命令用于从标准输入中读取输入单行,并将读取的单行根据IFS
环境变量分裂成多个字段,并将分割后的字段分别赋值给指定的变量列表var_name
。第一个字段分配给第一个变量var_name1
,第二个字段分配给第二个变量var_name2
,依次到结束。如果指定的变量名少于字段数量,则多出的字段数量也同样分配给最后一个var_name
,如果指定的变量命令多于字段数量,则多出的变量赋值为空。如果没有指定任何var_name
,则分割后的所有字段都存储在环境变量REPLY
中。
例如
1 | !/bin/bash |
运行结果:
1 | Enter your name: |
(3)给多个变量赋值时,必须在一行内输入所有的值,不能换行,否则只能给第一个变量赋值,后续变量都会赋值失败。
2.4test
test
命令用于检查某个条件是否成立,它可以进行数值、字符和文件三个方面的测试。通常和 if
语句一起使用,并且大部分 if
语句都依赖 test
。
以下只为举例说明使用格式,还有一些运算符可以查看本章《第四节 Shell
运算符》。
- 数值测试
1 | test $[num1] <关系运算符> $[num2] |
运算符 | -eq | 等于,则为真 |
-ne | 不等于,则为真 | |
-gt | 大于,则为真 | |
-ge | 大于等于,则为真 | |
-lt | 小于,则为真 | |
-le | 小于等于,则为真 |
- 字符串测试
1 | test $[num1] <字符串运算符> $[num2] |
运算符 | = | 等于,则为真 |
!= | 不等于,则为真 | |
-z 字符串 | 字符串的长度为零,则为真 | |
-n 字符串 | 字符串的长度不为零,则为真 |
- 文件测试
1 | test <文件测试运算符> file_name |
运算符 | -e file | 如果文件存在,则为真 |
-f file | 如果文件存在且可读,则为真 |
2.5.1基本参数
echo
用于在屏幕上打印出指定的字符串,命令格式如下:
1 | echo [option] string |
-n | 禁止在最后自动换行 | |
-e | 开启转义字符功能,即遇到以下字符进行处理,而不会将其当成一般字符输出 | |
\a | 警报,响铃 | |
\b | 退格(删除键) | |
\f | 换页(FF),将当前位置移到下页开头 | |
\n | 换行 | |
\c | 不换行 | |
\r | 回车 | |
\t | 水平制表符(tab键) | |
\v | 垂直制表符 | |
\ooo | 插入ooo(八进制)所代表的ASCII字符 | |
\\ | 反斜杠 | |
--version | 显示版本信息 | |
--helo | 显示帮助 |
2.5.2实例
- 显示普通字符串
1 | !/bin/bash |
运行结果:
1 | string1 |
- 显示转义字符
1 | !/bin/bash |
运行结果:
1 | "string" |
- 显示变量
1 | !/bin/bash |
运行结果:
1 | qidaink |
- 显示换行与不换行
1 | !/bin/bash |
运行结果:
1 | qidaink |
- 显示结果定向至文件
1 | !/bin/bash |
运行结果:
在当前Shell
脚本所在目录创建一个filt.txt
文件(无该文件的话),并将要显示的内容显示在filt.txt
文件中。
- 原样输出字符串,不进行转义或取变量(用单引号)
1 | !/bin/bash |
运行结果:
1 | qidaink\nString1 |
- 显示命令执行结果
1 | !/bin/bash |
运行结果:
1 | 2022年 02月 14日 星期一 13:29:17 CST |
2.6printf
printf
由 POSIX
标准所定义,因此使用 printf
的脚本比使用 echo
移植性好。printf
使用引用文本或空格分隔的参数,外面可以在 printf
中使用格式化字符串,还可以制定字符串的宽度、左右对齐方式等。默认的 printf
不会像echo
自动添加换行符。使用格式如下:
1 | printf "format-string>" [arguments...] |
其中format-string
为格式控制字符串,arguments
为参数列表。Shell
脚本中的printf
和C语言
中的printf
用法基本一致,只是在写法上有些许区别。
printf
命令不用加括号format-string
可以没有引号,但最好加上,单引号双引号均可。- 参数多于格式控制符(
%
)时,format-string
可以重用,可以将所有参数都转换。 arguments
使用空格分隔,不用逗号。
2.6.1 转义字符
\a | 警报,响铃 |
\b | 退格(删除键) |
\c | 不显示输出结果中任何结尾的换行字符,而且任何留在参数里的字符、任何接下来的参数以及任何留在格式字符串中的字符都被忽略。 |
\f | 换页(FF),将当前位置移到下页开头 |
\n | 换行 |
\r | 回车 |
\t | 水平制表符(tab键) |
\v | 垂直制表符 |
\ooo | 插入ooo(八进制)所代表的ASCII字符 |
\\ | 反斜杠字符 |
- 格式控制字符
格式字符 | 形式 | 意义 |
i, d | %i, %d | 以十进制形式输出带符号整数(正数不输出符号) |
u | %u | 以十进制形式输出无符号整数 |
o | %o | 以八进制形式输出无符号整数(不输出前缀0) |
x, X | %x, %X | 以十六进制形式输出无符号整数(不输出前缀0x) |
f | %f | 以小数形式输出单、双精度实数 |
e, E | %e, %E | 以指数形式输出单、双精度实数 |
g, G | %g, %G | 以 %f 或 %e 中较短的输出宽度输出单、双精度实数 |
c | %c | 输出单个字符 |
s | %s | 输出字符串 |
标识 | 描述 |
- | 在给定的字段宽度内左对齐,默认是右对齐 |
+ | 强制在结果之前显示加号或减号(+ 或 -),即正数前面会显示 + 号。默认情况下,只有负数前面会显示一个 - 号。 |
space (空格) |
有符号值若为正,则在值前显示前导空格(但是不显示符号);若为负,则在值前显示-。 |
# | 与 o、x 或 X 说明符一起使用时,非零值前面会分别显示 0、0x 或 0X。 |
与 e、E 和 f 一起使用时,会强制输出包含一个小数点,即使后边没有数字时也会显示小数点。默认情况下,如果后边没有数字时候,不会显示显示小数点。 | |
与 g 或 G 一起使用时,结果与使用 e 或 E 时相同,但是尾部的零不会被移除。 | |
0 | 输出数值时指定左面不使用的空位置自动填0。 |
- 最小输出宽度(
width
)
宽度 | 描述 |
number | 数值(十进制整数),数据长度 小于number,则左补空格;否则按实际输出。 |
* | 星号,精度在 format 字符串中未指定,但是会作为附加整数值参数放置于要被格式化的参数之前。 |
- 精度(
.precision
)
精度 | 描述 |
.number | 对于整数说明符(d、i、o、u、x、X):precision 指定了要写入的数字的最小位数。如果写入的值短于该数,结果会用前导零来填充。如果写入的值长于该数,结果不会被截断。精度为 0 意味着不写入任何字符。 |
对于 e、E 和 f 说明符:要在小数点后输出的小数位数。 | |
对于 g 和 G 说明符:要输出的最大有效位数。 | |
对于 s : 要输出的最大字符数,不足number则正常输出,超过则截断。默认情况下,所有字符都会被输出,直到遇到末尾的空字符。 | |
对于 c 类型:没有任何影响。 | |
当未指定任何精度时,默认为 1。如果指定时不带有一个显式值,则假定为 0。 | |
.* | 星号,精度在 format 字符串中未指定,但是会作为附加整数值参数放置于要被格式化的参数之前。 但是在尝试的时候似乎有一些数据类型不匹配的情况,不过也用的不多。 |
2.6.3 实例
1 | !/bin/bash |
2.7Shell
中带颜色的输出
2.7.1ANSI
控制码简介
ANSI
控制码用于在字符显示系统中控制光标移动和字符色彩等,常用于BBS
系统中。ANSI ESCAPE SEQUENCES
又称为VT100
系列控制码,国内译为ANSI
控制码。ANSI
控制码依赖终端,不是依赖语言,所以在shell
,perl
,C
里应用都是可以的。
**ANSI
控制码开始的标志都为ESC[
,ESC
对应ASCII码表
的033(八进制)
**,在Shell
编程中,输出的命令有echo
和printf
,echo
需要用-e
启用转义,两个输出命令通过\033或者\e
来输入ESC
,例如\033[32m
即为ESC[32m
。
####2.7.2ANSI
控制码
\033[0m | 关闭所有属性 |
\033[1m | 设置高亮度 |
\033[4m | 下划线 |
\033[5m | 闪烁 |
\033[7m | 反显 |
\033[8m | 消隐 |
\033[30m ~ \033[37m | 设置前景色(字体色) 30:黑 31:红 32:绿 33:黄 34:蓝色 35:紫色 36:深绿 37:白色 |
\033[40m ~ \033[47m | 设置背景色 40:黑 41:深红 42:绿 43:黄色 44:蓝色 45:紫色 46:深绿 47:白色 |
\033[nA | 光标上移n行 |
\033[nB | 光标下移n行 |
\033[nC | 光标右移n行 |
\033[nD | 光标左移n行 |
\033[y;xH | 设置光标位置 |
\033[2J | 清屏 |
\033[K | 清除从光标到行尾的内容 |
\033[s | 保存光标位置 |
\033[u | 恢复光标位置 |
\033[?25l | 隐藏光标 |
\033[?25h | 显示光标 |
【显示字符属性控制】
1 | [n1;n2;......m |
设定显示字符的属性状态。若有两个以上设定则以分号将代码隔开。除非重新设定,否则原设定之属性一直被保留,若想后边的不受影响,可以在结束后加一个\033[0m
。
2.7.3ANSI
码应用格式
echo
1 | echo -e "\033[颜色值m 文本" # 最后加上\033[0m可以使其不影响其他字体 |
printf
1 | printf "\033[颜色值m 文本\n" # 最后加上\033[0m可以使其不影响其他字体 |
2.7.4实例
echo
1 | !/bin/bash |
输出效果:
printf
1 | !/bin/bash |
输出效果:
3.结构性语句
结构性语句主要根据程序的运行状态、输入数据、变量的取值、控制信号以及运行时间等因素来控制程序的运行流程。主要包括:条件测试语句(两路分支)、多路分支语句、循环语句、循环控制语句和后台执行语句等。
3.1分支语句
3.1.1if...else...
语句
- 语法结构
1
1 | if ... fi 结构 |
- 语法结构
2
1 | if ... else ... fi 结构 |
- 语法结构
3
1 | if ... elif ... fi 结构 |
【注意】
(1)以上三种结构中,if
和elif
之后要跟一个then
,结束要有fi
,中间的执行语句不需要像C语言
一样有{}
。
(2)then
可以与if
或者elif
写在同一行,但是要加一个;
(3)if
也常与test
命令一起使用。
3.1.1case
语句
- 语法结构
1 | case 值 in |
【注意】
(1)取值后面必须为单词 in
,每一模式必须以右括号结束。取值可以为变量或常数,匹配发现取值符合某一模式后,其间所有命令开始执行直至 ;;
。
(2)每个 case
分支用右圆括号开始,用两个分号 ;;
表示 break
,即执行结束。如果没有匹配到任何一个模式,使用星号 *
捕获该值,再执行后面的命令。
(3)esac
(就是 case
反过来)作为整个结构结束标记。
例如:
1 | !/bin/bash |
3.2循环语句
3.2.1for
循环
- 语法结构
1
1 | for 变量 in 列表 |
- 语法结构
2
1 | for((表达式1; 表达式2; 表达式2)) |
【注意】
(1)以上两种结构都可以,但是都要注意不可省略do
和done
。
例如:
1 | !/bin/bash |
3.2.2while
循环
- 语法结构
1
1 | while [ expression ] |
- 语法结构
2
1 | while (( expression )) |
例如:
1 | !/bin/bash |
3.2.3until
循环
until
循环执行一系列命令直至条件为 true
时停止。until
循环与 while
循环在处理方式上刚好相反。一般 while
循环优于 until
循环,但在某些时候—也只是极少数情况下,until
循环更加有用。
- 语法结构
1 | until [ expression ] |
例如:
1 | !/bin/bash |
3.2.4无限循环写法
1 | for写法 |
3.2.5跳出循环
在循环过程中,有时候需要在未达到循环结束条件时强制跳出循环,Shell
与C语言
一致,使用两个命令来实现该功能:break
和continue
。用法与C语言
一致。
【注意】
(1)一般来说break
是跳出本层循环,continue
是结束本次循环,直接进入下一次循环。
(2)在嵌套循环中,break
命令后面还可以跟一个整数,表示跳出第几层循环。continue
后边也可以跟一个整数,表示继续第几层循环。
1 | break n # break所在循环层为第1层,然后再向外计算层数 |
例如:
1 | !/bin/bash |
六、Shell
函数
1.函数的定义
1 | [function] function_name() |
【注意】
(1)function
是可选的,可以省略不写,直接就是函数名字也可以。
(2)return int_value
表示返回值,如果不加,将以最后一条命令运行结果,作为返回值。函数的返回值可以在调用该函数后通过$?
来获得。
(3)Shell
中,通过return
只能返回整数值,并且是0-255
的范围,如果超出这个范围就会错误的结果。
(4)如果希望直接从终端调用函数,可以将函数定义在主目录下的 .profile
文件,这样每次登录后,在命令提示符后面输入函数名字就可以立即调用。
2.函数的调用
1 | 方式1 |
【注意】
(1)函数必须先定义后使用,这意味着必须将函数放在脚本开始部分,直至Shell
解释器首次发现它时,才可以使用。不像C语言
一样可以只在前边声明函数,到最后再定义。
(2)Shell
函数的调用只需要写出函数名即可,不需要写出括号()
。
3.函数的删除
像删除变量一样,函数也可以被删除。
1 | unset .f function_name |
4.函数的参数传递
在Shell
中,调用函数时可以向其传递参数。在函数体内部,通过 $n
(也就是位置变量)的形式来获取参数的值。除了$n
还有几个特殊字符也可以用于处理参数。
${n} | 位置变量 |
${*} | 以一个单字符串显示所有向脚本传递的参数 |
${$} | 脚本运行的当前进程ID号 |
${!} | 后台运行的最后一个进程的ID号 |
${@} | 与$*相同,但是使用时加引号,并在引号中返回每个参数 |
${?} | 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误 |
$# | 传递到脚本或函数的参数个数 |
【注意】 最后一个 #
特殊字符在使用时也可以加 {}
,只不过这里不写是因为加上后,会导致Hexo
渲染后的页面发生错乱。
1 | 定义函数时使用传入的参数 |
例如:
1 | !/bin/bash |
输出结果为:
1 | 1 = 1 |
5.函数内部的变量
Shell
函数内部可以定义变量,有两种定义方式。两种方式代表了变量有不同的作用域。
全局作用域:在脚本的其他任何地方都能够访问该变量。
局部作用域:只能在声明变量的作用域内(函数内部)访问。
1 | 局部变量 |
例如:
1 | !/bin/bash |
输出结果为:
1 | 函数内打印在函数内部定义的变量 |
可以看出,局部变量var2
的值在函数外是访问不到的。
七、Shell
文件包含
Shell
脚本与C语言
一样,可以包含外部文件,Shelll
可以将外部脚本的内容合并到当前脚本。这样可以很方便的封装一些公用的代码作为一个独立的文件以便于使用。
Shell
文件包含的语法格式有两种:
1 | 语法格式1 |
【注意】被包含的脚本文件不需要可执行权限。
例如:
1 | ------------以下分隔开的是两个文件--------- |
./file2.sh
输出结果:
1 | file1.sh |
八、Shell
重定向
Unix
命令默认从标准输入设备(stdin
)获取输入,将结果输出到标准输出设备(stdout
)显示。一般情况下,标准输入设备就是键盘,标准输出设备就是终端,即显示器。
8.1输出重定向
- 输出重定向的概念
命令的输出不仅可以是显示器,还可以输出到某个文件,这就叫做输出重定向。
命令 | 说明 |
command > file | 将输出到显示器的内容重定向到 file文件。 【注意】file文件中原有的内容会被清空 |
command >> file | 将输出到显示器的内容以追加的方式重定向到 file文件。 【注意】file文件中原有的内容会保留,新的内容追加在结尾。 |
【注意】>
和>>
两个符号都是起到重定向的作用,但是,前者会清空原有文件,但是后者则为追加。
- 实例
1 | 命令行中执行 |
运行结果如下图:
8.2输入重定向
- 输入重定向的概念
Unix
命令也可以从文件获取输入,而不是默认的键盘
命令 | 说明 |
command < file | 本来需要从键盘获取输入的命令会转移到文件file中读取内容。 |
- 实例
1 | 命令行中执行 |
运行结果如下图:
第一个命令没有进行重定向,会输出文件名;第二个命令却不会,因为第二个命令仅仅知道从标准输入读取内容。
8.3重定向的深入理解
文件描述符,File descriptor
,简称fd
,当应用程序请求内核打开/新建一个文件时,内核会返回一个文件描述符用于对应这个打开/新建的文件,其fd
本质上就是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX
、Linux
这样的操作系统。
文件描述符 0
通常是标准输入(STDIN
),1
是标准输出(STDOUT
),2
是标准错误输出(STDERR
)。
一般情况下,每个 Unix/Linux
命令运行时都会打开三个文件
标准输入文件(stdin) | stdin的文件描述符为0,Unix程序默认从stdin读取数据。 |
标准输出文件(stdout) | stdout 的文件描述符为1,Unix程序默认向stdout输出数据。 |
标准错误文件(stderr) | stderr的文件描述符为2,Unix程序会向stderr流中写入错误信息。 |
默认情况下,command > file
将 stdout
重定向到 file
,command < file
将stdin
重定向到 file
。
所有可用重定向命令如下:
命令 | 说明 |
command > file | 将输出到显示器的内容重定向到 file文件。 【注意】file文件中原有的内容会被清空 |
command >> file | 将输出到显示器的内容以追加的方式重定向到 file文件。 【注意】file文件中原有的内容会保留,新的内容追加在结尾。 |
n > file | 将文件描述符为 n 的内容重定向到 file文件。 【注意】file文件中原有的内容会被清空。 |
n >> file | 将文件描述符为 n 的内容以追加的方式重定向到 file文件。 【注意】file文件中原有的内容会保留,新的内容追加在结尾。 |
n >& file | 将输出文件 file 和 n 合并。 |
命令 | 说明 |
command < file | 本来需要从键盘获取输入的命令会转移到文件file中读取内容。 |
n <& file | 将输入文件 file 和 n 合并。 |
<< tag | 将开始标记 tag 和结束标记 tag 之间的内容作为输入。 |
-
stderr
重定向到file
1 | command 2 > file # 清空原文件再写入 |
stdout
和stderr
合并后重定向到file
1 | command > file 2>&1 |
【注意】这里的 2
和 >
之间不可以有空格,2>
是一体的时候才表示错误输出。
stdin
和stdout
都重定向
1 | command < file1 >file2 |
command
命令将 stdin
重定向到 file1
,将 stdout
重定向到 file2
。
Here Document
Here Document
是 Shell
中的一种特殊的重定向方式,它的基本的形式如下:
1 | 将两个 delimiter 之间的内容(document) 作为输入传递给 command |
【注意】
(1)结尾的delimiter
一定要顶格写,前面不能有任何字符,后面也不能有任何字符,包括Space
和 Tab
缩进。
(2)开始的delimiter
前后的空格会被忽略掉。
例如:
1 | !/bin/bash |
输出结果:
1 | Hello,World! |
/dev/null
文件
如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null
。/dev/null
是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null
文件非常有用,将命令的输出重定向到它,会起到”禁止输出”的效果。
1 | command > /dev/null 2>&1 # 屏蔽 stdout 和 stderr |
九、Shell
中的各种括号
9.1${}
${ }
用于变量替换,一般情况下,$var
与${var}
一样,但是用 ${ }
会比较精确的界定变量名称的范围。
1 | {Variable} |
9.2$()
$()
与反引号**``**都可用做命令替换。
1 | 命令替换格式 |
- 实例
1 | !/bin/bash |
输出结果:
1 | `date`= 2022年 02月 16日 星期三 16:26:49 CST |
9.3(())
只要括号中的运算符、表达式符合C语言
运算规则,都可用在$((expression))
中,甚至是三目运算符。作不同进位(如二进制、八进制、十六进制)运算时,输出结果全都自动转化成十进制。
用 (( ))
也可重定义变量值
例如:
1 | !/bin/bash |
输出结果:
1 | var=4 |
9.4[]
bash
的内部命令,[
和test
是等同的。if/test
结构中的左中括号[
是调用test
的命令标识,右中括号]
是关闭条件判断的。这个命令把它的参数作为比较表达式或者作为文件测试,并且根据比较的结果来返回一个退出状态码。if/test
结构中并不是必须右中括号,但是新版的Bash
中要求必须这样。
字符范围。用作正则表达式的一部分,描述一个匹配的字符范围。作为test
用途的中括号内不能使用正则表达式。
9.5$[]
和$(())
$[]
和$(())
是一样的,都是进行数学运算的。
例如:
1 | !/bin/bash |
输出结果:
1 | ((a * b * c))=30 |
9.5[[]]
  [[
是 bash
程序语言的关键字。并不是一个命令,[[ ]]
结构比[ ]
结构更加通用。在[[
和]]
之间所有的字符都不会发生文件名扩展或者单词分割,但是会发生参数扩展和命令替换。
支持字符串的模式匹配,使用=~
操作符时甚至支持shell
的正则表达式。字符串比较时可以把右边的作为一个模式,而不仅仅是一个字符串,比如[[ hello == hell? ]]
,结果为真。[[ ]]
中匹配字符串或通配符,不需要引号。
使用[[ ... ]]
条件判断结构,而不是[ ... ]
,能够防止脚本中的许多逻辑错误。比如,&&
、||
、<
和>
操作符能够正常存在于[[ ]]
条件判断结构中,但是如果出现在[ ]
结构中的话,会报错。比如可以直接使用if [[ $a != 1 && $a != 2 ]]
, 如果不适用双括号, 则为if [ $a -ne 1] && [ $a != 2 ]
或者if [ $a -ne 1 -a $a != 2 ]
。