羽夏 Bash 简明教程(上)

写在前面

  该文章根据 the unix workbench 中的 Bash Programming 进行汉化处理并作出自己的整理,并参考 Bash 脚本教程BashPitfalls 相关内容进行补充修正。一是我对 Bash 的学习记录,二是对大家学习 Bash 有更好的帮助。如对该博文有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我。本篇博文可能比较冗长,请耐心阅读和学习。

Bash 简介

  Bash 是 Unix 系统和 Linux 系统的一种 Shell(命令行环境),是目前绝大多数 Linux 发行版的默认 Shell。Shell 是一个程序,提供一个与用户对话的环境。这个环境只有一个命令提示符,让用户从键盘输入命令,所以又称为命令行环境。Shell 接收到用户输入的命令,将命令送入操作系统执行,并将结果返回给用户。其次,Shell 是一个命令解释器,解释用户输入的命令。它支持变量、条件判断、循环操作等语法,所以用户可以用 Shell 命令写出各种小程序,又称为脚本,这个也是本篇重点讲解的部分。

开发环境

  既然学的是 Bash ,那么必然是任何 Linux 发行版。至于用什么编辑器都可以。不过我个人建议使用VSCode + Bash Extension Pack进行学习,因为它有纠错功能,并指出一些不合适的写法,我也会在本篇末也会介绍一些。我羽夏使用的就是我建议使用的工具,我也默认你使用的是它,如果用其他的自己酌情参考。下面开始进入正题:

入门

  好,在阳光明媚、微风和煦(bushi)的一天,我们信心满满的开始了 Bash 的学习旅程。熟练的打开 VSCode ,进入 Bash 工作区,然后新建了一个名字叫math.sh的干净纯洁的文件,然后输入了以下内容:

#!/bin/bash
# File: math.sh

  注意,你不要复制和粘贴这些行到文件中,尽管你应该准确地输入我输入的的内容。写代码时你应该尽可能多地自己练习,因为写代码是一个实践性十分强的项目,不能眼高手低。这两行都以#开头,在 Bash 编程语言中,在#之后键入的任何内容都将被忽略(除非位于花括号之间,但这仅在非常特定的情况下)。#允许你在代码中进行注释,以便以后的你理解你当下写的代码,也可以使其他人更快地知道你的程序是如何设计的。
  但是,上面的内容的第一行代码具有特殊的含义,虽然它是注释。该行被称为 Shebang 行。脚本的第一行通常是指定解释器,即这个脚本必须通过什么解释器执行,而这一行以#!字符开头,正如上面展示的。
  #!后面就是脚本解释器的位置,Bash 脚本的解释器一般是/bin/sh/bin/bash

#!/bin/sh

  或者

#!/bin/bash

  #!与脚本解释器之间有没有空格,都是可以的。如果 Bash 解释器不放在目录/bin,脚本就无法执行了。为了保险,可以写成下面这样。

#!/usr/bin/env bash

  上面命令使用env命令,这个命令总是在/usr/bin目录,返回 Bash 可执行文件的位置,从而避免了这个问题。
  Shebang 行不是必需的,但是建议加上这行。如果缺少该行,就需要手动将脚本传给解释器。举例来说,脚本是script.sh,有 Shebang 行的时候,可以直接调用执行。

wingsummer@wingsummer-PC ~ → ./script.sh

  上面例子中,script.sh是脚本文件名。脚本通常使用.sh后缀名,不过这不是必需的。如果没有 Shebang 行,就只能手动将脚本传给解释器来执行。

wingsummer@wingsummer-PC ~ → /bin/sh ./script.sh

  或者

wingsummer@wingsummer-PC ~ → bash ./script.sh

  注意,“只要指定了 Shebang 行的脚本,可以直接执行”这句话有个前提条件,就是脚本需要有执行权限,否则这行也是没作用的。

数学运算

内容讲解

  Bash 编程语言可以完成非常基本的算法,现在你在 VSCode 打开了math.sh这个文件,我们开始输入下面内容:

#!/usr/bin/env bash
# File: math.sh

expr 5 + 2
expr 5 - 2
expr 5 \* 2
expr 5 / 2

  保存,并在终端去执行它,你将会得到如下结果:

7
3
10
2

  让我们分析一下刚才创建的 Bash 脚本中发生了什么。Bash 按照从文件的第一行到最后一行的顺序执行程序。expr命令可用于计算 Bash 表达式。表达式只是一个有效的 Bash 代码字符串,在运行时会生成一个结果。您已经熟悉的加法(+)、减法(-)和乘法(*)的算术运算符的工作方式与您预期的一样。请注意:在进行乘法运算时,需要转义星号字符,否则 Bash 会认为您正在尝试创建正则表达式。由于5 / 2 = 2.5,除法运算符(/)的工作方式与预期不同。Bash 进行整数除法,这意味着一个数除以另一个数的结果总是向下舍入到最接近的整数。让我们看一下命令行上的几个示例:

expr 1 / 3
expr 10 / 3
expr 40 / 21
expr 40 / 20

  另一个你可能不熟悉的数值运算符是模运算符(%)。模运算符返回整数除法后的余数。在整数除法中,如果A / B = CA % B = D,那么B * C + D = A。让我们看看命令行上的一些示例:

expr 1 % 3
expr 10 % 3
expr 40 % 21
expr 40 % 20

  然后是它的执行结果:

1
1
19
0

  注意,当一个数完全可被另一个数整除时,模的结果为零。如果你想做更复杂的数学运算,比如分数和小数,那么我强烈建议将echo和名为bc的台式计算器程序结合起来。打开一个名为bigmath.sh的新文件并输入以下内容:

#!/usr/bin/env bash
# File: bigmath.sh

echo "22 / 7" | bc -l
echo "4.2 * 9.15" | bc -l
echo "(6.5 / 0.5) + (6 * 2.2)" | bc -l

  如下是计算结果:

3.14285714285714285714
38.430
26.20000000000000000000

  为了在计算中使用十进制数,可以使用-l标志将任何数学字符串传输到bc

内容小结

  • Bash 程序从文件的第一行到最后一行按顺序执行。
  • #后面写的任何东西都是注释,Bash 不会执行。
  • 可以使用expr命令执行简单的算术运算。
  • 通过使用echo将字符串表达式传输到bc中,执行更复杂的数学运算。

小试牛刀

  1. 请使用命令行查看bc的帮助手册。
  2. bc交互控制台进行一些数运算。
  3. 在一个文件中写一些等式,然后将该文件作为参数提供给bc
