shell 编程
一、运算符
1、算术运算符
下表列出了常用的算术运算符,假定变量 a 为 10,变量 b 为 20:
运算符 | 说明 | 举例 |
---|---|---|
+ | 加法 | expr $a + $b 结果为 30。 |
- | 减法 | expr $a - $b 结果为 -10。 |
* | 乘法 | expr $a \* $b 结果为 200。 |
/ | 除法 | expr $b / $a 结果为 2。 |
% | 取余 | expr $b % $a 结果为 0。 |
= | 赋值 | a=$b 把变量 b 的值赋给 a。 |
== | 相等。用于比较两个数字,相同则返回 true。 | [ $a ==$b ] 返回 false。 |
!= | 不相等。用于比较两个数字,不相同则返回 true。 | [ $a !=$b ] 返回 true。 |
注意: 条件表达式要放在方括号之间,并且要有空格,例如: [$a==$b] 是错误的,必须写成 [ $a ==$b ] 。
- 乘号(*)前边必须加反斜杠()才能实现乘法运算;
- if…then…fi 是条件语句,后续将会讲解。
- 在 MAC 中 shell 的 expr 语法是: $((表达式)) ,此处表达式中的 “*” 不需要转义符号 “" 。
2、关系运算符
关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
下表列出了常用的关系运算符,假定变量 a 为 10,变量 b 为 20:
运算符 | 说明 | 举例 |
---|---|---|
-eq | 检测两个数是否相等,相等返回 true。 | [ $a -eq$b ] 返回 false。 |
-ne | 检测两个数是否不相等,不相等返回 true。 | [ $a -ne$b ] 返回 true。 |
-gt | 检测左边的数是否大于右边的,如果是,则返回 true。 | [ $a -gt$b ] 返回 false。 |
-lt | 检测左边的数是否小于右边的,如果是,则返回 true。 | [ $a -lt$b ] 返回 true。 |
-ge | 检测左边的数是否大于等于右边的,如果是,则返回 true。 | [ $a -ge$b ] 返回 false。 |
-le | 检测左边的数是否小于等于右边的,如果是,则返回 true。 | [ $a -le$b ] 返回 true。 |
3、布尔运算符
下表列出了常用的布尔运算符,假定变量 a 为 10,变量 b 为 20:
运算符 | 说明 | 举例 |
---|---|---|
! | 非运算,表达式为 true 则返回 false, | [ ! false ] 返回 true。 |
-o | 或运算,有一个表达式为 true 则返回 true。 | [ $a -lt 20 -o$b -gt 100 ] 返回 true。 |
-a | 与运算,两个表达式都为 true 才返回 true。 | [ $a -lt 20 -a$b -gt 100 ] 返回 false。 |
4、逻辑运算符
以下介绍 Shell 的逻辑运算符,假定变量 a 为 10,变量 b 为 20:
运算符 | 说明 |
---|---|
&& | 逻辑的 AND |
|| | 逻辑的 OR |
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。 |
6、文件测试运算符
文件测试运算符用于检测 Unix 文件的各种属性。
属性检测描述如下:
操作符 | 说明 |
---|---|
-b file | 检测文件是否是块设备文件,如果是,则返回 true。 |
-c file | 检测文件是否是字符设备文件,如果是,则返回 true。 |
-d file | 检测文件是否是目录,如果是,则返回 true。 |
-e file | 检测文件是否存在,如果是,则返回 true。 |
-f file | 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。 |
-g file | 检测文件是否设置了 SGID 位,如果是,则返回 true。 |
-k file | 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。 |
-p file | 检测文件是否是有名管道,如果是,则返回 true。 |
-u file | 检测文件是否设置了 SUID 位,如果是,则返回 true。 |
-r file | 检测文件是否可读,如果是,则返回 true。 |
其他检查符:
- -S: 判断某文件是否 socket。
- -L: 检测文件是否存在并且是一个符号链接。
二、字符串处理
1、字符串切片
${变量:偏移量:截取长度}
--------------------------example
[192.168.2.230] - root@ubuntu20.04.5-template:/ root
$ MYPATH=' /usr/bin/wget'
[192.168.2.230] - root@ubuntu20.04.5-template:/root
$ echo ${MYPATH: 4:4}
/bin #其会左向右从第四位向后取四位(字符串第一位为0)
[192.168.2.230] - root@ubuntu20.04.5-template:/ root
$ echo ${MYPATH: -4} #这会右向左从末尾向前取四位,「-4和冒号之间要有一个空格」
2、基于模式取子串
${var#*word}
其中word可以是指定的任意字符,自左而右,删除字符串开头至第一次出现word字符之间的所有字符。
${var##*word}
其中word可以是指定的任意字符,自左而右,删除字符串开头至最后一次出现word字符之间的所有字符。
${var%sword*}
其中word可以是指定的任意字符,自右而左,删除字符串尾部至第一次出现word字符之间的所有字符。
${var%%sword*}
其中word可以是指定的任意字符,自右而左,删除字符串尾部至最后一次出现word字符之间的所有字符
3、查找删除
${var/匹配文本}
删除第一次匹配到的内容
${var//pattern}
删除所有匹配到的内容
${var/#pattern}
删除行首匹配到的内容
${var/%pattern}
删除行尾匹配到的内容
4、查找替换
${var/匹配文本/替换文本}
替换第一次匹配到的内容
${var//匹配文本/替换文本}
替换所有匹配到的内容
${var/#匹配文本/替换文本}
行首匹配时替换行首
${var/%匹配文本/替换文本}
行尾匹配时替换行尾
三、条件判断语法格式
- test条件表达式
- [ 条件表达式 ]
- [[ 条件表达式 ]]
特别说明:
1)[ 亲亲,我两边都有空格,不空会被打死的喔~ ]
2)[[ 亲亲,我两边都有空格,不空会被打死的喔~ ]]
3)更多判断,man test
查看,许多的参数都可用来进行条件判断
[] 和 [[]] 的区别:
[]
(test 命令)
[]
是传统的测试条件的命令,也称为 test
命令。这种语法适用于POSIX标准的Shell,包括sh和bash。
基本语法:
[ condition ]
用法和特点:
- 条件需要用空格分隔。
- 只支持基本的逻辑和比较操作。
- 适用于大多数Unix系统。
示例:
if [ "$a" -eq "$b" ]; then echo "a 等于 b" fi
[[]]
(扩展测试命令)
[[]]
是Bash特有的扩展测试命令,提供了更强大的功能和更简洁的语法。
基本语法:
[[ condition ]]
用法和特点:
- 支持更复杂的条件测试,例如模式匹配(通配符)。
- 可以直接使用逻辑运算符
&&
和||
进行复合条件判断。 - 允许在字符串比较中使用
=~
操作符进行正则表达式匹配。 - 不需要对条件中的变量进行双引号引用,因为它可以正确处理空字符串和带空格的字符串。
示例:
if [[ $a == $b ]]; then echo "a 等于 b" fi if [[ $a =~ ^[0-9]+$ ]]; then echo "a 是一个数字" fi
详细对比
字符串比较:
[]
使用=
进行字符串比较,通常需要使用双引号。[ "$a" = "$b" ]
[[]]
使用==
进行字符串比较,不需要双引号。[[ $a == $b ]]
逻辑运算:
[]
需要使用-a
(AND) 和-o
(OR)。[ "$a" -eq "$b" -a "$c" -eq "$d" ]
[[]]
可以使用&&
和||
。[[ $a -eq $b && $c -eq $d ]]
正则表达式匹配:
[]
不支持直接的正则表达式匹配。[[]]
支持=~
操作符。[[ $a =~ ^[0-9]+$ ]]
模式匹配:
[]
不支持模式匹配。[[]]
支持模式匹配。[[ $a == *.txt ]]
总结
- 使用
[]
更适合于需要兼容POSIX标准的脚本和基本条件测试。 - 使用
[[]]
提供了更强大和简洁的语法,适合于需要进行复杂条件判断的Bash脚本。
在实际编程中,建议在Bash脚本中尽量使用 [[]]
,除非需要兼容性考虑,这样可以充分利用其扩展功能和更清晰的语法。
四、流程控制
if else 语法格式
if condition
then
command1
command2
...
commandN
else
command
fi
if else-if else 语法格式
if condition1
then
command1
elif condition2
then
command2
else
commandN
fi
if else 的 […] 判断语句中大于使用 -gt,小于使用 -lt,其他用法请参照运算符。
if [ "$a" -gt "$b" ]; then
...
fi
如果使用((…)) 作为判断语句,就可以使用类c风格判断,大于和小于可以直接使用 > 和 <。
if (( a > b )); then
...
fi
for循环
for var in item1 item2 ... itemN
do
command1
command2
...
commandN
done
举个栗子🌰
#!/bin/bash
for str in This is a string
do
echo $str
done
输出结果:
This
is
a
string
要打印1 -> 10的数字,可以用以下格式
#!/bin/zsh
for i in {1..10}
do
echo "$i"
done
或者以下类c风格
#!/bin/zsh
for ((i=1; i<11; i++))
do
echo "$i"
done
while循环
while condition
do
command
done
举个栗子🌰
#!/bin/bash
int=1
while(( $int<=5 ))
do
echo $int
let "int++"
done
无限循环
无限循环语法格式:
while :
do
command
done
或者
while true
do
command
done
或者
for (( ; ; ))
until 循环
until 循环执行一系列命令直至条件为 true 时停止。
until 循环与 while 循环在处理方式上刚好相反。
一般 while 循环优于 until 循环,但在某些时候—也只是极少数情况下,until 循环更加有用。
until 语法格式:
until condition
do
command
done
condition 一般为条件表达式,如果返回值为 false,则继续执行循环体内的语句,否则跳出循环。
五、获取当前日期以及时间
在 Unix 或 Linux 系统中,date
命令用于显示或设置系统日期和时间。date
命令可以接受多种格式化字符串来指定输出的格式。
命令 date +%M
会返回当前时间中的“分钟”部分。例如,如果当前时间是 14:35,那么运行 date +%M
会输出 35
。
下面是一些常用的 date
格式化字符串及其含义:
%Y
:四位数的年份(例如,2023)%m
:两位数的月份(例如,05 表示五月)%d
:两位数的日期(例如,17)%D
:格式化日期(例如2024年五月17日,其会输出05/17/24)%H
:两位数的小时(24 小时制,例如,14 表示下午 2 点)%M
:两位数的分钟(例如,35)%S
:两位数的秒(例如,45)%s
:当前时间的Unix时间戳(也称为纪元时间)。Unix时间戳是从1970年1月1日00:00:00 UTC(协调世界时)开始经过的秒数。
所以,date +%M
的输出仅仅是当前时间的分钟部分。
六、传递参数
特殊参数
我们可以在执行 Shell 脚本时,向脚本传递参数,脚本内获取参数的格式为 $n,n 代表一个数字,1 为执行脚本的第一个参数,2 为执行脚本的第二个参数。
例如可以使用 $1、$2 等来引用传递给脚本的参数,其中$1表示第一个参数,$2 表示第二个参数,依此类推。
在#!/bin/zsh
脚本中,$#
和$0
是特定的shell变量,用于脚本参数处理和信息获取。让我们详细解释它们的含义:
$#
用于表示传递给脚本的参数数量。$0
为执行的文件名(包含文件路径),即脚本自身的文件名。$?
显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。$-
显示Shell使用的当前选项,与set命令功能相同。$!
后台运行的最后一个进程的ID号$$
脚本运行的当前进程ID号$*
以一个单字符串显示所有向脚本传递的参数。$@
与$*相同,但是使用时加引号,并在引号中返回每个参数。
$# 的含义
$#
代表传递给脚本的参数数量。在脚本开头,有以下代码:
if [ $# -ne 1 ]; then
echo "Usage: $0 <directory>"
exit 1
fi
这段代码的意思是:检查传递给脚本的参数数量是否不等于1。如果不是1个参数(即传递了0个参数或者多于1个参数),则输出使用说明并退出脚本。换句话说,$#
用于确保脚本接收正好一个参数。
$0 的含义
$0
代表脚本自身的文件名(包含文件路径)。在脚本中同样的代码:
if [ $# -ne 1 ]; then
echo "Usage: $0 <directory>"
exit 1
fi
在这个例子中,$0
表示脚本文件的名称。假设脚本的文件名是rename_files.sh
,那么当你执行这个脚本时,$0
就会被替换为rename_files.sh
。因此,输出的使用说明会是:
Usage: rename_files.sh <directory>
常用命令
find命令
语法
find [路径] [匹配条件] [动作]
参数说明 : 路径 是要查找的目录路径,可以是一个目录或文件名,也可以是多个路径,多个路径之间用空格分隔,如果未指定路径,则默认为当前目录。
expression 是可选参数,用于指定查找的条件,可以是文件名、文件类型、文件大小等等。 匹配条件 中可使用的选项有二三十个之多,以下列出最常用的部份:
- -name pattern:按文件名查找,支持使用通配符 * 和 ?。
- -type type:按文件类型查找,可以是 f(普通文件)、d(目录)、l(符号链接)等。
- -size [+-]size[cwbkMG]:按文件大小查找,支持使用 + 或 - 表示大于或小于指定大小,单位可以是 c(字节)、w(字数)、b(块数)、k(KB)、M(MB)或 G(GB)。
- -mtime days:按修改时间查找,支持使用 + 或 - 表示在指定天数前或后,days 是一个整数表示天数。
- -user username:按文件所有者查找。
- -group groupname:按文件所属组查找。
动作: 可选的,用于对匹配到的文件执行操作,比如删除、复制等。
find 命令中用于时间的参数如下:
- -amin n:查找在 n 分钟内被访问过的文件。
- -atime n:查找在 n*24 小时内被访问过的文件。
- -cmin n:查找在 n 分钟内状态发生变化的文件(例如权限)。
- -ctime n:查找在 n*24 小时内状态发生变化的文件(例如权限)。
- -mmin n:查找在 n 分钟内被修改过的文件。
- -mtime n:查找在 n*24 小时内被修改过的文件。
在这些参数中,n 可以是一个正数、负数或零。正数表示在指定的时间内修改或访问过的文件,负数表示在指定的时间之前修改或访问过的文件,零表示在当前时间点上修改或访问过的文件。
正数应该表示时间之前,负数表示时间之内。 例如:-mtime 0 表示查找今天修改过的文件,-mtime -7 表示查找一周以前修改过的文件。
关于时间 n 参数的说明:
- +n:查找比 n 天前更早的文件或目录。
- -n:查找在 n 天内更改过属性的文件或目录。
- n:查找在 n 天前(指定那一天)更改过属性的文件或目录。
实例
查找当前目录下名为 file.txt 的文件:
find . -name file.txt
将当前目录及其子目录下所有文件后缀为 .c 的文件列出来:
find . -name "*.c"
将当前目录及其子目录中的所有文件列出:
find . -type f
如果后面的参数过多,可以用()括起来,但是注意使用 \
转义符,以下命令的含义是找出当前目录以及子目录所有的.md和.txt文件
find . -type f \( -name "*.md" -o -name "*.txt" \)
read命令
read 内部命令被用来从标准输入读取单行数据。这个命令可以用来读取键盘输入,当使用重定向的时候,可以读取文件中的一行数据。
语法
read [-ers] [-a aname] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ...]
参数说明:
- -a 后跟一个变量,该变量会被认为是个数组,然后给其赋值,默认是以空格为分割符。
- -d 后面跟一个标志符,其实只有其后的第一个字符有用,作为结束的标志。
- -p 后面跟提示信息,即在输入前打印提示信息。
- -e 在输入的时候可以使用命令补全功能。
- -n 后跟一个数字,定义输入文本的长度,很实用。
- -r 屏蔽\,如果没有该选项,则\作为一个转义字符,有的话 \就是个正常的字符了。
- -s 安静模式,在输入字符时不再屏幕上显示,例如login时输入密码。
- -t 后面跟秒数,定义输入字符的等待时间。
- -u 后面跟fd,从文件描述符中读入,该文件描述符可以是exec新开启的。
实例
1、简单读取
#!/bin/bash
#这里默认会换行
echo "输入网站名: "
#读取从键盘的输入
read website
echo "你输入的网站名是 $website"
exit 0 #退出
测试结果为:
输入网站名:
www.runoob.com
你输入的网站名是 www.runoob.com
2、-p 参数,允许在 read 命令行中直接指定一个提示。
#!/bin/bash
read -p "输入网站名:" website
echo "你输入的网站名是 $website"
exit 0
测试结果为:
输入网站名:www.runoob.com
你输入的网站名是 www.runoob.com
3、-t 参数指定 read 命令等待输入的秒数,当计时满时,read命令返回一个非零退出状态。
#!/bin/bash
if read -t 5 -p "输入网站名:" website
then
echo "你输入的网站名是 $website"
else
echo "\n抱歉,你输入超时了。"
fi
exit 0
执行程序不输入,等待 5 秒后:
输入网站名:
抱歉,你输入超时了
4、除了输入时间计时,还可以使用 -n 参数设置 read 命令计数输入的字符。当输入的字符数目达到预定数目时,自动退出,并将输入的数据赋值给变量。
#!/bin/bash
read -n1 -p "Do you want to continue [Y/N]?" answer
case $answer in
Y | y)
echo "fine ,continue";;
N | n)
echo "ok,good bye";;
*)
echo "error choice";;
esac
exit 0
该例子使用了-n 选项,后接数值 1,指示 read 命令只要接受到一个字符就退出。只要按下一个字符进行回答,read 命令立即接受输入并将其传给变量,无需按回车键。
只接收 2 个输入就退出:
#!/bin/bash
read -n2 -p "请随便输入两个字符: " any
echo "\n您输入的两个字符是:$any"
exit 0
执行程序输入两个字符:
请随便输入两个字符: 12
您输入的两个字符是:12
5、-s 选项能够使 read 命令中输入的数据不显示在命令终端上(实际上,数据是显示的,只是 read 命令将文本颜色设置成与背景相同的颜色)。输入密码常用这个选项。
#!/bin/bash
read -s -p "请输入您的密码:" pass
echo "\n您输入的密码是 $pass"
exit 0
执行程序输入密码后是不显示的:
请输入您的密码:
您输入的密码是 runoob
6.读取文件 每次调用 read 命令都会读取文件中的 “一行” 文本。当文件没有可读的行时,read 命令将以非零状态退出。 通过什么样的方法将文件中的数据传给 read 呢?使用 cat 命令并通过管道将结果直接传送给包含 read 命令的 while 命令。 测试文件 test.txt 内容如下:
123
456
runoob
测试代码:
#!/bin/bash
count=1 # 赋值语句,不加空格
cat test.txt | while read line # cat 命令的输出作为read命令的输入,read读到>的值放在line中
do
echo "Line $count:$line"
count=$[ $count + 1 ] # 注意中括号中的空格。
done
echo "finish"
exit 0
执行结果为:
Line 1:123
Line 2:456
Line 3:runoob
finish
使用 -e 参数,以下实例输入字符 a 后按下 Tab 键就会输出相关的文件名(该目录存在的):
$ read -e -p "输入文件名:" str
输入文件名:a
a.out a.py a.pyc abc.txt
输入文件名:a