目录
当命令或程序语句不在命令行下执行,而是通过一个程序文件来执行时,该程序文件就被称为Shell脚本。 在Shell脚本里内置了很多命令、语句及循环控制,然后将这些命令一次性执行完毕,这种通过文件执行脚本的方式称为非交互的方式。 Shell脚本语言很适合用于处理纯文本型的数据,而Linux系统中几乎所有的配置文件、日志文件,以及绝大对数的启动文件都是纯文本类型的文件。
业界所说的 Shell 通常都是指 Shell 脚本, “Shell编程” 通常都是指 Shell 脚本编程,不是指开发 shell 自身。
Shell是一种脚本语言
什么是脚本语言?
任何代码最终都要被“翻译”成二进制的形式才能在计算机中执行。
有的编程语言,如 C/C++、Pascal、Go语言、汇编等,必须在程序运行之前将所有代码都翻译成二进制形式,也就是生成可执行文件,用户拿到的是最终生成的可执行文件,看不到源码。
这个过程叫做编译(Compile),这样的编程语言叫做编译型语言,完成编译过程的软件叫做编译器(Compiler)。
而有的编程语言,如 Shell、JavaScript、Python、PHP等,需要一边执行一边翻译,不会生成任何可执行文件,用户必须拿到源码才能运行程序。程序运行后会即时翻译,翻译完一部分执行一部分,不用等到所有代码都翻译完。
这个过程叫做解释,这样的编程语言叫做解释型语言或者脚本语言(Script),完成解释过程的软件叫做解释器。
编译型语言的优点是执行速度快、对硬件要求低、保密性好,适合开发操作系统、大型应用程序、数据库等。
脚本语言的优点是使用灵活、部署容易、跨平台性好,非常适合 Web 开发以及小工具的制作。
Shell 就是一种脚本语言,我们编写完源码后不用编译,直接运行源码即可。
第一个Shell脚本
打开文本编辑器(可以使用 vi/vim 命令来创建文件),新建一个文件 test.sh,扩展名为 sh(sh代表shell),扩展名并不影响脚本执行,见名知意就好,如果你用 php 写 shell 脚本,扩展名就用 php 好了。
输入一些代码,第一行一般是这样:
#!/bin/bash
echo "Hello World !"
“#!” 是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即 使用哪一种Shell。echo命令用于向窗口输出文本。
运行 Shell 脚本的方法:
1、作为可执行程序
将上面的代码保存为 test.sh,并 cd 到相应目录:
chmod +x ./test.sh #使脚本具有执行权限
./test.sh #执行脚本
注意,一定要写成 ./test.sh,而不是 test.sh(把./加进去),运行其它二进制的程序也一样,直接写 test.sh,linux 系统会去 PATH 里寻找有没有叫 test.sh 的,而只有 /bin, /sbin, /usr/bin,/usr/sbin 等在 PATH 里,你的当前目录通常不在 PATH 里,所以写成 test.sh 是会找不到命令的,要用 ./test.sh 告诉系统说,就在当前目录找。
也可以使用绝对路径path/test.sh运行。
使用ls -l选项可以查看到新建的test.sh在chmod前后的权限差别:
2、作为解释器参数
这种运行方式是,直接运行解释器,其参数就是 shell 脚本的文件名,如:
/bin/sh test.sh
/bin/php test.php
/bin/bash test.sh
bash test.sh
这种方式运行的脚本,不需要在第一行指定解释器信息,写了也没用。
该方法不需要使脚本具备可执行权限。
/bin/bash test.sh 和 bash test.sh本质是一样的,都是运行 Bash 解释器,第一种写法给出了绝对路径,会直接运行 Bash 解释器;第二种写法通过 bash 命令找到 Bash 解释器所在的目录,然后再运行,只不过多了一个查找的过程而已。
3、使用source 命令
source 是 Shell 内置命令的一种,它会读取脚本文件中的代码,并依次执行所有语句。你也可以理解为,source 命令会强制执行脚本文件中的全部命令,而忽略脚本文件的权限。
source 命令的用法为:
source filename
也可以简写为:
. filename
两种写法的效果相同。对于第二种写法,注意点号.和文件名中间有一个空格。
例如,使用 source 运行 test.sh:
[mozhiyan@localhost ~]$ cd demo #切换到test.sh所在的目录
[mozhiyan@localhost demo]$ source ./test.sh #使用source
Hello World !
[mozhiyan@localhost demo]$ source test.sh #使用source
Hello World !
[mozhiyan@localhost demo]$ . ./test.sh #使用点号
Hello World !
[mozhiyan@localhost demo]$ . test.sh #使用点号
Hello World !
注意:使用 source 命令不用给脚本增加执行权限,并且写不写./都行。
对于以上3种方式还需注意的是,方法1和方法2是在新的进程中运行脚本,方法3是在当前的进程中运行脚本。
Shell编程语法
Shell变量
在 Bash shell 中,每一个变量的值都是字符串,无论你给变量赋值时有没有使用引号,值都会以字符串的形式存储。
这意味着,Bash shell 在默认情况下不会区分变量类型,即使你将整数和小数赋值给变量,它们也会被视为字符串。
定义Shell变量
Shell 支持以下三种定义变量的方式:
variable=value
variable=‘value’
variable=“value”
variable 是变量名,value 是赋给变量的值。
如果 value 不包含任何空白符(例如空格、Tab 缩进等),那么可以不使用引号;如果 value 包含了空白符,那么就必须使用引号包围起来。
以单引号’ '包围变量的值时,单引号里面是什么就输出什么,即使内容中有变量和命令(命令需要反引起来)也会把它们原样输出。
以双引号" "包围变量的值时,输出时会先解析里面的变量和命令,而不是把双引号中的变量名和命令原样输出。双引号是最常用的。
注意,赋值号=的两边不能有空格
Shell 变量的命名规范:
变量名由数字、字母、下划线组成;(不能包含空格,标点符号)
必须以字母或者下划线开头;(尤其注意不能以数字开头)
不能使用 Shell 里的关键字(通过 help 命令可以查看保留关键字)。
使用变量
使用一个定义过的变量,只要在变量名前面加美元符号$即可,如:
your_name="qinjx"
echo $your_name
echo ${your_name}
变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界。
只读变量
使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。(注意,readonly 是一个命令)
variable=value
readonly variable
删除变量
使用 unset 命令可以删除变量。语法:
unset variable_name
变量被删除后不能再次使用。unset 命令不能删除只读变量。
变量作用域
运行shell时,会同时存在三种变量:
- 局部变量 局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。
- 环境变量 所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。
- shell变量 shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行
前面我们讲的,在脚本中定义的,或是在命令行中定义的都是局部变量。
字符串
前面讲变量的时候已经讲过了,在 Bash shell 中,每一个变量的值都是字符串。
变量定义的时候有三种方式,三者之间的区别:
-
由单引号’ '包围的字符串:
任何字符都会原样输出,在其中使用变量是无效的。
字符串中不能出现单引号,即使对单引号进行转义也不行。 -
由双引号" "包围的字符串:
如果其中包含了某个变量,那么该变量会被解析(得到该变量的值),而不是原样输出。
字符串中可以出现双引号,只要它被转义了就行。 -
不被引号包围的字符串
不被引号包围的字符串中出现变量时也会被解析,这一点和双引号" "包围的字符串一样。
字符串中不能出现空格,否则空格后边的字符串会作为其他变量或者命令解析。
示例:
#!/bin/bash
n=74
str1=abc$n
str2="a \"bc\" $n"
str3='abc $n'
echo $str1
echo $str2
echo $str3
结果:
abc74
a "bc" 74
abc $n
str1 中包含了$n
,它被解析为变量 n 的引用。$n
后边有空格,紧随空格的是 str2;Shell 将 str2 解释为一个新的变量名,而不是作为字符串 str1 的一部分。
str2 中包含了引号,但是被转义了(由反斜杠\开头的表示转义字符)。str2 中也包含了$n
,它也被解析为变量 n 的引用。
str3 中也包含了$n
,但是仅仅是作为普通字符,并没有解析为变量 n 的引用。
获取字符串长度
在 Shell 中获取字符串长度很简单,具体方法如下:
${#string_name}
string_name 表示字符串名字。
示例:
string="abcd"
echo ${#string} #输出 4
字符串拼接
在 Shell 中,将两个字符串并排放在一起就能实现拼接。
#!/bin/bash
str11="abc"
str22="def"
str1=$str11$str22 #中间不能有空格
str2="$str11 $str22" #如果被双引号包围,那么中间可以有空格
str3=$str11": "$str22 #中间可以出现别的字符串
str4="$str11: $str22" #这样写也可以 同上
str5="${str11}hhh: ${str22}hhh" #这个时候需要给变量名加上大括号
echo $str1
echo $str2
echo $str3
echo $str4
echo $str5
运行结果:
acbdef
abc def
abc: def
abc: def
abchhh: defhhh
截取字符串
大致分为从指定位置开始截取和从指定字符(子字符串)开始截取。
格式 | 说明 |
---|---|
${string: start :length} | 从 string 字符串的左边第 start 个字符开始,向右截取 length 个字符。 |
${string: start} | 从 string 字符串的左边第 start 个字符开始截取,直到最后。 |
${string: 0-start :length} | 从 string 字符串的右边第 start 个字符开始,向右截取 length 个字符。 |
${string: 0-start} | 从 string 字符串的右边第 start 个字符开始截取,直到最后。 |
${string#*chars} | 从 string 字符串第一次出现 *chars 的位置开始,截取 *chars 右边的所有字符。 |
${string##*chars} | 从 string 字符串最后一次出现 *chars 的位置开始,截取 *chars 右边的所有字符。 |
${string%chars*} | 从 string 字符串第一次出现 chars* 的位置开始,截取 chars* 左边的所有字符。 |
${string%%chars*} | 从 string 字符串最后一次出现 chars* 的位置开始,截取 chars* 左边的所有字符。 |
示例:
#!/bin/bash
str="abc/def//g-g-"
#从字符串左边第2个开始,向右截取3个字符(从左边开始,从 0 开始计数)
echo ${str: 2: 3}
#length省略的话表示直到字符串的末尾
echo ${str: 2}
#从 string 字符串的右边第 5 个字符开始,向右截取 3个字符
echo ${str: 0-5: 3}
#省略 length,直接截取到字符串末尾
echo ${str: 0-5}
#从 string 字符串第一次出现 *bc 的位置开始,截取 *bc 右边的所有字符
echo ${str#*bc}
#不用通配符*
echo ${str#abc}
#从 string 字符串最后一次出现 */的位置开始,截取 */右边的所有字符
echo ${str##*/}
#从 string 字符串第一次出现 /*的位置开始,截取 /*左边的所有字符,注意这里的第一次出现是从右往左开始看
echo ${str%/*}
#从 string 字符串最后一次出现 /*的位置开始,截取 /*左边的所有字符,注意这里的最后一次出现是从右往左开始看
echo ${str%%/*}
运行结果:
Shell数组
bash支持一维数组(不支持多维数组),并且没有限定数组的大小。
类似于 C 语言,数组元素的下标由 0 开始编号。获取数组中的元素要利用下标,下标可以是整数或算术表达式,其值应大于或等于 0。
定义数组
在 Shell 中,用括号( )来表示数组,数组元素之间用空格来分隔。赋值号=两边不能有空格,必须紧挨着数组名和数组元素。
array_name=(ele1 ele2 ele3 ... elen)
注意点:
1、Shell 是弱类型的,它并不要求所有数组元素的类型必须相同,可以同时有数字和字符串
2、Shell 数组的长度不是固定的,定义之后还可以增加元素。
3、无需逐个元素地给数组赋值,可以只给特定元素赋值。
如:ages=([3]=24 [5]=19 [10]=12)
读取数组
获取数组元素的值,一般使用下面的格式:
${array_name[index]}
使用@或*
可以获取数组中的所有元素,例如:
${nums[*]}
${nums[@]}
两者都可以得到 nums 数组的所有元素。
获取数组长度
所谓数组长度,就是数组元素的个数。(注意,不是指内存字节大小)
利用@或*,可以将数组扩展成列表,然后使用#来获取数组元素的个数,格式如下:
${#array_name[@]}
${#array_name[*]}
删除数组元素
使用unset
如:
unset nums[1] #删除nums数组的1号元素
unset nums #删除整个nums数组
位置参数
我们可以在执行 Shell 脚本时,向脚本传递参数,脚本内获取参数的格式为:$n。n 代表一个数字,1 为执行脚本的第一个参数,2 为执行脚本的第二个参数,以此类推……
给脚本传递参数
示例:
#!/bin/bash
echo "Shell 传递参数实例!";
echo "执行的文件名:$0";
echo "第一个参数为:$1";
执行脚本,并传参:
$ ./test.sh 1 2
输出:
Shell 传递参数实例!
执行的文件名:./test.sh
第一个参数为:1
第二个参数为:2
特殊变量
变量 | 含义 |
---|---|
$0 | 当前脚本的文件名。 |
$n(n≥1) | 传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是 $1,第二个参数是 $2。 |
$# | 传递给脚本或函数的参数个数。 |
$* | 传递给脚本或函数的所有参数。 |
$@ | 传递给脚本或函数的所有参数。与$*相同,但是使用时加引号,并在引号中返回每个参数。 |
$? | 上个命令的退出状态, |
$$ | 当前 Shell 进程 ID。 |
内建命令
echo
echo 是一个 Shell 内建命令,用来在终端输出字符串,并在最后默认加上换行符。
输出字符串
这是最简单的应用,用它可以实现我们梦的开始。
echo “Hello World!”
也可以不加双引号
echo Hello World!
输出字符串不换行
echo默认是换行的,如果需要不换行可以参考下面的做法:
1、使用-n
选项
示例:
#!/bin/bash
echo -n "abc"
echo "def"
输出:
abcdef
2、使用\c 转义字符:
示例:
#!/bin/bash
echo -e "OK! \c" # -e 开启转义 \c 不换行
echo "It is a test"
输出:
OK! It is a test
显示转义字符
比如需要显示双引号
echo "\"It is a test\""
输出:
“It is a test”
开启转义字符
默认情况下,echo 不会解析以反斜杠\开头的转义字符。使用-e选项开启转义字符解析。
如:
#!/bin/bash
echo "hello \nworld"
echo -e "hello \nworld"
输出:
hello \nworld
hello
world
显示结果定向至文件
echo "It is a test" > file # file如果已存在则输出到该文件,flie如果不存在则会自动新建在当前工作目录
echo `date` > file # date是命令,注意是反引号,命令的结果输出到file
输出变量的值
value=abc
echo "$value"
read
read 是 Shell 内置命令,用来从标准输入中读取数据并赋值给变量。如果没有进行重定向,默认就是从键盘读取用户输入的数据;如果进行了重定向,那么可以从文件中读取数据。
read 命令的用法为:
read [-options] [variables]
options表示选项,如下表所示;variables表示用来存储数据的变量,可以有一个,也可以有多个。
options和variables都是可选的,如果没有提供变量名,那么读取的数据将存放到环境变量 REPLY 中。
选项 | 说明 |
---|---|
-a array | 把读取的数据赋值给数组 array,从下标 0 开始。 |
-d delimiter | 用字符串 delimiter 指定读取结束的位置,而不是一个换行符(读取到的数据不包括 delimiter)。 |
-e | 在获取用户输入的时候,对功能键进行编码转换,不会直接显式功能键对应的字符。 |
-n num | 读取 num 个字符,而不是整行字符。 |
-p prompt | 显示提示信息,提示内容为 prompt。 |
-r | 原样读取(Raw mode),不把反斜杠字符解释为转义字符。 |
-s | 静默模式(Silent mode),不会在屏幕上显示输入的字符。当输入密码和其它确认信息的时候,这是很有必要的。 |
-t seconds | 设置超时时间,单位为秒。如果用户没有在指定时间内输入完成,那么 read 将会返回一个非 0 的退出状态,表示读取失败。 |
-u fd | 使用文件描述符 fd 作为输入源,而不是标准输入,类似于重定向。 |
示例:
read -n 1 char #读取单个字符赋值给char
read value1 value2 value3 #连续读取多个字符给多个变量赋值
read -p "please input:" #读取之前打印提示
printf
printf 命令模仿 C 程序库(library)里的 printf() 程序。
printf 命令的语法:
printf format-string [arguments…]
参数说明:
format-string: 为格式控制字符串
arguments: 为参数列表。
与C语言中的printf应用大致相同,不一样的是C语言中有函数括号,有参数逗号分隔,有分号结尾。
示例:
$ echo "Hello, Shell"
Hello, Shell
$ printf "Hello, Shell\n"
Hello, Shell
echo "Hello, Shell"的效果等同于printf “Hello, Shell\n”,只是printf要加入’\n’实现换行,而echo会自动在结尾加换行符。
exit
exit 是一个 Shell 内置命令,用来退出当前 Shell 进程,并返回一个退出状态;
exit 命令可以接受一个整数值作为参数,代表退出状态。如果不指定,默认状态值是 0。
exit 退出状态只能是一个介于 0~255 之间的整数,其中只有 0 表示成功,其它值都表示失败。
注意,exit 表示退出当前 Shell 进程,我们必须在新进程中运行 test.sh,否则当前 Shell 会话(终端窗口)会被关闭,我们就无法看到输出结果了。(不要用source)
示例:
exit 0 #写在最后
test
Shell中的 test 命令用于检查某个条件是否成立,它可以进行数值、字符和文件三个方面的测试。
test 通常和 if 语句一起使用,并且大部分 if 语句都依赖 test。
Shell test 命令的用法为:
test expression
当 test 判断 expression 成立时,退出状态为 0,否则为非 0 值。
test 命令也可以简写为[ ],它的用法为:
[ expression ]
注意[]和expression之间的空格,这两个空格是必须的,否则会导致语法错误。[]的写法更加简洁,比 test 使用频率高。test 和 [ ] 是等价的。
数值测试
选 项 | 作 用 |
---|---|
num1 -eq num2 | 判断 num1 是否和 num2 相等。 |
num1 -ne num2 | 判断 num1 是否和 num2 不相等。 |
num1 -gt num2 | 判断 num1 是否大于 num2 。 |
num1 -lt num2 | 判断 num1 是否小于 num2。 |
num1 -ge num2 | 判断 num1 是否大于等于 num2。 |
num1 -le num2 | 判断 num1 是否小于等于 num2。 |
示例:
#!/bin/bash
read age #输入年龄
if test $age -le 2; then #小于等于2
echo "婴儿"
elif test $age -ge 3 && test $age -le 8; then #大于等于3 && 小于等于8
echo "幼儿"
elif [ $age -ge 9 ] && [ $age -le 17 ]; then
echo "少年"
elif [ $age -ge 18 ] && [ $age -le 25 ]; then
echo "成年"
elif test $age -ge 26 && test $age -le 40; then
echo "青年"
elif test $age -ge 41 && [ $age -le 60 ]; then
echo "中年"
else
echo "老年"
fi
字符串测试
选 项 | 作 用 |
---|---|
-z str | 判断字符串 str 是否为空。 |
-n str | 判断宇符串 str 是否为非空。 |
str1 = str2 str1 ==str2 | =和==是等价的,都用来判断 str1 是否和 str2 相等。 |
str1 != str2 | 判断 str1 是否和 str2 不相等。 |
str1 > str2 | 判断 str1 是否大于 str2。>是>的转义字符,这样写是为了防止>被误认为成重定向运算符。 |
str1 < str2 | 判断 str1 是否小于 str2。同样,<也是转义字符。 |
示例:
#!/bin/bash
str1="abc"
str2="abcd"
#检测字符串是否为空
if [ -z "$str1" ] || [ -z "$str2" ]
then
echo "字符串不能为空"
exit 0
fi
#比较字符串
if [ $str1 = $str2 ]
then
echo "两个字符串相等"
else
echo "两个字符串不相等"
fi
if [ $str1 \< $str2 ]
then
echo "$str1小于$str2"
else
echo "$str1大于等于$str2"
fi
输出:
两个字符串不相等
abc小于abcd
注意:
不管是比较数字还是字符串,Shell 都不支持 >= 和 <= 运算符
字符串比较大小是按字典序比较大小的,排在后面的比排在前面的大,如果一个字符串是另一个字符串的前缀,则前一个字符串小于后一个字符串。
"$str1"用双引号包起来是为了防止 $str1 是空字符串时出现错误。
文件测试
文件类型判断
选 项 | 作 用 |
---|---|
-b filename | 判断文件是否存在,并且是否为块设备文件。 |
-c filename | 判断文件是否存在,并且是否为字符设备文件。 |
-d filename | 判断文件是否存在,并且是否为目录文件。 |
-e filename | 判断文件是否存在。 |
-f filename | 判断文件是否存在,井且是否为普通文件。 |
-L filename | 判断文件是否存在,并且是否为符号链接文件。 |
-p filename | 判断文件是否存在,并且是否为管道文件。 |
-s filename | 判断文件是否存在,并且是否为非空。 |
-S filename | 判断该文件是否存在,并且是否为套接字文件。 |
文件权限判断 | |
选 项 | 作 用 |
:- | :- |
-r filename | 判断文件是否存在,并且是否拥有读权限。 |
-w filename | 判断文件是否存在,并且是否拥有写权限。 |
-x filename | 判断文件是否存在,并且是否拥有执行权限。 |
-u filename | 判断文件是否存在,并且是否拥有 SUID 权限。 |
-g filename | 判断文件是否存在,并且是否拥有 SGID 权限。 |
-k filename | 判断该文件是否存在,并且是否拥有 SBIT 权限。 |
文件比较
选 项 | 作 用 |
---|---|
filename1 -nt filename2 | 判断 filename1 的修改时间是否比 filename2 的新。 |
filename -ot filename2 | 判断 filename1 的修改时间是否比 filename2 的旧。 |
filename1 -ef filename2 | 判断 filename1 是否和 filename2 的 inode 号一致,可以理解为两个文件是否为同一个文件。这个判断用于判断硬链接是很好的方法 |
示例:
先新建一个文件为file.txt
#!/bin/bash
filename="file.txt"
text="hello"
if test -w $filename && test -n $text #filename已存在且可写 text非空
then
echo $text > $filename
echo "写入成功"
else
echo "写入失败"
fi
运行后file.txt中被写入hello
流程控制
if…else
if 语句语法格式:
if condition
then
command1
command2
...
commandN
fi
condition是判断条件,如果 condition 成立(返回“真”),那么 then 后边的语句将会被执行;如果 condition 不成立(返回“假”),那么不会执行任何语句。
因为有fi 来结尾,所以即使有多条语句也不需要用{ }包围起来。
if else 语法格式:
if condition
then
command1
command2
...
commandN
else
command
fi
如果 condition 成立,那么 then 后边的语句将会被执行;否则,执行 else 后边的语句。
if else-if else 语法格式:
if condition1
then
statement1
elif condition2
then
statement2
elif condition3
then
statement3
……
else
statementn
fi
注意,if 和 elif 后边都得跟着 then。
for循环
Shell for 循环有两种使用形式,C语言风格的 for 循环和Python风格的for循环。
C语言风格的 for 循环的用法如下:
for((exp1; exp2; exp3))
do
statements
done
运行过程为:
-
先执行 exp1。
-
再执行 exp2,如果它的判断结果是成立的,则执行循环体中的语句,否则结束整个 for 循环。
-
执行完循环体后再执行 exp3。
-
重复执行步骤 2) 和 3),直到 exp2 的判断结果不成立,就结束循环。
基本与C语言的for循环执行过程一样。
注意点:
exp1和exp2和exp3都是可省的,但分号不可省。
使用break可以立即退出循环。
Python 风格的 for in 循环
Python 风格的 for in 循环的用法如下:
for variable in value_list
do
statements
done
variable 表示变量,value_list 表示取值列表,in 是 Shell 中的关键字。
每次循环都会从 value_list 中取出一个值赋给变量 variable,然后进入循环体(do 和 done 之间的部分),执行循环体中的 statements。直到取完 value_list 中的所有值,循环就结束了。
value_list 的形式有多种:
- 直接给出具体的值
可以是数字,字符串
例:
#!/bin/bash
for str in "shell" "非常" "强大"
do
echo $str
done
- 给出一个取值范围
具体格式为:
{start…end}
注意中间用两个点号相连,而不是三个点号。这种形式只支持数字和字母。
3) 使用命令的执行结果
-
使用 Shell 通配符
-
使用特殊变量
while循环
Shell while 循环的用法如下:
while condition
do
statements
done
condition表示判断条件,statements表示要执行的语句(可以只有一条,也可以有多条)
例:
计算从 1 加到 100 的和。
#!/bin/bash
i=1
sum=0
while ((i <= 100))
do
((sum += i))
((i++))
done
echo "The sum is: $sum"
until循环
unti 循环和 while 循环恰好相反,当判断条件不成立时才进行循环,一旦判断条件成立,就终止循环。
这与C语言的do…while循环不一样。
Shell until 循环的用法如下:
until condition
do
statements
done
condition表示判断条件,statements表示要执行的语句(可以只有一条,也可以有多条)
case in
case … esac 为多选择语句,与其他语言中的 switch … case 语句类似,是一种多分枝选择结构,每个 case 分支用右圆括号开始,用两个分号 ;; 表示 break,即执行结束,跳出整个 case … esac 语句,esac(就是 case 反过来)作为结束标记。
case expression in
pattern1)
statement1
;;
pattern2)
statement2
;;
pattern3)
statement3
;;
……
*)
statementn
esac
注意点:
-
取值后面必须为单词 in,每一模式必须以右括号结束。
-
取值可以为变量或常数,匹配发现取值符合某一模式后,其间所有命令开始执行直至
;;
。 -
取值将检测匹配的每一个模式。一旦模式匹配,则执行完匹配模式相应命令后不再继续其他模式。如果无一匹配模式,使用星号 * 捕获该值,再执行后面的命令。
-
可以没有*)部分。如果 expression 没有匹配到任何一个模式,那么就不执行任何操作。
-
除最后一个分支外(这个分支可以是普通分支,也可以是*)分支),其它的每个分支都必须以;;结尾,;;代表一个分支的结束,不写的话会有语法错误。最后一个分支可以写;;,也可以不写。
例:
#!/bin/bash
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
break和continue
使用 while、until、for、select 循环时,如果想提前结束循环(在不满足结束条件的情况下结束循环),可以使用 break 或者 continue 关键字。
Shell 中的 break 和 continue 却能够跳出多层循环,也就是说,内层循环中的 break 和 continue 能够跳出外层循环。这一点与C语言不一样。
【break】
Shell break 关键字的用法为:
break n
n 表示跳出循环的层数,如果省略 n,则表示跳出当前的整个循环。
【continue】
Shell continue 关键字的用法为:
continue n
n 表示循环的层数:
如果省略 n,则表示 continue 只对当前层次的循环语句有效,遇到 continue 会跳过本次循环,忽略本次循环的剩余代码,直接进入下一次循环。
如果带上 n,比如 n 的值为 2,那么 continue 对内层和外层循环语句都有效,不但内层会跳过本次循环,外层也会跳过本次循环,其效果相当于内层循环和外层循环同时执行了不带 n 的 continue。
break 和 continue 的区别
break 用来结束所有循环,循环语句不再有执行的机会;continue 用来结束本次循环,直接跳到下一次循环,如果循环条件成立,还会继续循环。
(( ))和[[ ]]
(( ))的用法
双小括号 (( )) 的语法格式为:
((表达式))
表达式可以只有一个,也可以有多个,多个表达式之间以逗号,分隔。对于多个表达式的情况,以最后一个表达式的值作为整个 (( )) 命令的执行结果。
使用$
获取 (( )) 命令的结果,不用$
则不能获取到结果。
在 (( )) 中使用变量无需加上$前缀,(( )) 会自动解析变量名。
运算操作符/运算命令 | 说明 |
---|---|
((a=10+66) ((b=a-15)) ((c=a+b)) ((c=a+b)) | 这种写法可以在计算完成后给变量赋值。以 ((b=a-15)) 为例,即将 a-15 的运算结果赋值给变量 c。 注意,使用变量时不用加$前缀,(( )) 会自动解析变量名。 |
a=$((10+66) b=$((a-15)) c=$((a+b)) | 可以在 (( )) 前面加上
符号获取
(
(
)
)
命令的执行结果,也即获取整个表达式的值。以
c
=
符号获取 (( )) 命令的执行结果,也即获取整个表达式的值。以 c=
符号获取(())命令的执行结果,也即获取整个表达式的值。以c=((a+b)) 为例,即将 a+b 这个表达式的运算结果赋值给变量 c。 注意,类似 c=((a+b)) 这样的写法是错误的,不加$就不能取得表达式的结果。 |
((a>7 && b==c)) | (( )) 也可以进行逻辑运算,在 if 语句中常会使用逻辑运算。 |
echo $((a+10)) | 需要立即输出表达式的运算结果时,可以在 (( )) 前面加$符号。 |
((a=3+5, b=a+10)) | 对多个表达式同时进行计算。 |
示例:
$ echo $((6-3))
$ i=5
$ ((i=i*2)) #可以简写为 ((i*=2))。
$ echo $i
echo $((1+2**3-4%3)) #也可以直接将表达式的结果输出,注意不要丢掉 $ 符号
8
$ a=10
$ echo $((a++)) #如果++在a的后面,那么在输出整个表达式时,会输出a的值,因为a为10,所以表达式的值为10。
10
$ ((a=3+5, b=a+10)) #先计算第一个表达式,再计算第二个表达式
$ echo $a $b
8 18
$ c=$((4+8, a+b)) #以最后一个表达式的结果作为整个(())命令的执行结果
$ echo $c
26
示例2:
依次输入两个数计算和
#!/bin/bash
read -p "first num:" a
read -p "second num:" b
total=$((a+b)) #不需要写成total=$(($a+$b)) 但写成这样不会出问题
echo "$a + $b = $total"
[[ ]]的用法
[[ ]]是 Shell 内置关键字,不是命令。它和 test 命令类似,也用来检测某个条件是否成立。
可以认为 [[ ]] 是 test 的升级版,对细节进行了优化,并且扩展了一些功能。
[[ ]] 的用法为:
[[ expression ]]
当 [[ ]] 判断 expression 成立时,退出状态为 0,否则为非 0 值。注意[[ ]]和expression之间的空格,这两个空格是必须的,否则会导致语法错误。
[[ ]] 相对于[]的提升:
不需要把变量名用双引号""包围起来,即使变量是空值,也不会出错。因为[[]]是关键字不是函数。
不需要、也不能对 >、< 进行转义。
[[ ]] 支持逻辑运算符写在里面。
[[ ]] 支持正则表达式。
示例:
#!/bin/bash
read str1
read str2
if [[ -z $str1 ]] || [[ -z $str2 ]] #不需要对变量名加双引号 [[ ]] 支持逻辑运算符写在里面
then
echo "字符串不能为空"
elif [[ $str1 < $str2 ]] #不需要也不能对 < 进行转义
then
echo "str1 < str2"
else
echo "str1 >= str2"
fi
read tel
if [[ $tel =~ ^1[0-9]{10}$ ]] #判断开头是1,后面连续10位都是0-9之间
then
echo "你输入的是手机号码"
else
echo "你输入的不是手机号码"
fi
对^1[0-9]{10}$
的说明:
- ^匹配字符串的开头(一个位置);
- [0-9]{10}匹配连续的十个数字;
- $匹配字符串的末尾(一个位置)。
Shell函数
Shell 函数的本质是一段可以重复使用的脚本代码,这段代码被提前编写好了,放在了指定的位置,使用时直接调取即可。
shell中函数的定义格式如下:
[ function ] funname [()]
{
action;
[return int;]
}
1、可以带function fun() 定义,也可以直接fun() 定义,不带任何参数。
2、参数返回,可以显示加:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。 return后跟数值n(0-255)
示例:
#!/bin/bash
#函数调用
fun #可以将调用放在定义的前面,这种用法要注意
#函数定义
function fun(){
echo "第一个shell函数"
}
#函数调用
fun
#计算所有参数的和
function getsum(){
local sum=0
for n in $@
do
((sum+=n))
done
return $sum
}
getsum 10 20 55 15 #调用函数并传递参数
echo $?
$@
表示函数的所有参数,$?
表示函数的退出状态(返回值)。
注释
单行注释
以 # 开头的行就是注释。类似C语言里的//
# 注释1
# 注释2
# 注释3
多行注释
:<<EOF
注释内容...
注释内容...
注释内容...
EOF
EOF可以换成其他的字符串,字符,数字等,只要前后统一即可
:<<EOF222
注释内容...
注释内容...
注释内容...
EOF222
:<<0
注释内容...
注释内容...
注释内容...
0
综合应用
在指定时间内输入密码。
#!/bin/bash
if
read -t 20 -sp "Enter password in 20 seconds(once) > " pass1 && printf "\n" && #第一次输入密码
read -t 20 -sp "Enter password in 20 seconds(again)> " pass2 && printf "\n" && #第二次输入密码
[ $pass1 == $pass2 ] #判断两次输入的密码是否相等
then
echo "Valid password"
else
echo "Invalid password"
fi
参考:
https://www.runoob.com/linux/linux-shell.html
http://c.biancheng.net/view/706.html
https://blog.csdn.net/qq_36119192/article/details/82964713