🔒 点击查看答案 🔒
# 1:
wingsummer@wingsummer-PC ~ → man bc

# 2:略

# 3:

wingsummer@wingsummer-PC ~ → echo "1+8" > test.txt
wingsummer@wingsummer-PC ~ → bc test.txt
bc 1.07.1
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006, 2008, 2012-2017 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'. 
9

变量

内容讲解

  Bash 变量分成环境变量和自定义变量两类。环境变量是 Bash 环境自带的变量,进入 Shell 时已经定义好了,可以直接使用。它们通常是系统定义好的,也可以由用户从父 Shell 传入子 Shell。使用env命令或printenv命令,可以显示所有环境变量。创建变量的时候,变量名必须遵守下面的规则:

  • 变量尽量全为小写
  • 变量开头必须要以字母开头
  • 变量只能包含英文字符和下划线_
  • 变量之间的单词最好要用下划线分割,不允许出现空格和标点符号

  如果遵循这些规则,你就可以避免意外地覆盖存储在环境变量中的数据。
  可以使用等号(=)将数据分配给变量。存储在变量中的数据可以是字符串或数字。现在让我们在命令行上创建一个变量:

myvar="hello world"

  变量名在等号的左侧,存储在该变量中的数据在等号的右侧。请注意,等号两边都没有空格,分配变量时不允许使用空格:

myvar = "hello world"   #错误!

  为了打印变量(也称为变量值)中的数据,我们可以使用echo。要检索变量的值,必须在变量名称前使用美元符号$

wingsummer@wingsummer-PC ~ → myvar="helloworld"
wingsummer@wingsummer-PC ~ → echo $myvar
helloworld

  通过使用let命令,可以使用算术运算符修改变量的值:

wingsummer@wingsummer-PC ~ → myvar=5
wingsummer@wingsummer-PC ~ → let newvar=$myvar
wingsummer@wingsummer-PC ~ → echo $newvar
6

  有时,您可能希望像在命令行上一样运行命令,并将该命令的结果存储在变量中。我们可以通过将命令用美元符号和括号$())括起来来实现这一点。这种语法称为命令替换。执行该命令,然后替换为运行该命令所产生的字符串。例如,如果我们想在获取math.sh文件的行数:

math_lines=$(cat math.sh | wc -l)
echo $math_lines

  带美元符号的变量名也可以在其他字符串中使用,以便将变量的值插入字符串:

wingsummer@wingsummer-PC ~ → myvar="world"
wingsummer@wingsummer-PC ~ → echo "hello $myvar"
hello world

  在编写 Bash 脚本时,可以自由使用一些变量。让我们创建一个名为vars.sh的新文件。使用下面的代码:

#!/usr/bin/env bash
# File: vars.sh

echo "Script arguments: $*"
echo "First arg: $1. Second arg: $2."
echo "Number of arguments: $#"

  现在,让我们尝试以几种不同的方式运行脚本:

wingsummer@wingsummer-PC ~ → bash vars.sh
Script arguments:
First arg: . Second arg: .
Number of arguments: 0
wingsummer@wingsummer-PC ~ → bash vars.sh red
Script arguments: red
First arg: red. Second arg: .
Number of arguments: 1
wingsummer@wingsummer-PC ~ → bash vars.sh red blue
Script arguments: red blue
First arg: red. Second arg: blue.
Number of arguments: 2
wingsummer@wingsummer-PC ~ → bash vars.sh red blue green
Script arguments: red blue green
First arg: red. Second arg: blue.
Number of arguments: 3

  你的脚本可以像命令行程序一样接受参数。脚本的第一个参数存储在$1中,第二个参数存储在$2中……但如果脚本的参数多于9个,那么第10个参数可以用${10}的形式引用,以此类推。传递给脚本的所有参数的数组存储在$*中,我们将在本章后面讨论如何处理数组。传递给脚本的参数总数存储在$#中。既然知道如何将参数传递给脚本,我们就可以开始编写自己的命令行工具了!
  下面我们继续扩展一下这个特殊的变量:

变量 含义
$0 脚本文件名
$1-$9 对应脚本的第一个参数到第九个参数
$# 参数的总数
$* 全部的参数,参数之间使用变量$IFS值的第一个字符分隔,默认为空格,但是可以自定义
$@ 全部的参数,参数之间使用空格分隔
$? 上一个命令的退出码,用来判断上一个命令是否执行成功。返回值是0,表示上一个命令执行成功;如果不是零,表示上一个命令执行失败
$$ 当前 Shell 的进程 ID
$_ 上一个命令的最后一个参数
$! 最近一个后台执行的异步命令的进程 ID
$- 当前 Shell 的启动参数

  上面的特殊变量我就不一一做示例验证了,感兴趣的话可以自己试试。
  前面我们简单提过读取变量的值是前面加一个$,下面我们继续讨论读取变量这个事情。
  读取变量的时候,直接在变量名前加上$就可以了,但变量名也可以使用花括号{}包围,比如$a也可以写成${a}。这种写法可以用于变量名与其他字符连用的情况,如下所示:

wingsummer@wingsummer-PC ~ → myvar="hello"
wingsummer@wingsummer-PC ~ → echo $myvar_world

wingsummer@wingsummer-PC ~ → echo ${myvar}_world
hello_world

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

wingsummer@wingsummer-PC ~ → myvar=USER
wingsummer@wingsummer-PC ~ → echo ${!myvar}
wingsummer

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

wingsummer@wingsummer-PC ~ → a="1 2    3"
wingsummer@wingsummer-PC ~ → echo $a
1 2 3
wingsummer@wingsummer-PC ~ → echo "$a"
1 2    3

  这个也将会在篇末继续讨论。
  如果我使用了一个变量,我突然不想要了咋整。我们可以使用unset命令:

wingsummer@wingsummer-PC ~ → echo $myvar
USER
wingsummer@wingsummer-PC ~ → unset myvar
wingsummer@wingsummer-PC ~ → echo $myvar

  用户创建的变量仅可用于当前 Shell,子 Shell 默认读取不到父 Shell 定义的变量。有时我们有一种使用情况,我在一个 Shell 声明的变量,我想让它的子 Shell 也能用,我们可以将该变量进行导出:

NAME=foo
export NAME

  上面命令输出了变量NAME。变量的赋值和输出也可以在一个步骤中完成。

