Linux Shell是一种基本功,由于怪异的语法加之较差的可读性,现在逐渐被Python等脚本代替。既然是基本功,那就需要掌握,毕竟学习 Shell 脚本的过程中,还是能了解到很多Linux系统的内容。Linux 脚本大师不是人人都可以达到的,但是用一些简单的Shell实现一些常见的基本功能还是很有必要的。
一、初识Shell
1.1 什么是 Shell
Shell 是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务,Windows Explorer 是一个典型的图形界面 Shell。Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。GUI 提供了一种图形化的用户接口,使用起来非常简便易学;而 Shell 则为用户提供了一种命令行的接口,接收用户的键盘输入,并分析和执行输入字符串中的命令,然后给用户返回执行结果,使用起来可能会复杂一些,但是由于占用的资源少,而且在操作熟练以后可能会提高工作效率,而且具有批处理的功能,因此在某些应用场合还非常流行。
1.2 什么是Shell脚本
Shell 脚本(Shell Script),是一种为 Shell 编写的脚本程序,可以被重复使用。业界所说的 Shell 通常都是指 Shell 脚本,但小伙伴儿们要知道,Shell 和 Shell Script 是两个不同的概念。Shell Script 与Windows 下的批处理相似,就是用各类命令预先放入到一个文件中,方便一次性执行的一个程序文件。当 Shell 接收用户输入的命令(脚本名),并进行分析。如果文件被标记为可执行但不是被编译过的程序, Shell 就认为它是一个 Shell Script。 Shell 将读取其中的内容,并加以解释执行。所以,从用户的观点看,执行 Shell 脚本的方式与执行一般的可执行文件的方式相似,大大简化了管理员的操作步骤,提高工作效率,减少人为的干预。
因此,用户开发的 Shell 脚本可以驻留在命令搜索路径的目录之下(通常是/bin、/usr/bin等),像普通命令一样使用。这样,也就开发出自己的新命令。如果打算反复使用编好的 Shell 脚本,那么采用这种方式就比较方便。
1.3 Shell 语法初识
先来介绍一个 Shell 程序的基本结构,以 Hello, World 为例。打开文本编辑器(可以使用 vi/vim 命令来创建文件),新建一个文件 test.sh
。输入一些代码,如下所示:
#!/bin/bash
echo "Hello World !"
#!
是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行。Unix/Linux上常见的Shell脚本解释器有bash、sh、csh、ksh等,习惯上把它们称作一种Shell。以 # 开头的行就是注释,会被解释器忽略。sh里没有多行注释,可以通过每一行加一个 # 号设置多行注释。
#!/bin/bash
# --------
# 这是注释块
# --------
1.4 Shell 脚本执行
当 Shell 脚本以非交互式的方式运行时,它会先查找环境变量ENV,该变量指定了一个环境文件(比如.bashrc、.bash_profile、/etc/profile等),然后从该环境变量文件开始回字形,当读取了ENV文件后,shell才开始执行shell脚本中的内容。
Shell脚本的执行通常有以下三种方式:
- bash test.sh 或 sh test.sh
- ./test.sh(当前路径执行脚本)
- source test.sh 或. test.sh
第一种方式是脚本文件本身没有可执行权限时常使用的方法,推荐使用bash执行;第二种方式需要将脚本文件的权限改为可执行(chmod u+x test.sh),然后通过脚本路径就可以执行脚本了;第三种方式是使用source或者"."点号加载sehll脚本文件,该方式可以将脚本中变量或者函数导出到当前环境中进行调用。
想要让编写的脚本文件执行,还需要加上可执行权限,
chmod +x xxxx.sh
。
二、基本使用
2.1 Shell 变量
变量是任何一种编程语言都必不可少的组成部分,变量用来存放各种数据。脚本语言在定义变量时通常不需要指明类型,直接赋值就可以,shell 变量也遵循这个规则。
2.1.1 定义变量
Shell 变量的命名规范和大部分编程语言都一样,变量名由数字、字母、下划线组成,且必须以字母或者下划线开头,不能使用 shell 里的关键字。变量名和等号之间不能有空格,但可以使用下划线 _。Shell 支持以下三种定义变量的方式:
variableName=value
variableName='value'
variableName="value"
Shell 中使用单引号或双引号是有区别的,具体如下:
- 在单引号中所有的字符包括特殊字符( $、‘’、` 和 \ )都将解释成字符本身而成为普通字符,任何字符都会原样输出,这种方式比较适合定义显示纯字符串的情况,即不希望解析变量、命令等的场景。
- 在双引号中,除了$ ‘’ ` 和 \ 以外所有的字符都解释成字符本身,输出时会先解析里面的变量和命令,而不是把双引号中的变量名和命令原样输出。这种方式比较适合字符串中附带有变量和命令并且想将其解析后再输出的变量定义
如果变量的内容是数字,那么可以不加引号;如果真的需要原样输出就加单引号;其他没有特别要求的字符串等最好都加上双引号,定义变量时加双引号是最常见的使用场景。
在 Shell 中,每一个变量的值都是字符串,无论你给变量赋值时有没有使用引号,值都会以字符串的形式存储。这意味着,Shell 在默认情况下不会区分变量类型,即使你将整数和小数赋值给变量,它们也会被视为字符串,这一点和大部分的编程语言不同。例如在 Java 中,变量分为整数、字符串、布尔等多种类型。
2.1.2 使用变量
使用一个定义过的变量,只要在变量名前面加**$**即可。例如,下面的脚本将访问一个定义的变量名,将它打印在标准输出:
your_name="独泪了无痕"
echo $your_name
echo ${your_name}
注意:变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界。个人推荐给所有变量加上花括号,这是个好的编程习惯。
在实际使用中,有的时候由于业务需求,需要我们对已经定义的变量进行重新赋值。在Shell中,已定义的变量,可以被重新定义,其方法与定义变量一样。
your_name="tom"
echo $your_name
your_name="alibaba"
echo $your_name
2.1.3 特殊变量
前面已经讲到,变量名只能包含数字、字母和下划线,因为某些包含其他字符的变量有特殊含义,这样的变量被称为特殊变量。
变量 | 含义 |
---|---|
$0 | 当前脚本的文件名 |
$n | 传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是$1 ,第二个参数是$2 。 |
$# | 传递给脚本或函数的参数个数。 |
$* | 传递给脚本或函数的所有参数。 |
$@ | 传递给脚本或函数的所有参数。被双引号(“ “)包含时,与 $* 稍有不同 |
$? | 上个命令的退出状态,或函数的返回值。 |
$$ | 当前Shell进程ID。对于 Shell 脚本,就是这些脚本所在的进程ID。 |
$*
和$@
都表示传递给函数或脚本的所有参数,不被双引号(“ “)包含时,都以"$1" "$2" … "$n"
的形式输出所有参数。是当它们被双引号(“ “)包含时,”$*
“ 会将所有的参数作为一个整体,以”$1 $2 … $n
“的形式输出所有参数;”$@
“ 会将各个参数分开,以"$1" "$2" … "$n"
的形式输出所有参数。
新建一个文件test.sh
,内容如下所示,执行 ./test.sh "a" "b" "c" "d"
#!/bin/bash
echo "\$*=" $*
echo "\"\$*\"=" "$*"
echo "\$@=" $@
echo "\"\$@\"=" "$@"
echo "输出每个参数: \$*"
for var in $*
do
echo "$var"
done
echo "输出每个参数: \$@"
for var in $@
do
echo "$var"
done
echo "输出每个参数: \"\$*\""
for var in "$*"
do
echo "$var"
done
echo "输出每个参数: \"\$@\""
for var in "$@"
do
echo "$var"
done
2.2 Shell 字符串
2.2.1 字符串详解
字符串是shell编程中最常用最有用的数据类型(除了数字和字符串,也没啥其它类型好用了),简单来说,就是一系列字符的组合。
str1=www.baidu.com
str2="shell script"
str3='shell详解'
在 shell 中获取字符串长度很简单,具体方法如下:
${#string_name}
string_name 表示字符串名字。
下面是具体的演示:
#!/bin/bash
str="https://www.baidu.com"
echo ${#str}
2.2.2 字符串拼接
在脚本语言中,字符串的拼接(也称字符串连接或者字符串合并)往往都非常简单,在 Shell 中不需要使用任何运算符,将两个字符串并排放在一起就能实现拼接,非常简单粗暴,请看下面的例子:
#!/bin/bash
name="独泪了无痕"
email="duleilewuhen@sina.com"
# 中间不能有空格
echo $name$email
# 如果被双引号包围,那么中间可以有空格
echo $name $email
# 中间可以出现别的字符串
echo $name": "$email
echo "$name: $email"
2.2.3 字符串截取
Shell 截取字符串通常有两种方式:从指定位置开始截取和从指定字符(子字符串)开始截取。
-
从指定位置开始截取:这种方式需要两个参数:指定起始位置、截取长度,才能最终确定要截取的字符串。如果想从字符串指定位置开始截取,那么截取字符串的具体格式如下:
表达式 含义 ${str:start} 从 str 字符串的左边第 start 位置开始截取,直到最后(起始数字是 0) ${str:start:length} 从 str 字符串的左边第 start 位置开始,向右截取 length 个字符(起始数字是0) ${str:0-start} 从 str 字符串的右边第 start 位置开始截取,直到最后(起始数字是 1) ${str:0-start:length} 从 str 字符串的左边第 start 位置开始,向左截取 length 个字符(起始数字是1) #!/bin/bash str='i love you' # 从第1个截取到末尾。注意从0开始 echo ${str:1} # 从第2个截取2个。 echo ${str:2:2} # 全部截取 echo ${str:0} # 负数无效,视为0 echo ${str:-3}
-
从指定字符开始截取:这种截取方式无法指定字符串长度,只能从指定字符截取到字符串末尾。shell 可以截取指定字符右边的所有字符,也可以截取左边的所有字符。使用#号可以截取指定字符或字符串右边的所有字符,使用%号可以截取指定字符左边的所有字符,具体格式如下:
表达式 含义 *${str#chars} 从 str 字符串第一次出现chars的位置开始,截取 *chars 右边的所有字符 *${str##chars} 从 str 字符串最后一次出现chars的位置开始,截取 *chars 右边的所有字符 ${str%substring} 从 str 字符串第一次出现chars的位置开始,截取 *chars 左边的所有字符 ${str%%substring} 从 str 字符串最后一次出现chars的位置开始,截取 *chars 左边的所有字符 #!/bin/bash str="substringbig" # 从开头截断匹配的子串 echo ${str#s*b} echo ${str##s*b} # 从末尾截断匹配的子串 echo ${str%b*g} echo ${str%%b*g}
2.2.4 字符串替换
Shell 遇到包含一个或多个特殊字符的表达式时,就会执行替换。
表达式 | 含义 |
---|---|
${str/substring/replacement} | 使用 replacement来代替第一个匹配的 substring |
${str//substring/replacement} | 使用 replacement 代替所有匹配的 substring |
${str/#substring/replacement} | 如果字符串str以 replacement 开头,则用 replacement 替换它 |
${str/%substring/replacement} | 如果字符串str以 replacement 结尾,则用 replacement 替换它 |
#!/bin/bash
str="substringbig"
# 替换第一个字串
echo ${str/string/sss}
# 替换所有子串
echo ${str//b/s}
# 替换匹配开头的子串
echo ${str/#sub/sss}
# 替换结尾的子串
echo ${str/%big/sss}
2.2.5 字符串查找
表达式 | 含义 |
---|---|
expr index ${str} $substring | 在字符串 str上匹配 substring 中字符最早第一次出现的位置(从左到右,从1开始),匹配不到,则返回0。 |
这个方法让我想起来了js的indexOf,各种语言对字符串的操作方法大方向都差不多,如果有语言基础的话,学习shell会很快的。
#!/bin/bash
str='i love you'
expr index "${str}" l
# echo `expr index "${str}" l`
# 从字符串中位置1开始截取6个字符。索引是从0开始的。
expr substr "$str" 1 6
# echo `expr substr "$str" 1 6`
2.3 Shell 数组
和其他编程语言一样,Shell 也支持数组。数组是若干数据的集合,其中的每一份数据都称为元素。在 Shell 中,用括号来表示数组,数组元素之间用 空格
符号分割开,初始化时不需要限制数组的大小,理论上可以存放无限量的数据。定义数组的语法格式如下,需要注意,赋值号 = 两边不能有空格,必须紧挨着数组名和数组元素。而且 Shell 是弱类型的,它并不要求所有数组元素的类型必须相同。
# 格式一
array_name=(ele1 ele2 ele3 ... elen)
# 格式二:使用下标来定义数组
array_name[0]=value0
array_name[1]=value1
array_name[2]=value2
与大部分编程语言类似,获取数组中的元素要使用下标 [ ],下标可以是一个整数,也可以是一个结果为整数的表达式,其值应大于或等于0。读取数组元素值的一般格式是:
${array_name[index]}
在这里,array_name 是数组的名称,index是索引进行访问的值。下面是一个简单的例子:
#!/bin/bash
array_name=(A B C D)
# 读取下标为2的元素
echo ${array_name[2]}
# 使用 * 符号可以获取数组中的所有元素
echo ${array_name[*]}
# 使用 @ 符号可以获取数组中的所有元素
echo ${array_name[@]}
获取数组长度的方法与获取字符串长度的方法相同,例如:
#!/bin/bash
array_name=(a1,a2,a3)
# 取得数组元素的个数
echo ${#array_name[*]}
# 获取数组长度
echo ${#array_name[@]}
# 取得数组单个元素的长度
echo ${#array_name[1]}
2.3 Shell 运算符
Shell 和其他编程语言一样,支持多种运算符,包括:算数运算符、关系运算符、布尔运算符、字符串运算符等。
2.3.1 算术运算符
下表列出了常用的算术运算符。注意:原生bash不支持简单的数学运算,但是可以通过其他命令来实现,例如 awk 和 expr,expr 最常用。expr 是一款表达式计算工具,使用它能完成表达式的求值操作。
运算符 | 说明 | 举例 |
---|---|---|
+ | 加法 | expr $a + $b |
- | 减法 | expr $a - $b |
* | 乘法 | expr $a \* $b |
/ | 除法 | expr $b / $a |
% | 取余 | expr $b % $a |
= | 赋值 | a=$b |
== | 相等。用于比较两个数字,相同则返回 true。 | [ $a == $b ] |
!= | 不相等。用于比较两个数字,不相同则返回 true。 | [ $a != $b ] |
注意:表达式要放在方括号之间,并且要有空格,例如: [
$a==$b
] 是错误的,必须写成 [$a == $b
]。
下面是一个例子,使用所有的算术运算符:
#!/bin/sh
a=10
b=20
val=`expr $a + $b`
echo "a + b : $val"
val=`expr $a - $b`
echo "a - b : $val"
val=`expr $a * $b`
echo "a * b : $val"
val=`expr $b / $a`
echo "b / a : $val"
val=`expr $b % $a`
echo "b % a : $val"
if [ $a == $b ]
then
echo "a 等于 b"
fi
if [ $a != $b ]
then
echo "a 不等于 b"
fi
2.3.2 关系运算符
关系运算符只支持数字,不支持字符串,除非字符串的值是数字。下表列出了常用的关系运算符:
运算符 | 说明 | 举例 |
---|---|---|
-eq | 检测两个数是否相等,相等返回 true。 | [ $a -eq $b ] |
-ne | 检测两个数是否不相等,不相等返回 true。 | [ $a -ne $b ] |
-gt | 检测左边的数是否大于右边的,如果是,则返回 true。 | [ $a -gt $b ] |
-lt | 检测左边的数是否小于右边的,如果是,则返回 true。 | [ $a -lt $b ] |
-ge | 检测左边的数是否大于等于右边的,如果是,则返回 true。 | [ $a -ge $b ] |
-le | 检测左边的数是否小于等于右边的,如果是,则返回 true。 | [ $a -le $b ] |
下面是一个例子,使用所有的关系运算符:
#!/bin/sh
a=10
b=20
if [ $a -eq $b ]
then
echo "$a -eq $b : a 等于 b"
else
echo "$a -eq $b: a 不等于 b"
fi
if [ $a -ne $b ]
then
echo "$a -ne $b: a 不等于 b"
else
echo "$a -ne $b : a 等于 b"
fi
if [ $a -gt $b ]
then
echo "$a -gt $b: a 大于 b"
else
echo "$a -gt $b: a 不大于 b"
fi
if [ $a -lt $b ]
then
echo "$a -lt $b: a 小于 b"
else
echo "$a -lt $b: a 不小于 b"
fi
if [ $a -ge $b ]
then
echo "$a -ge $b: a 大于或等于 b"
else
echo "$a -ge $b: a 不大于或等于 b"
fi
if [ $a -le $b ]
then
echo "$a -le $b: a 小于或等于 b"
else
echo "$a -le $b: a 不小于或等于 b"
fi
2.3.3 布尔运算符
下表列出了常用的布尔运算符,假定变量 a 为 10,变量 b 为 20:
运算符 | 说明 | 举例 |
---|---|---|
! | 非运算,表达式为 true 则返回 false,否则返回 true | [ ! ] |
-o | 或运算(or),有一个表达式为 true 则返回 true | [ $a -lt 20 -o $b -gt 100 ] |
小 | 与运算(and),两个表达式都为 true 才返回 true | [ $a -lt 20 -a $b -gt 100 ] |
下面是一个例子,使用所有的布尔运算符:
#!/bin/sh
a=10
b=20
if [ $a != $b ]
then
echo "$a != $b : a 不等于 b"
else
echo "$a != $b: a 等于 b"
fi
if [ $a -lt 100 -a $b -gt 15 ]
then
echo "$a -lt 100 -a $b -gt 15 : returns true"
else
echo "$a -lt 100 -a $b -gt 15 : returns false"
fi
if [ $a -lt 100 -o $b -gt 100 ]
then
echo "$a -lt 100 -o $b -gt 100 : returns true"
else
echo "$a -lt 100 -o $b -gt 100 : returns false"
fi
if [ $a -lt 5 -o $b -gt 100 ]
then
echo "$a -lt 100 -o $b -gt 100 : returns true"
else
echo "$a -lt 100 -o $b -gt 100 : returns false"
fi
2.3.4 逻辑运算符
以下介绍 Shell 的逻辑运算符,假定变量 a 为 10,变量 b 为 20:
运算符 | 说明 | 举例 |
---|---|---|
&& | 逻辑的 AND | [ $a -lt 100 && $b -gt 100 ] 返回 false |
**` | `** |
2.3.5 字符串运算符
下表列出了常用的字符串运算符,假定变量 a 为 “abc”,变量 b 为 “efg”:
运算符 | 说明 | 举例 |
---|---|---|
= | 检测两个字符串是否相等,相等返回 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。 |
下面是一个例子,它使用的所有字符串运算:
#!/bin/sh
a="abc"
b="efg"
if [ $a = $b ]
then
echo "$a = $b : a is equal to b"
else
echo "$a = $b: a is not equal to b"
fi
if [ $a != $b ]
then
echo "$a != $b : a is not equal to b"
else
echo "$a != $b: a is equal to b"
fi
if [ -z $a ]
then
echo "-z $a : string length is zero"
else
echo "-z $a : string length is not zero"
fi
if [ -n $a ]
then
echo "-n $a : string length is not zero"
else
echo "-n $a : string length is zero"
fi
if [ $a ]
then
echo "$a : string is not empty"
else
echo "$a : string is empty"
fi
2.3.6 文件操作
在日常学习和工作中,总是在不断地和各种文件打交道,这些文件包括普通文本文件,可以执行的程序,带有控制字符的文档、存放各种文件的目录、网络套接字文件、设备文件等。这些文件又具有诸如属主、大小、创建和修改日期等各种属性。文件对应文件系统的一些数据块,对应磁盘等存储设备的一片连续空间,对应于显示设备却是一些具有不同形状的字符集。
运算符 | 说明 |
---|---|
-b 文件名 | 判断文件是否是块设备文件,如果是,则返回 true |
-c 文件名 | 判断文件是否存在且为字符型特殊文件,如果是,则返回 true |
-d 文件名 | 判断文件是否存在且是目录,如果是,则返回 true |
-f 文件名 | 判断文件是否存在且是普通文件,如果是,则返回 true |
-r 文件名 | 判断文件是否存在且可读,如果是,则返回 true |
-w 文件名 | 判断文件是否存在且可写,如果是,则返回 true |
-x 文件名 | 判断文件是否存在且可执行,如果是,则返回 true |
-s 文件名 | 判断文件是否为空(文件大小是否大于0),不为空返回 true |
-e 文件名 | 判断文件(包括目录)是否存在,如果是,则返回 true |
下面是一个例子,它使用的所有文件测试操作:
#!/bin/sh
file="/var/www/yiibai/unix/test.sh"
if [ -r $file ]
then
echo "File has read access"
else
echo "File does not have read access"
fi
if [ -w $file ]
then
echo "File has write permission"
else
echo "File does not have write permission"
fi
if [ -x $file ]
then
echo "File has execute permission"
else
echo "File does not have execute permission"
fi
if [ -f $file ]
then
echo "File is an ordinary file"
else
echo "This is sepcial file"
fi
if [ -d $file ]
then
echo "File is a directory"
else
echo "This is not a directory"
fi
if [ -s $file ]
then
echo "File size is zero"
else
echo "File size is not zero"
fi
if [ -e $file ]
then
echo "File exists"
else
echo "File does not exist"
fi
2.4 Shell 命令
2.4.1 echo
echo 是 Shell 的一个内部指令,用于字符串的输出。命令格式:
echo string
可以使用echo实现更复杂的输出格式控制,例如:
#!/bin/bash
# 显示普通字符串
echo "It is a test"
# 显示转义字符
echo "\"It is a test\""
# 显示换行
echo -e "OK! \n" # -e 开启转义
echo "It is a test"
# 显示不换行
echo -e "OK! \c" # -e 开启转义 \c 不换行
echo "It is a test"
注意:echo后单引号和双引号作用是不同的。单引号不能转义里面的字符。双引号可有可无,单引号主要用在原样输出中。如果想要显示命令执行结果,那么就需要使用的是反引号 `, 而不是单引号 '。
2.4.2 printf
printf
命令用于格式化输出, 是echo
命令的增强版。它是C语言printf()
库函数的一个有限的变形,并且在语法上有些不同。默认的 printf 不会像 echo 自动添加换行符,必须显式添加 \n。如同 echo
命令,printf
命令也可以输出简单的字符串:
printf "Hello World!\n"
既然在上面提到 printf
是用于格式化输出的,那就不仅仅是输出字符串,当然还是有更复杂的使用的,先来看下printf 命令的语法:
# format-string: 为格式控制字符串
# arguments: 为参数列表,使用空格分隔,不用逗号
printf format-string [arguments...]
代码示例如下:
#!/bin/sh
# 双引号
printf "%d %s\n" 10 "abc"
# 单引号与双引号效果一样
printf '%d %s\n' 10 "abc"
# 没有引号也可以输出
printf %s abc
# 格式只指定了一个参数,但多出的参数仍然会按照该格式输出,format-string 被重用
printf %s a b c
printf "\n"
printf "%s\n" a b c
# 如果没有 arguments,那么 %s 用NULL代替,%d 用 0 代替
printf "%s and %d \n"
其中:
- %s %c %d %f 都是格式替代符,%s 输出一个字符串,%d 整型输出,%c 输出一个字符,%f 输出实数,以小数形式输出。
- %-10s 指一个宽度为 10 个字符(- 表示左对齐,没有则表示右对齐),任何字符都会被显示在 10 个字符宽的字符内,如果不足则自动以空格填充,超过也会将内容全部显示出来。
- %-4.2f 指格式化为小数,其中 .2 指保留2位小数。
2.4.3 test
test 是 Shell 内置命令,命令用于检查某个条件是否成立。test 命令通常和 if 语句一起使用,它有很多选项,可以进行数值、字符串和文件三个方面的判断。
- 数值测试
参数 | 说明 |
---|---|
-eq | 等于则为真 |
-ne | 不等于则为真 |
-gt | 大于则为真 |
-ge | 大于等于则为真 |
-lt | 小于则为真 |
-le | 小于等于则为真 |
num1=100
num2=100
if test $[num1] -eq $[num2]
then
echo '两个数相等!'
else
echo '两个数不相等!'
fi
- 字符串测试
参数 | 说明 |
---|---|
= | 等于则为真 |
!= | 不相等则为真 |
-z 字符串 | 字符串的长度为零则为真 |
-n 字符串 | 字符串的长度不为零则为真 |
num1="ru1noob"
num2="runoob"
if test $num1 = $num2
then
echo '两个字符串相等!'
else
echo '两个字符串不相等!'
fi
2.4.3 expr
expr是 Shell 中对字符串处理最常用的命令,
格式 | 说明 |
---|---|
expr length $str | 获取字符串长度 |
expr index $str $substring | str 中匹配不到 substring 中的任何字符,返回0 str 中能匹配到 substring 中的所有字符,返回首字符位置 |
expr match $str $substring | 匹配成功返回substring的长度,匹配不成功返回0。支持使用使用正则表达式 |
expr substr $string $position $length | 截取指定开始位置、指定长度的字符串 |
2.4.5 source
source 就是让其他shell脚本识别该变量,换言之就是让文件生效。source命令用法如下:
source FileName
注:该命令通常用命令“.”来替代。如:source .bash_rc 与 . .bash_rc 是等效的。
source在当前bash环境下执行命令,而scripts是启动一个子shell来执行命令。这样如果把设置环境变量(或alias等等)的命令写进scripts中,就只会影响子shell,无法改变当前的bash,所以通过文件(命令列)设置环境变量时,要用source 命令。
2.5 Shell 条件语句
在编写shell脚本,有可能是一种情况,当你需要采取一个路径两条路径。所以,需要利用条件语句,让程序作出正确的决策和执行正确的动作。
2.5.1 条件判断:if语句
if 语句通过关系运算符判断表达式的真假来决定执行哪个分支。Shell 有三种 if … else 语句:
if...fi
语句的基本控制语句,它允许Shell有条件作出决定并执行语句。其语法格式如下:
if [ condition ]
then
statement
fi
如果 condition 是true,给定 statement 被执行。如果 condition 为false ,则没有语句将不会被执行。大部分的时候,会使用比较操作符决策。注意:
condition
和方括号([ ])之间必须有空格,这个空格是强制性的,否则会有语法错误。
if...else...fi
语句是控制语句,可以用来从在两个选择之间选择一个选项。其语法格式如下:
if [ condition ]
then
statement1
else
statement2
fi
如果 condition 的结果值是true的,给定 statement1 被执行。如果表达式为 false,则语句将不会被执行,而是执行statement2。
if else-if else
语句是一个多级别的控制语句,形式出几个条件,允许 Shell 作出正确的决定。
if [ condition1 ]
then
command1
elif [ condition2 ]
then
command2
else
commandN
fi
这段代码没有什么特别的,这仅仅是一个系列,if 语句每一个语句else子句的一部分。下面语句是执行的基础上的真实情况,如果条件不为 ture,则执行else块。
#!/bin/sh
a=10
b=20
if [ $a == $b ]
then
echo "a is equal to b"
elif [ $a -gt $b ]
then
echo "a is greater than b"
elif [ $a -lt $b ]
then
echo "a is less than b"
else
echo "None of the condition met"
fi
2.5.2 分支控制:case语句
虽然可以使用多个 if… elif 语句执行多分支,然而,这并不总是最佳的解决方案,尤其是当所有的分支依赖于一个单一的变量的值,case…esac 语句处理正是由于这个情况。与其他语言中的 switch … case 语句类似,是一种多分支选择结构,每个 case 分支用右圆括号开始,用两个分号 ;; 表示 break,即执行结束,跳出整个 case … esac 语句,esac(就是 case 反过来)作为结束标记。可以用 case 语句匹配一个值与一个模式,如果匹配成功,执行相匹配的命令。如果没有匹配,默认情况下会被使用。case…esac 语句基本语法格式如下:
case 值 in
模式1)
command1
command2
...
commandN
;;
模式2)
command1
command2
...
commandN
;;
esac
case 取值后面必须为单词 in,每一模式必须以右括号结束。取值可以为变量或常数,匹配发现取值符合某一模式后,其间所有命令开始执行直至 ;;
,与其他语言中的 break
类似,意思是跳到整个 case
语句的最后。取值将检测匹配的每一个模式,一旦模式匹配,则执行完匹配模式相应命令后不再继续其他模式。如果没有找到匹配,声明退出的情况下不执行任何动作。使用星号 *
捕获该值,再执行后面的命令。
echo '输入 1 到 4 之间的数字:'
echo '你输入的数字为:'
read aNum
case $aNum in
1) echo '你选择了 1'
;;
2) echo '你选择了 2'
;;
3) echo '你选择了 3'
;;
4) echo '你选择了 4'
;;
*) echo '你没有输入 1 到 4 之间的数字'
;;
esac
2.6 Shell 循环语句
2.6.1 for 循环
与其他编程语言类似,Shell支持for循环,for循环格式为:
# 形式一:
for ((初始值;限制值;执行步阶))
do
程序段
done
# 形式二:
for var in item1 item2 ... itemN
do
程序段
done
var是一个变量,item1 到 itemN 是由空格分隔的字符列表。每次for 循环的执行,变量var的值被设置为下一个单词的列表中的字:item1 到 itemN 。
下面是一个简单的例子,它使用for循环扫描当前文件:
#!/bin/bash
for fileName in `ls`
do
if [ -d $fileName ]
then
echo "${fileName}是文件夹"
elif [ -f $fileName ]; then
echo "${fileName}是普通文件"
fi
done
2.6.2 while 语句
while 循环是 Shell 脚本中最简单的一种循环,用于不断执行一系列命令,直到某些条件发生。只要while后面的条件满足,就一直执行do里面的代码块;当条件不满足时,就退出 while 循环。其语法格式为:
while [ condition ]
do
程序段
done
如果 condition 成立的时候进入 while 循环,直到 condition 不成立时退出。
下面是一个简单的例子,使用while循环显示数字0到9:
#!/bin/sh
a=0
while [ $a -lt 10 ]
do
echo $a
a=`expr $a + 1`
done
2.6.3 break
在循环过程中,有时候需要在未达到循环结束条件时强制跳出循环,Shell使用break命令来终止执行后面的所有循环。下面是一个简单的例子,只要一变为5循环将终止:
!/bin/sh
a=0
while [ $a -lt 10 ]
do
echo $a
if [ $a -eq 5 ]
then
break
fi
a=`expr $a + 1`
done
在嵌套循环中,break 命令后面还可以跟一个整数,表示跳出第几层循环。
2.6.4 continue
continue语句break命令类似,只有一点差别,它不会跳出所有循环,仅仅会导致当前迭代的循环退出。
#!/bin/sh
NUMS="1 2 3 4 5 6 7"
for NUM in $NUMS
do
Q=`expr $NUM % 2`
if [ $Q -eq 0 ]
then
echo "数字是偶数!"
continue
fi
echo "找到奇数:" + ${NUM}
done
2.7 Shell 函数
2.7.1 函数定义
函数的本质是一段可以重复使用的脚本代码,可以将一个复杂功能划分成若干模块,让程序结构更加清晰,代码重复利用率更高。像其他编程语言一样,Shell 也支持函数。Shell 函数在使用前必须定义,必须将函数放在脚本开始部分,直至 Shell 解释器首次发现它时,才可以使用。Shell 函数的定义格式如下:
function function_name () {
commands
[return value]
}
- function 是 shell 中的关键字,专门用来定义函数,如果嫌麻烦,函数定义时也可以省略 function 关键字。
- commands 是函数要执行的代码,也就是一组语句
- return value 表示函数的返回值,其中 return 是 shell 关键字,专门用在函数中返回一个值,这一部分可以写也可以不写。如果不写,那么将以最后一条命令运行结果作为返回值。
2.7.2 函数调用
所有调用函数只需要给出函数名,不需要加括号。调用 Shell 函数时可以给它传递参数,也可以不传递。如果不传递参数,直接给出函数名字即可。如果传递参数,那么多个参数之间以空格分隔。在函数体内部,通过 $n
的形式来获取参数的值,例如,$1
表示第一个参数,$2
表示第二个参数…这就是前面讲的特殊变量。
#!/bin/bash
funWithParam(){
echo "第一个参数为 $1 !"
echo "第二个参数为 $2 !"
echo "第十个参数为 $10 !"
echo "第十个参数为 ${10} !"
echo "第十一个参数为 ${11} !"
echo "参数总数有 $# 个!"
echo "作为一个字符串输出所有参数 $* !"
}
funWithParam 1 2 3 4 5 6 7 8 9 34 73
注意,
$10
不能获取第十个参数,获取第十个参数需要${10}
。当n>=10
时,需要使用${n}
来获取参数。
2.7.3 函数返回值
Shell 函数返回值只能是整数,一般用来表示函数执行成功与否,返回值为 0 表示函数执行成功了,其他值表示执行失败了,if、while、for 等语句都是根据函数的退出状态来判断条件是否成立。如果一定要让函数返回字符串,那么可以先定义一个变量,用来接收函数的计算结果,脚本在需要的时候访问这个变量来获得函数返回值。
#!/bin/bash
function helloFun(){
return 'hello';
}
str=helloFun
echo $str
2.7.4 内置函数
函数名 | 说明 |
---|---|
s(x) | 计算 x 的正弦值,x 是弧度值 |
c(x) | 计算 x 的余弦值,x 是弧度值 |
a(x) | 计算 x 的反正切值,返回弧度值 |
l(x) | 计算 x 的自然对数 |
e(x) | 计算 e 的 x 次方 |
j(n,x) | 计算从n 到 x 的阶乘 |
2.8 输入/输出
大多数 UNIX 系统命令从你的终端接受输入并将所产生的输出发送回到您的终端。一个命令通常从一个叫标准输入的地方读取输入,默认情况下,这恰好是你的终端。同样,一个命令通常将其输出写入到标准输出,默认情况下,这也是你的终端。重定向命令列表如下:
命令 | 说明 |
---|---|
command > file | 将输出重定向到 file |
command < file | 将输入重定向到 file |
command >> file | 将输出以追加的方式重定向到 file |
n > file | 将文件描述符为 n 的文件重定向到 file |
n >> file | 将文件描述符为 n 的文件以追加的方式重定向到 file |
n >&m | 将输出文件 m 和 n 合并 |
n <&m | 将输入文件 m 和 n 合并 |
<< tag | 将开始标记 tag 和结束标记 tag 之间的内容作为输入 |
需要注意的是文件描述符 0 通常是标准输入(STDIN),1 是标准输出(STDOUT),2 是标准错误输出(STDERR)。
如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null:
command > /dev/null
/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到"禁止输出"的效果。