export NAME=value

  上面命令执行后,当前 Shell 及随后新建的子 Shell,都可以读取变量$NAME
  对于参数,如果我想获取,如果没有返回默认值,这个我们通过 Shell 如何实现呢?如下是表格总结:

语法 含义
${varname:-word} 如果变量varname存在且不为空,则返回它的值,否则返回word。它的目的是返回一个默认值,比如${count:-0}表示变量count不存在时返回0
${varname:=word} 如果变量varname存在且不为空,则返回它的值,否则将它设为word,并且返回word。它的目的是设置变量的默认值,比如${count:=0}表示变量count不存在时返回0,且将count设为0
${varname:+word} 如果变量名存在且不为空,则返回word,否则返回空值。它的目的是测试变量是否存在,比如${count:+1}表示变量count存在时返回1(表示true),否则返回空值
${varname:?message} 如果变量varname存在且不为空,则返回它的值,否则打印出varname: message,并中断脚本的执行。如果省略了message,则输出默认的信息parameter null or not set.。它的目的是防止变量未定义,比如${count:?"undefined!"}表示变量count未定义时就中断执行,抛出错误,返回给定的报错信息undefined!

  声明变量的方式不仅仅本篇介绍的,还有declarereadonly,由于感觉并不太常用就不介绍了,如果想详细了解请阅读 Bash 变量 相应的部分。

内容小结

  • 变量可以用等号运算符赋值。
  • 字符串或数字可以分配给变量。
  • 变量的值可以在变量名前用美元符号$访问。
  • 可以使用美元符号和括号语法(命令替换)执行命令并将输出保存在变量中。
  • 可以在自己的脚本中使用美元符号和参数编号来访问命令行参数。
  • Shell 有一些特殊的变量以访问参数和其他信息。
  • 定义变量有多种方式,比如直接等号赋值、使用declarereadonly声明变量。
  • 定义的变量可删除。

小试牛刀

  1. 编写一个 Bash 程序,将两个数字分配给不同的变量,然后程序打印这些变量的总和。
  2. 编写一个 Bash 程序,将两个字符串分配给不同的变量,然后程序打印这两个字符串。要求打印两次,第一次分行,第二次不分行。
  3. 编写一个 Bash 程序,打印提供给该程序的参数个数乘以提供给该程序的第一个参数(假设该参数为整数)。
🔒 点击查看答案 🔒
# 1
num1=$1
num2=$2
echo $(($num1+$num2))

# 2
str1=$1
str2=$2
echo "$str1"
echo "$str2"
echo "$str1$str2"

#3
num1=$#
num2=$1
echo $(($num1*$num2))

算术运算

  由于前面的内容仅仅讲解了简单的算术运算作为入门,下面开始进行介绍算术运算:

算术表达式

  (())可以进行整数的算术运算,如下所示。

wingsummer@wingsummer-PC ~ → echo $((5+5))
10

  上面的$的作用就读取算术运算的结果的意思,我们还可以拆成下面的代码:

wingsummer@wingsummer-PC ~ → ((NUM = 5+5))
wingsummer@wingsummer-PC ~ → echo $NUM
10

  它会自动忽略内部的空格,所以下面的写法都正确,得到同样的结果。

((2+2))          #写法1
(( 2+2 ))        #写法2
(( 2 + 2 ))      #写法3

  它不返回值,命令执行的结果根据算术运算的结果而定。只要算术结果不是0,命令就算执行成功,至于是否成功我们可以使用环境变量$?进行获取,这个会在之后的部分进行讲解。
  (())支持的算术运算符如下:

  • + :加法
  • - :减法
  • * :乘法
  • / :除法(整除)
  • % :余数
  • ** :指数
  • ++ :自增运算(前缀或后缀)
  • -- :自减运算(前缀或后缀)

  自增运算好自减运算的规则和C/C++是一样的,作为前缀是先运算后返回值,作为后缀是先返回值后运算。我们可以作出如下测试:

wingsummer@wingsummer-PC ~ → i=0
wingsummer@wingsummer-PC ~ → echo $i
0
wingsummer@wingsummer-PC ~ → echo $((i++))
0
wingsummer@wingsummer-PC ~ → echo $i
1
wingsummer@wingsummer-PC ~ → echo $((++i))
2
wingsummer@wingsummer-PC ~ → echo $i
2

数值的进制

  Bash 的数值默认都是十进制,但是在算术表达式中,也可以使用其他进制。

  • number:没有任何特殊表示法的数字是十进制数(以10为底)。
  • 0number:八进制数。
  • 0xnumber:十六进制数。
  • base#number:base进制的数。

  下面是一些例子:

wingsummer@wingsummer-PC ~ → echo $((0xff))
255
wingsummer@wingsummer-PC ~ → echo $((2#11111111))
255

位运算

  $(())支持以下的二进制位运算符。

  • << :位左移运算,把一个数字的所有位向左移动指定的位。
  • >> :位右移运算,把一个数字的所有位向右移动指定的位。
  • & :位的与运算,对两个数字的所有位执行一个AND操作。
  • | :位的或运算,对两个数字的所有位执行一个OR操作。
  • ~ :位的反运算,对一个数字的所有位取反。
  • ^ :位的异或运算,对两个数字的所有位执行一个XOR操作。

  如下是一些例子:

wingsummer@wingsummer-PC ~ → echo $((16>>2))
4
wingsummer@wingsummer-PC ~ → echo $((16<<2))
64
wingsummer@wingsummer-PC ~ → echo $((17&3))
1
wingsummer@wingsummer-PC ~ → echo $((17|3))
19
wingsummer@wingsummer-PC ~ → echo $((17^3))
18

逻辑运算

  $(())支持以下的逻辑运算符:

  • < :小于
  • > :大于
  • <= :小于或相等
  • >= :大于或相等
  • == :相等
  • != :不相等
  • && :逻辑与
  • || :逻辑或
  • ! :逻辑否
  • expr1?expr2:expr3 :三元条件运算符。若表达式expr1的计算结果为真,则执行表达式expr2,否则执行表达式expr3

  如下是几个例子:

wingsummer@wingsummer-PC ~ → echo $((3 > 2))
1
wingsummer@wingsummer-PC ~ → echo $(( (3 > 2) || (4 <= 1) ))
1
wingsummer@wingsummer-PC ~ → a=0
wingsummer@wingsummer-PC ~ → echo $((a<1 ? 1 : 0))
1
wingsummer@wingsummer-PC ~ → echo $((a>1 ? 1 : 0))
0

赋值运算

  算术表达式$(())可以执行赋值运算,先看一个例子:

wingsummer@wingsummer-PC ~ → echo $((a=1))
1
wingsummer@wingsummer-PC ~ → echo $a
1

  上面例子中,a=1对变量a进行赋值。这个式子本身也是一个表达式,返回值就是等号右边的值。
  $())支持的赋值运算符,有以下这些:

  • parameter = value :简单赋值
  • parameter += value等价于parameter = parameter + value
  • parameter -= value :等价于parameter = parameter – value
  • parameter *= value :等价于parameter = parameter * value
  • parameter /= value:等价于parameter = parameter / value
  • parameter %= value:等价于parameter = parameter % value
  • parameter <<= value:等价于parameter = parameter << value
  • parameter >>= value:等价于parameter = parameter >> value
  • parameter &= value:等价于parameter = parameter & value
  • parameter |= value:等价于parameter = parameter | value
  • parameter ^= value:等价于parameter = parameter ^ value

  下面是一个例子:

wingsummer@wingsummer-PC ~ → foo=5
wingsummer@wingsummer-PC ~ → echo $((foo*=2))
10

  如果在表达式内部赋值,可以放在圆括号中,否则会报错。

wingsummer@wingsummer-PC ~ → echo $(( a<1 ? (a+=1) : (a-=1) ))

求值运算

  逗号,$(())内部是求值运算符,执行前后两个表达式,并返回后一个表达式的值,例子如下:

wingsummer@wingsummer-PC ~ → echo $((foo = 1 + 2, 3 * 4))
12
wingsummer@wingsummer-PC ~ → echo $foo
3

  上述命令逗号前后两个表达式都会执行,然后返回后一个表达式的值12

用户输入

内容讲解

  如果你正在为自己或其他人制作Bash程序,那么获得用户输入的一种方法就是指定用户提供给您的程序的参数。您还可以通过使用read命令暂时停止程序的执行,要求用户在命令行中键入字符串。让我们编写一个小脚本,从中可以了解read命令的工作原理:

#!/usr/bin/env bash
# File: letsread.sh

echo "Type in a string and then press Enter:"
read response
echo "You entered: $response"

  然后运行该脚本:

Type in a string and then press Enter:
|

  上面的|表示光标的位置,我们输入Hello!,然后回车:

Type in a string and then press Enter:
Hello!
You entered: Hello!

  read命令提示用户键入字符串,用户提供的字符串存储在脚本中指定给read命令的变量中,下面我们来看一下它的高级用法,首先得了解它的使用和参数,read命令的格式如下。

read [-options] [variable...]

  具体参数总结如下:

参数 含义
-t 设置超时的秒数。如果超过了指定时间,用户仍然没有输入,脚本将放弃等待,继续向下执行
-p 指定用户输入的提示信息
-a 把用户的输入赋值给一个数组,从零号位置开始
-n 指定只读取若干个字符作为变量值,而不是整行读取
-e 允许用户输入的时候,使用readline库提供的快捷键,比如自动补全。
-r 不把用户输入的反斜杠字符解释为转义字符
-s 使得用户的输入不显示在屏幕上,这常常用于输入密码或保密信息

  当然read的参数并不仅仅这些,剩下的就不太常用了,具体例子可以自己写进行测试,这里由于篇幅就不进行了。

内容小结

  • read存储用户在变量中提供的字符串。

小试牛刀

  1. 编写一个脚本,要求用户输入形容词、名词和动词,然后使用这些词造句。
🔒 点击查看答案 🔒
read -r -p "请输入形容词:" adj
read -r -p "请输入名词:" n
read -r -p "请输入动词:" v

echo "$v $adj $v"

条件判断

内容讲解

  在编写计算机程序时,您的程序能够根据参数、文件和环境变量等输入做出决策通常很有用。Bash 提供了创建类似于数学方程的逻辑表达式的机制。可以对这些逻辑表达式求值,直到它们为真或假。事实上,truefalse都是简单的 Bash 命令。现在我们测试一下:

true
false

  貌似看起来他们差不多。为了了解它们是如何工作的,我们需要稍微了解一下 Unix 的特性。每当在命令行上执行程序时,通常会发生以下两种情况之一:要么命令执行成功,要么出现错误。就错误而言,程序可能会出现很多错误,Unix 可以根据发生的错误类型采取不同的操作。例如,如果我输入了终端中不存在的命令名,那么我将看到一个错误:

this_command_does_not_exist

  由于该命令不存在,它会创建一种特定类型的错误,该错误由程序的退出状态指示。程序的退出状态是一个整数,表示程序是否成功执行或是否发生错误。上次程序运行的退出状态存储在$?中。我们可以通过echo查看最后一个程序的退出状态:

echo $?

  这个特定的退出状态向 Shell 发出指示,它应该向控制台打印一条错误消息。成功运行的程序的退出状态是什么?我们来看看:

echo I will succeed.
echo $?

  它的输出如下:

I will succeed.
0

  因此,成功程序的退出状态为0。现在我们来看一下truefalse的退出状态:

wingsummer@wingsummer-PC ~ → true
wingsummer@wingsummer-PC ~ → echo $?
0
wingsummer@wingsummer-PC ~ → false
wingsummer@wingsummer-PC ~ → echo $?
1

  如您所见,true的退出状态为0false的退出状态为1。由于这些程序没有做很多其他事情,所以可以将true定义为始终具有0退出状态的程序,将false定义为始终具有1退出状态的程序。
  在讨论逻辑运算符时,了解这些程序的退出状态很重要:AND运算符&&OR运算符||ANDOR运算符可用于在命令行上有条件地执行程序。当一个程序的执行取决于另一个程序的退出状态时,就会发生条件执行。例如,对于AND运算符,只有当&&左侧的程序的退出状态为0时,才会执行&&右侧的程序。让我们来看一些小例子:

true && echo "Program 1 was executed."
false && echo "Program 2 was executed."

  由于false的退出状态为1,因此echo "Program 2 was executed."不会被执行,因此不会为该命令向控制台打印任何内容。可以将多个和运算符链接在一起,如下所示:

false && true && echo Hello
echo 1 && false && echo 3
echo Athos && echo Porthos && echo Aramis

  在由AND运算符连接在一起的一系列程序中,程序右侧任何非零退出状态的程序都不会执行。OR运算符||遵循一组类似的原则。||右侧的命令只有在左侧的命令失败,因此退出状态不是0时才会执行。让我们看看它是如何工作的:

true || echo "Program 1 was executed."
false || echo "Program 2 was executed."

  结果只执行了echo "Program 2 was executed.",因为false的退出状态为非零。你可以组合多个OR运算符,以便只执行退出状态为0的第一个程序:

false || echo 1 || echo 2
echo 3 || false || echo 4
echo Athos || echo Porthos || echo Aramis

  可以在命令中组合ANDOR运算符,这些命令从左到右求值:

echo Athos || echo Porthos && echo Aramis
echo Gaspar && echo Balthasar || echo Melchior

  通过组合ANDOR运算符,可以精确控制执行某些命令的条件。
  使 Bash 脚本能够做出决策非常有用。条件执行允许您根据某些程序是成功还是失败来控制执行这些程序的情况,但您也可以构造条件表达式,这些表达式是等价于truefalse的逻辑语句。条件表达式要么比较两个值,要么询问关于一个值的问题。条件表达式总是在双中括号[[]]之间,它们要么使用逻辑标志,要么使用逻辑运算符。例如,有几个逻辑标志可用于比较两个整数。如果我们想知道一个整数是否大于另一个整数,我们可以使用-gt,即大于。在命令行中输入以下简单条件表达式:

[[ 4 -gt 3 ]]

  上面的逻辑表达式是这样的:4是否大于3?没有结果被打印到控制台,所以让我们检查该表达式的退出状态:

wingsummer@wingsummer-PC ~ → echo $?
0

  该程序的退出状态似乎为0,与true的退出状态相同。这个条件表达式表示[[ 4 -gt 3 ]]等价于true,我们当然知道这在逻辑上是一致的,4实际上是大于3的。让我们看看如果我们翻转表达式会发生什么,我们问3是否大于4

[[ 3 -gt 4 ]]

  同样,控制台上没有打印任何内容,因此我们将查看退出状态:

wingsummer@wingsummer-PC ~ → echo $?
1

  显然3不大于4,所以这个错误的逻辑表达式导致退出状态为1,这与false的退出状态相同。因为它们具有相同的退出状态[[ 3 -gt 4 ]]false本质上是等价的。为了快速测试条件表达式的逻辑值,我们可以使用ANDOR运算符,以便表达式在为真时打印t,在为假时打印f

[[ 4 -gt 3 ]] && echo t || echo f
[[ 3 -gt 4 ]] && echo t || echo f

  这是一个小技巧,可以用来快速查看逻辑表达式的结果值。这些二进制逻辑表达式比较两个值,但也有一元逻辑表达式只查看一个值。例如,可以使用-e逻辑标志测试文件是否存在:

wingsummer@wingsummer-PC ~ → cd ~ # 假设我们的 math.sh 在该目录下
wingsummer@wingsummer-PC ~ → [[ -e math.sh ]] && echo t || echo f
t

  大多数情况下,在编写 Bash 脚本时,您不会比较两个原始值,也不会试图找出关于一个原始值的信息,而是希望创建一个关于变量中包含的值的逻辑语句。变量的行为就像逻辑表达式中的原始值。让我们看几个例子:

number=7
[[ $number -gt 3 ]] && echo t || echo f
[[ $number -gt 10 ]] && echo t || echo f
[[ -e $number ]] && echo t || echo f

  7大于3,尽管它不大于10,而且这个目录中没有名为7的文件。还有其他几种不同的逻辑标志,如下表格所示:

标志 含义 示例
-gt 大于 [[ $planets -gt 8 ]]
-ge 大于等于 [[ $votes -ge 270 ]]
-eq 等于 [[ $fingers -eq 10 ]]
-ne 不等于 [[ $pages -ne 0 ]]
-le 小于等于 [[ $candles -le 9 ]]
-lt 小于 [[ $wives -lt 2 ]]
-e 文件是否存在 [[ -e $taxes_2016 ]]
-d 文件夹是否存在 [[ -d $photos ]]
-z 字符串长度是否为零 [[ -z $name ]]
-n 字符串长度是否非零 [[ -n $name ]]

  在继续下一节之前,请尝试在命令行中使用这些标志。除了逻辑标志,还有逻辑运算符。最有用的逻辑运算符之一是正则表达式匹配运算符=~。正则表达式匹配运算符将字符串与正则表达式进行比较,如果字符串与正则表达式匹配,则表达式等价于true,否则等价于false。让我们用两种不同的方法测试这个操作符:

[[ rhythms =~ [aeiou] ]] && echo t || echo f
my_name=sean
[[ $my_name =~ ^s.+n$ ]] && echo t || echo f

  还有NOT运算符!,它反转任何条件表达式的值。NOT运算符将真表达式转换为假表达式,反之亦然。让我们来看几个使用NOT运算符的示例:

[[ 7 -gt 2 ]] && echo t || echo f
[[ ! 7 -gt 2 ]] && echo t || echo f
[[ 6 -ne 3 ]] && echo t || echo f
[[ ! 6 -ne 3 ]] && echo t || echo f

  下面是一些有用的逻辑运算符的列表,以供参考:

标志 含义 示例
=~ 匹配正则表达式 [[ $consonants =~ [aeiou] ]]
= 判断字符串是否相同 [[ $password = "pegasus" ]]
!= 判断字符串是否不同 [[ $fruit != "banana" ]]
! 取反 [[ ! "apple" =~ ^b ]]

  条件表达式非常强大,因为可以使用它们来控制正在编写的 Bash 程序的执行方式。Bash 编程中的一个基本构造是IF语句。IF语句中编写的代码只有在特定条件为true时才会执行,否则代码将被跳过。让我们用IF语句编写一个小程序:

#!/usr/bin/env bash
# File: simpleif.sh

echo "Start program"

if [[ $1 -eq 4 ]]
then
  echo "You entered $1"
fi

echo "End program"

  首先,这个程序将打印Start program,然后IF语句将检查条件表达式[[ $1 -eq 4 ]]是否为真。只有将4作为脚本的第一个参数时,才是真。如果条件表达式为true,那么它将执行在thenfi之间的代码,否则它将跳过该代码。最后,程序将打印End program。让我们尝试以几种不同的方式运行这个 Bash 程序:

wingsummer@wingsummer-PC ~ → bash simpleif.sh
Start program
End program
wingsummer@wingsummer-PC ~ → bash simpleif.sh 77
Start program
End program
wingsummer@wingsummer-PC ~ → bash simpleif.sh 4
Start program
You entered 4
End program

  直到最后,由于该脚本的第一个参数是44等于4,因此执行了IF语句中的代码。可以将IF语句与ELSE语句配对。ELSE语句仅在IF语句计算的条件表达式为false时运行。让我们创建一个使用ELSE语句的简单程序:

#!/usr/bin/env bash
# File: simpleifelse.sh

echo "Start program"

if [[ $1 -eq 4 ]]
then
  echo "Thanks for entering $1"
else
  echo "You entered: $1, not what I was looking for."
fi

echo "End program"

  我们继续相同的操作:

wingsummer@wingsummer-PC ~ → bash simpleifelse.sh 4
Start program
Thanks for entering 4
End program
wingsummer@wingsummer-PC ~ → bash simpleifelse.sh 3
Start program
You entered: 3, not what I was looking for.
End program

  当第一个参数是4时,条件表达式[[ $1 -eq 4]]true,因此IF语句中的代码运行,而ELSE语句中的代码未运行。当参数改为3时,条件表达式[[ $1 -eq 4]]为为false,因此ELSE语句中的代码运行,IF语句中的代码未运行。
  在IFELSE语句之间,还可以使用ELIF语句。这些语句的行为类似于IF语句,除非它们仅在前面的IFELIF语句都计算值为假,条件表达式时才被计算。让我们使用ELIF创建一个简短的程序:

#!/usr/bin/env bash
# File: simpleelif.sh

if [[ $1 -eq 4 ]]
then
  echo "$1 is my favorite number"
elif [[ $1 -gt 3 ]]
then
  echo "$1 is a great number"
else
  echo "You entered: $1, not what I was looking for."
fi

  我们如法炮制:

wingsummer@wingsummer-PC ~ → bash simpleelif.sh 4
4 is my favorite number
wingsummer@wingsummer-PC ~ → bash simpleelif.sh 5
5 is a great number
wingsummer@wingsummer-PC ~ → bash simpleelif.sh 2
You entered: 2, not what I was looking for.

  由于篇幅,我这里就不细说了。我们还可以组合条件执行、条件表达式和IF/ELIF/ELSE语句,条件执行运算符&&||可用于IFELIF语句。让我们来看一个在IF语句中使用这些运算符的示例:

#!/usr/bin/env bash
# File: condexif.sh

if [[ $1 -gt 3 ]] && [[ $1 -lt 7 ]]
then
  echo "$1 is between 3 and 7"
elif [[ $1 =~ "Jeff" ]] || [[ $1 =~ "Roger" ]] || [[ $1 =~ "Brian" ]]
then
  echo "$1 works in the Data Science Lab"
else
  echo "You entered: $1, not what I was looking for."
fi

  现在,让我们用几个不同的参数来测试这个脚本:

wingsummer@wingsummer-PC ~ → bash condexif.sh 2
You entered: 2, not what I was looking for.
wingsummer@wingsummer-PC ~ → bash condexif.sh 4
4 is between 3 and 7
wingsummer@wingsummer-PC ~ → bash condexif.sh 6
6 is between 3 and 7
wingsummer@wingsummer-PC ~ → bash condexif.sh Jeff
Jeff works in the Data Science Lab
wingsummer@wingsummer-PC ~ → bash condexif.sh Brian
Brian works in the Data Science Lab
wingsummer@wingsummer-PC ~ → bash condexif.sh Sean
You entered: Sean, not what I was looking for.

  条件执行操作符的工作方式与它们在命令行上的工作方式相同。如果整个条件表达式的计算结果等效于true,则执行If语句中的代码,否则将跳过它。
  最后,我们应该注意,IF/ELIF/ELSE语句可以嵌套在其他IF语句中。下面是一个带有嵌套语句的程序的小示例:

#!/usr/bin/env bash
# File: nested.sh

if [[ $1 -gt 3 ]] && [[ $1 -lt 7 ]]
then
  if [[ $1 -eq 4 ]]
  then
    echo "four"
  elif [[ $1 -eq 5 ]]
  then
    echo "five"
  else
    echo "six"
  fi
else
  echo "You entered: $1, not what I was looking for."
fi

  这里就不测试了,通过IF语句,我们可以实现比较强大的脚本。

内容小结

  • 所有 Bash 程序都有退出状态。true的退出状态为0false的退出状态为1
  • 条件执行使用两个运算符:AND &&和 OR ||,您可以使用它们根据退出状态控制执行相应的命令。
  • 条件表达式始终位于双中括号[[]]内。如果包含true断言,则退出状态为0;如果包含false断言,则退出状态为1
  • IF语句计算条件表达式。如果表达式为true,则执行If语句中的代码,否则将跳过它。
  • ELIFELSE语句也有助于控制 Bash 程序的流,IF语句可以嵌套在其他IF语句中。

小试牛刀

  1. 编写一个 Bash 脚本,将字符串作为参数,如果字符串以大写字母开头,则打印大写开头
  2. 编写一个 Bash 脚本,假设输入一个正整数参数。判断如果是偶数,则打印偶数;如果是奇数,则打印奇数
  3. 编写一个包含两个参数的 Bash 脚本。如果两个参数都是纯数字,则打印它们的和,否则只打印两个参数。
  4. 写一个 Bash 脚本,如果今天是星期五,打印今天是星期五。(提示:看一下 date 程序帮助)。
🔒 点击查看答案 🔒
# 1
echo "请输入英文单词"
read -r INPUT
if [[ $INPUT =~ ^[A-Z] ]]
then
    echo "大写开头"
fi

# 2
num=$1
if ((num % 2 == 0)); then
    echo "偶数"
else
    echo "奇数"
fi

#3
num1=$1
num2=$2

if [[ $num1 =~ [[:digit:]] ]] && [[ $num2 =~ [[:digit:]] ]] ;then
echo $((num1+num2))
else
echo "$1 $2"
fi

# 4
day=$(date +%w)
if ((day==5));then
echo "今天是星期五"
fi

拓展

  上面都是比较简单的编写带有条件判断语句脚本的基础知识,当然不能仅仅会IF语句,下面我们对条件判断进行拓展知识:

if 结构

  if是最常用的条件判断结构,只有符合给定条件时,才会执行指定的命令。它的语法如下:

if commands; then
  commands
[elif commands; then
  commands...]
[else
  commands]
fi

  ifthen写在同一行时,需要分号分隔。分号是 Bash 的命令分隔符。它们也可以写成两行,这时不需要分号。除了多行的写法,if结构也可以写成单行。

if true; then echo 'hello world'; fi
if false; then echo "It's true."; fi

test 命令

  if结构的判断条件,一般使用test命令,它有三种形式。

# 写法一
test expression

# 写法二
[ expression ]

# 写法三
[[ expression ]]

  上面三种形式是等价的,但是第三种形式还支持正则判断,前两种不支持。
  上面的expression是一个表达式。这个表达式为真,test命令执行成功,返回值为0;表达式为假,test命令执行失败,返回值为1。注意,第二种和第三种写法,[]与内部的表达式之间必须有空格。下面把test命令的三种形式,用在if结构中,判断一个文件是否存在:

# 写法一
if test -e /tmp/foo.txt ; then
  echo "Found foo.txt"
fi

# 写法二
if [ -e /tmp/foo.txt ] ; then
  echo "Found foo.txt"
fi

# 写法三
if [[ -e /tmp/foo.txt ]] ; then
  echo "Found foo.txt"
fi

判断表达式

  if关键字后面,跟的是一个命令。这个命令可以是test命令,也可以是其他命令。命令的返回值为0表示判断成立,否则表示不成立。因为这些命令主要是为了得到返回值,所以可以视为表达式。常用的判断表达式有下面这些:

文件判断

  以下表达式用来判断文件状态:

  • [ -a file ] :如果file存在,则为true
  • [ -b file ] :如果file存在并且是一个块(设备)文件,则为true
  • [ -c file ] :如果file存在并且是一个字符(设备)文件,则为true
  • [ -d file ] :如果file存在并且是一个目录,则为true
  • [ -e file ] :如果file存在,则为true
  • [ -f file ] :如果file存在并且是一个普通文件,则为true
  • [ -g file ] :如果file存在并且设置了组ID,则为true
  • [ -G file ] :如果file存在并且属于有效的组ID,则为true
  • [ -h file ] :如果 file 存在并且是符号链接,则为true
  • [ -k file ] :如果 file 存在并且设置了它的“sticky bit”,则为true。
  • [ -L file ] :如果file存在并且是一个符号链接,则为true
  • [ -N file ] :如果file存在并且自上次读取后已被修改,则为true
  • [ -O file ] :如果file存在并且属于有效的用户ID,则为true
  • [ -p file ] :如果file存在并且是一个命名管道,则为true
  • [ -r file ] :如果file存在并且可读(当前用户有可读权限),则为true
  • [ -s file ] :如果file存在且其长度大于零,则为true
  • [ -S file ] :如果file存在且是一个网络socket,则为true
  • [ -t fd ] :如果fd是一个文件描述符,并且重定向到终端,则为true。这可以用来判断是否重定向了标准输入/输出/错误。
  • [ -u file ] :如果file存在并且设置了setuid位,则为true
  • [ -w file ] :如果file存在并且可写(当前用户拥有可写权限),则为true
  • [ -x file ] :如果file存在并且可执行(有效用户有执行/搜索权限),则为true
  • [ file1 -nt file2 ] :如果FILE1FILE2的更新时间最近,或者FILE1存在而FILE2不存在,则为true
  • [ file1 -ot file2 ] :如果FILE1FILE2的更新时间更旧,或者FILE2存在而FILE1不存在,则为true
  • [ FILE1 -ef FILE2 ] :如果FILE1FILE2引用相同的设备和inode编号,则为true

  下面是一个比较完整的示例:

#!/bin/bash

FILE=~/.bashrc

if [ -e "$FILE" ]; then
  if [ -f "$FILE" ]; then
    echo "$FILE is a regular file."
  fi
  if [ -d "$FILE" ]; then
    echo "$FILE is a directory."
  fi
  if [ -r "$FILE" ]; then
    echo "$FILE is readable."
  fi
  if [ -w "$FILE" ]; then
    echo "$FILE is writable."
  fi
  if [ -x "$FILE" ]; then
    echo "$FILE is executable/searchable."
  fi
else
  echo "$FILE does not exist"
  exit 1
fi

  上面代码中,$FILE要放在双引号之中,这样可以防止变量$FILE为空,从而出错。因为$FILE如果为空,这时[ -e $FILE ]就变成[ -e ],这会被判断为真。而$FILE放在双引号之中,[ -e "$FILE" ]就变成[ -e "" ],这会被判断为假。

字符串判断

  以下表达式用来判断字符串:

[ string ] :如果string不为空(长度大于0),则判断为真。
[ -n string ] :如果字符串string的长度大于零,则判断为真。
[ -z string ] :如果字符串string的长度为零,则判断为真。
[ string1 = string2 ] :如果string1string2相同,则判断为真。
[ string1 == string2 ] 等同于[ string1 = string2 ]
[ string1 != string2 ] :如果string1string2不相同,则判断为真。
[ string1 '>' string2 ] :如果按照字典顺序string1排列在string2之后,则判断为真。
[ string1 '<' string2 ] :如果按照字典顺序string1排列在string2之前,则判断为真。

  注意:test命令内部的><,必须用引号引起来(或者是用反斜杠转义)。否则,它们会被 shell 解释为重定向操作符。下面是一个示例。

#!/bin/bash

ANSWER=maybe

if [ -z "$ANSWER" ]; then
  echo "There is no answer." >&2
  exit 1
fi
if [ "$ANSWER" = "yes" ]; then
  echo "The answer is YES."
elif [ "$ANSWER" = "no" ]; then
  echo "The answer is NO."
elif [ "$ANSWER" = "maybe" ]; then
  echo "The answer is MAYBE."
else
  echo "The answer is UNKNOWN."
fi

  上面代码中,首先确定$ANSWER字符串是否为空。如果为空,就终止脚本,并把退出状态设为1。注意,这里的echo命令把错误信息There is no answer.重定向到标准错误,这是处理错误信息的常用方法。如果$ANSWER字符串不为空,就判断它的值是否等于yesno或者maybe
  注意,字符串判断时,变量要放在双引号之中,比如[ -n "$COUNT" ],否则变量替换成字符串以后,test命令可能会报错,提示参数过多。另外,如果不放在双引号之中,变量为空时,命令会变成[ -n ],这时会判断为真。如果放在双引号之中,[ -n "" ]就判断为假。

整数判断

  下面的表达式用于判断整数:

[ integer1 -eq integer2 ] :如果integer1等于integer2,则为true
[ integer1 -ne integer2 ] :如果integer1不等于integer2,则为true
[ integer1 -le integer2 ] :如果integer1小于或等于integer2,则为true
[ integer1 -lt integer2 ] :如果integer1小于integer2,则为true
[ integer1 -ge integer2 ] :如果integer1大于或等于integer2,则为true
[ integer1 -gt integer2 ] :如果integer1大于integer2,则为true

  下面是一个用法的例子:

#!/bin/bash

INT=-5

if [ -z "$INT" ]; then
  echo "INT is empty." >&2
  exit 1
fi
if [ $INT -eq 0 ]; then
  echo "INT is zero."
else
  if [ $INT -lt 0 ]; then
    echo "INT is negative."
  else
    echo "INT is positive."
  fi
  if [ $((INT % 2)) -eq 0 ]; then
    echo "INT is even."
  else
    echo "INT is odd."
  fi
fi

  上面例子中,先判断变量$INT是否为空,然后判断是否为0,接着判断正负,最后通过求余数判断奇偶。

算术判断

  Bash 还提供了(())作为算术条件,进行算术运算的判断:

if ((3 > 2)); then
  echo "true"
fi

  上面代码执行后,会打印出true。注意,算术判断不需要使用test命令,而是直接使用(())结构。这个结构的返回值,决定了判断的真假。如果算术计算的结果是非零值,则表示判断成立。这一点跟命令的返回值正好相反,需要小心。

wingsummer@wingsummer-PC ~ →  if ((1)); then echo "It is true."; fi
It is true.
wingsummer@wingsummer-PC ~ →  if ((0)); then echo "It is true."; else echo "it is false."; fi
It is false.

  上面例子中,((1))表示判断成立,((0))表示判断不成立。算术条件(())也可以用于变量赋值:

wingsummer@wingsummer-PC ~ →  if (( foo = 5 ));then echo "foo is $foo"; fi
foo is 5

  上面例子中,(( foo = 5 ))完成了两件事情。首先把5赋值给变量foo,然后根据返回值5,判断条件为真。注意,赋值语句返回等号右边的值,如果返回的是0,则判断为假。

wingsummer@wingsummer-PC ~ → if (( foo = 0 ));then echo "It is true.";else echo "It is false."; fi
It is false.

  下面是用算术条件改写的数值判断脚本:

#!/bin/bash

INT=-5

if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
  if ((INT == 0)); then
    echo "INT is zero."
  else
    if ((INT < 0)); then
      echo "INT is negative."
    else
      echo "INT is positive."
    fi
    if (( ((INT % 2)) == 0 )); then
      echo "INT is even."
    else
      echo "INT is odd."
    fi
  fi
else
  echo "INT is not an integer." >&2
  exit 1
fi

  只要是算术表达式,都能用于(())语法。

case 结构

  case结构用于多值判断,可以为每个值指定对应的命令,跟包含多个elifif结构等价,但是语义更好。它的语法如下:

case expression in
  pattern )
    commands ;;
  pattern )
    commands ;;
  ...
esac

  上面代码中,expression是一个表达式,pattern是表达式的值或者一个模式,可以有多条,用来匹配多个值,每条以两个分号;;结尾。

#!/bin/bash

echo -n "输入一个1到3之间的数字(包含两端)> "
read character
case $character in
  1 ) echo 1
    ;;
  2 ) echo 2
    ;;
  3 ) echo 3
    ;;
  * ) echo 输入不符合要求
esac

  上面例子中,最后一条匹配语句的模式是*,这个通配符可以匹配其他字符和没有输入字符的情况,类似ifelse部分。下面是另一个例子:

#!/bin/bash

OS=$(uname -s)

case "$OS" in
  FreeBSD) echo "This is FreeBSD" ;;
  Darwin) echo "This is Mac OSX" ;;
  AIX) echo "This is AIX" ;;
  Minix) echo "This is Minix" ;;
  Linux) echo "This is Linux" ;;
  *) echo "Failed to identify this OS" ;;
esac

  上面的例子判断当前是什么操作系统。case的匹配模式还可以使用各种通配符,下面是一些例子:

  • a ) :匹配a
  • a|b ):匹配ab
  • [[:alpha:]] ) :匹配单个字母。
  • ??? ) :匹配3个字符的单词。
  • *.txt ) :匹配.txt结尾。
  • * ) :匹配任意输入,通过作为case结构的最后一个模式。

  然后我们看一下示例代码:

#!/bin/bash

echo -n "输入一个字母或数字 > "
read character
case $character in
  [[:lower:]] | [[:upper:]] ) echo "输入了字母 $character"
                              ;;
  [0-9] )                     echo "输入了数字 $character"
                              ;;
  * )                         echo "输入不符合要求"
esac

  上面例子中,使用通配符[[:lower:]] | [[:upper:]]匹配字母,[0-9]匹配数字。Bash 4.0之前,case结构只能匹配一个条件,然后就会退出case结构。Bash 4.0之后,允许匹配多个条件,这时可以用;;&终止每个条件块:

#!/bin/bash
# test.sh

read -n 1 -p "Type a character > "
echo
case $REPLY in
  [[:upper:]])    echo "'$REPLY' is upper case." ;;&
  [[:lower:]])    echo "'$REPLY' is lower case." ;;&
  [[:alpha:]])    echo "'$REPLY' is alphabetic." ;;&
  [[:digit:]])    echo "'$REPLY' is a digit." ;;&
  [[:graph:]])    echo "'$REPLY' is a visible character." ;;&
  [[:punct:]])    echo "'$REPLY' is a punctuation symbol." ;;&
  [[:space:]])    echo "'$REPLY' is a whitespace character." ;;&
  [[:xdigit:]])   echo "'$REPLY' is a hexadecimal digit." ;;&
esac

  执行上面的脚本,会得到下面的结果。

wingsummer@wingsummer-PC ~ → test.sh
Type a character > a
'a' is lower case.
'a' is alphabetic.
'a' is a visible character.
'a' is a hexadecimal digit.

  可以看到条件语句结尾添加了;;&以后,在匹配一个条件之后,并没有退出case结构,而是继续判断下一个条件。

小结

  由于本篇篇幅原因,暂时介绍这些,剩下的重要的知识点将会在下一篇继续。

下一篇

  羽夏 Bash 简明教程(下)

posted @ 2022-05-13 16:33  寂静的羽夏  阅读(1467)  评论(0编辑  收藏  举报