Linux 入门总结(十二) —— Shell 编程
基础正则表达式
1、正则表达式与通配符
正则表达式用来在文件中匹配符合条件的字符串,正则是包含匹配。grep、awk、sed 等命令可以支持正则表达式。
通配符用来匹配符合条件的文件名,通配符是完全匹配。ls、find、cp 这些命令不支持正则表达式,所以只能使用 Shell 自己的通配符来进行匹配了。
注意:通配符和正则表达式这种区分,仅限于 Linux 的 Shell 当中。
注意:这里是基础正则表达式,还存在扩展正则表达式。
注意:「^」位于中括号里面表示取反,位于外面表示行首。
test.txt 内容如下,将适用于以下所有用到 test.txt 的地方:
ID | Name | Gender | Score |
---|---|---|---|
1 | zhangsan | male | 86 |
2 | lisi | female | 90 |
3 | wangwu | male | 83 |
「*」示例:
1
2
3
4
5
6
7
8# 匹配所有内容,包括空白行(即 a 出现了 0 次),相当于列出整个文档
grep "a*" test.txt
# 匹配至少包含一个 a 的行
grep "aa*" test.txt
# 匹配至少包含两个 a 的行
grep "aaa*" test.txt「.」示例:
1
2
3
4
5
6
7
8# s..d 匹配在 s 和 d 之间一定有两个字符的所在行
grep "s..d" test.txt
# 匹配在 s 和 d 之间有任意字符,把「.*」当做一个整体看,表示除换行符外任意一个字符出现任意次
grep "s.*d" test.txt
# 匹配所有内容
grep ".*" test.txt「^」行首、「$」行尾示例:
1
2
3
4
5
6
7
8# 以 M 开头的行
grep "^M" test.txt
# 以 n 结尾的行
grep "n$" test.txt
# 匹配空白行
grep -n "^$" test.txt「[]」示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16注意:中括号里面是不能匹配换行符的。
# 匹配 s 和 i 之间,要么是 a、要么是 o。注意:一个中括号只匹配一个字符
grep "s[ao]id" test.txt
# 匹配任意一个数字
grep "[0-9]" test.txt
# 匹配以小写字母开头的行
grep "^[a-z]" test.txt
# 匹配不用小写字母开头的行
grep "^[^a-z]" test.txt
# 匹配不用字母开头的行
grep "^[^a-zA-Z]" test.txt「\」转义符示例
1
2
3
4注意:「.」在正则里面表示任意一个字符,所以这里要加转义符。
# 匹配使用「.」结尾的行
grep "\.$" test.txt其他示例
1
2
3
4
5
6
7
8
9
10
11# a 连续出现 3 次
grep "a\{3\}" test.txt
# 包含连续的 3 个数字的字符串所在行
grep "[0-9]\{3\}" test.txt
# 匹配最少用连续三个数字开头的行
grep "^[0-9]\{3,\}" test.txt
# 匹配在 s 和 i 之间,最少有一个 a,最多有三个 a
grep "sa\{1,3\}i" test.txt
字符截取命令
1、cut 字段提取命令「注意:cut 命令默认列之间的分隔符为 tab 键」1
2
3
4
5
6
7
8
9
10
11
12
13
14
15格式:cut [选项] 文件名
选项:
-f列号 提取第几列
-d分隔符 按照指定分隔符分割列
举例:
# 提取第二列
cut -f 2 test.txt
# 提取第二、第三列,注意不是范围,是指定的列,中间以逗号分隔
cut -f 2,3 test.txt
# 指定列之间的分隔符为「:」,默认为 tab 键
cut -d ":" -f 1,3 test.txt
注意:grep 命令表示在指定的文件中提取匹配的行,cut、awk 命令用来提取匹配的列。cut 命令中列之间的默认分割符为制表符即 tab 键。命令 grep -v [内容],表示列出不包含指定的内容的行。grep 确认行,cut 确认列,利用这两个命令可以做一些操作,比如,获取所有普通用户的用户名:cat /etc/passwd | grep /bin/bash | grep -v root | cut -d ":" -f 1
。
cut 命令有一个局限就是「无法」识别空格(指识别起来超级麻烦扩展性还差)。对于空格的判断我们使用 awk 命令,但是因为 awk 比较复杂,所以能用 cut 的尽量不要用 awk,因为 cut 简单,但是存在局限性。
2、printf 命令1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23格式:printf '输出类型输出格式' 输出内容
输出类型:
%ns 输出字符串。n 是数字,指代输出几个字符
%ni 输出整数。n 是数字,指代输出几个数字
%m.nf 输出浮点数。m 和 n 是数字,指代输出的整数位数和小数位数。
eg:%8.2f 代表共输出 8 位数,其中 2 位是小数,6 位是整数
输出格式:
\a 输出警告音
\b 输出退格键,即 Backspace 键
\f 清除屏幕
\n 换行
\r 回车,即 Enter 键
\t 水平输出 Tab 键
\v 垂直输出 Tab 键
举例:
# 注意以下命令中是否加了单引号或双引号
printf %s 1 2 3 4 5 6
printf %s %s %s 1 2 3 4 5 6
printf '%s %s %s' 1 2 3 4 5 6
printf '%s %s %s\n' 1 2 3 4 5 6
printf 是格式化输出命令,类似于 echo 命令。注意:printf 后面不能直接加文件名,也不能接受管道符的内容。但是可以使用 printf '%s' $(cat 文件名)
的形式输出命令执行结果,需要注意的是 printf 不调整输出格式。
printf 命令没有 cat 和 echo 的自动格式化功能,为什么还要学习 printf 命令,是因为在 awk 命令中不能调用系统命令 cat 或者 echo,只能使用 printf。
在 awk 命令的输出中支持 print 和 printf 命令:
print:print 会在每个输出之后自动加入一个换行符(Linux 默认没有 print 命令)。
printf:printf 是标准格式输出命令,并不会自动加入换行符,如果需要换行,需要手工加入换行符。
注意:print 只能在 awk 中使用。printf 命令在 Linux 系统中直接使用的几率不大,主要是在 awk 命令中使用。
3、awk 命令,注意单引号1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20格式:
awk '条件1{动作1} 条件2{动作2} ...' 文件名
条件:
一般使用关系表达式作为条件,如:
x > 10
x <= 10
动作:
格式化语句
流程控制语句
举例:
# $2 表示第 2 列,$4 表示第 4 列
awk '{printf $2 "\t" $4 "\n"}' test.txt
df -h | awk '{printf $1 "\t" $3 "\n"}'
典例:获取系统中根分区硬盘的使用率,可以用来报警,如果超过80则给管理员提示。
df -h | grep 只有根分区才存在的字符串 | awk '{printf $5}' | cut -d "%" -f 1
注意:awk 命令虽然是列提取命令,但是它处理数据的时候,是先读入一行数据,然后把这行数据所有的内容都复制给对应的变量,$1 代表第一列,依次类推,然后再判断条件是否符合,对满足条件的执行相应的动作。
注意:awk 默认是以空格或者制表符作为分割符的。
注意:BEGIN 必须大写。它位于大括号前,表示它是一个条件,只有满足了这个条件,其后的动作才会被执行。BEGIN 的作用是:在所有的数据读取之前,执行其后面相对应的动作。注意所有的动作都要用单引号括起来。
BEGIN 表示最先执行 BEGIN 条件后面的动作
1
2# 在进行真正的数据读取之前,即 awk '{printf $2 "\t" $4 "\n"}' test.txt,打印「This is a transcript.」这句话
awk 'BEGIN{printf "This is a transcript. \n"} {printf $2 "\t" $4 "\n"}' test.txtFS 内置变量
FS 的作用是指定分隔符的。注意:awk 命令在处理的时候是先读入第一行数据,然后再执行相应的动作。1
2
3
4
5# 输出结果中,第一行数据没有按照预期输出
awk '{FS=":"}{print $1 "\t" $3}' /etc/passwd
# 改进:读取第一条数据之前,先把分隔符变成「:」
awk 'BEGIN{FS=":"}{print $1 "\t" $3}' /etc/passwdEND 表示最后执行 END 条件后面的动作
1
2# 表示在输出 {printf $2 "\t" $4 "\n"} 动作中的内容之后,输出 {printf "The end. \n"} 动作中的内容
awk 'END{printf "The end. \n"} {printf $2 "\t" $4 "\n"}' test.txt关系运算符
1
2获取成绩大于 87 的用户名,grep -v Name 的作用是去掉标题行
cat test.txt | grep -v Name | awk '$4 >= 87 {printf $2 "\n"}'
4、sed 命令
sed 是一种几乎包括在所有 Unix 平台(包括 Linux)的轻量级流编辑器。sed 主要是用来将数据进行选取、替换、删除、新增的命令。
sed 其实不是截取命令,其实是一个流的编辑器。相当于一个编辑器。既然已经学了 vim,为什么还有学习 sed 呢?因为 vim 命令只能修改文件,不能直接修改命令结果里的内容。要想使用 vim 修改命令的输出,则需要先把输出保存到文件当中,然后再用 vim 修改文件。sed 不光可以修改文件,因为其是一个流编辑器,所以可以从管道符接受数据来进行修改,即 sed 支持管道符操作。实际操作中主要用在对命令的结果进行操作,但是在 shell 编程中,sed 是一个重要的流数据处理编辑器。
1 | 格式: |
字符处理命令
1、排序命令 sort1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22格式:
sort [选项] 文件名
选项:
-f 忽略大小写
-n 以数值型进行排序,默认使用字符串型排序
-r 反向排序
-t 指定分割符,默认分隔符是制表符
-k n[,m] 按照指定的字段范围排序。从第 n 字段开始,m 字段结束(默认到行尾)
举例:
# 排序用户信息文件
sort /etc/passwd
# 反向排序
sort -t /etc/passwd
# 指定分隔符「:」。用第三字段开头,第三字段结尾排序,就是只用第三字段排序(类比数据库 order by 后面可以跟多个字段)
sort -t ":" -k 3,3 /etc/passwd
# -n 表示将提取的字段当成数字来对待
sort -n -t ":" -k 3,3 /etc/passwd
条件判断(即测试一个条件是否成立)
1、按照文件类型进行判断(记住蓝色的就行,其它的了解)
2、两种判断格式1
2
3
4
5
6
7
8
9
10
11
12格式一:
# 注意该命令执行后没有任何输出,要想知道该命令的输出结果,可以使用 $? 来获取结果,「echo $?」打印结果
# $? 的作用是判断上一条命令是否正确执行,正确执行返回 0,否则返回其他数字
test -e /root/install.log
格式二:
# 注意两端必须有空格
[ -e /root/install.log ]
举例:
# 第一个判断命令如果正确执行,则打印 yes,否则打印 no
[ -d /root ] && echo "yes" || echo "no"
这种判断主要用在 Shell 脚本当中,而在脚本当中最常用的判断格式是用中括号。
3、按照文件权限进行判断
举例:[ -w test.txt ] && echo "yes" || echo "no"
判断文件是否拥有写权限
注意:以上权限部分不区分所有者、所属组、其他人,只要该文件有这个权限,就为真。
4、两个文件之间进行比较(不常用)
5、两个整数之间比较(常用)
-ne : (not equal) 不相等
-gt : (greater than) 大于
-lt : (less than) 小于
-ge : (greater than or equal) 大于或等于
-le : (less than or equal)小于或等于
举例:[ 23 -ge 22 ] && echo "yes" || "no"
判断 23 是否大于等于 22
6、字符串的判断(常用)
注意:== 会把参数当成字符串
7、多重条件判断
流程控制
if 语句
1、单分支 if 条件语句1
2
3
4
5
6
7
8
9
10
11# 注意:中括号内两边都有空格,注意分号不能少
if [ 条件判断式 ];then
程序
fi
或者
if [ 条件判断式 ]
then
程序
fi
单分支条件语句注意点:
if 语句使用 fi 结尾,和一般语言使用大括号结尾不同。
[ 条件判断式 ] 就是使用 test 命令判断,所以中括号和条件判断式之间必须有空格。
then 后面跟符合条件之后执行的程序,可以放在 [] 之后,用「;」分割。也可以换行写入,就不需要「;」了。
举例:判断分区使用率1
2
3
4
5
6
7
8
9
10# 统计根分区使用率
#!/bin/bash
# 把根分区使用率作为变量赋值给 rate
rate=$(df -h | grep "/dev/sda3" | awk '{printf $5}' | cut -d '%' -f 1)
if [ $rate -ge 80 ]
then
echo "Warning! /dev/sda3 is full!"
fi
2、双分支 if 条件语句1
2
3
4
5
6
7if [ 条件判断式 ]
then
条件成立时,执行的程序
else
# 注意 else 后面没有 then 了
条件不成立时,执行的另一个程序
fi
示例1:备份 mysql 数据库1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 !/bin/bash
同步系统时间。注意:必须联网才能同步系统时间
ntpdate asia.pool.ntp.org &> /dev/null
把当前系统时间按照「年月日」格式赋予变量 date
date=$(date +%y%m%d)
统计 mysql 数据库的大小,并把大小赋予 size 变量
size=$(du -sh /var/lib/mysql)
if [-d /tmp/dbbak ]
then
echo "Date: $date!" > /tmp/dbbak/dbinfo.txt
echo "Data size: $size" >> /tmp/dbbak/dbinfo.txt
cd /tmp/dbbak
# 压缩的时候源文件可以写多个,中间用空格隔开就行
tar -zcf mysql-lib-$date.tar.gz /var/lib/mysql dbinfo.txt &> /dev/null
rm -rf /tmp/dbbak/dbinfo.txt
else
mkdir /tmp/dbbak
echo "Date: $date!" > /tmp/dbbak/dbinfo.txt
echo "Data size: $size" >> /tmp/dbbak/dbinfo.txt
cd /tmp/dbbak
tar -zcf mysql-lib-$date.tar.gz /var/lib/mysql dbinfo.txt &> /dev/null
rm -rf /tmp/dbbak/dbinfo.txt
示例2:判断 apache 是否启动1
2
3
4
5
6
7
8
9
10
11
12
13 !/bin/bash
使用 nmap 命令扫描服务,并截取 apache 服务的状态,赋予变量 port
nmap 命令默认是没有装的
-sT 表示扫描指定服务器上开启的 tcp 端口
port=$(nmap -sT 127.0.0.1 | grep tcp | grep http | awk '{printf $2}')
if [ "$port" == "open" ]
then
echo "$(date) httpd is ok!" >> /tmp/autostart-acc.log
else
/etc/rc.d/init.d/httpd start &> /dev/null
echo "$(date) restart httpd!" >> /tmp/autostart-err.log
fi
如何判断系统中某个服务是否启动?此处已 apache 服务器为例。
方式1:通过 ps aux | grep httpd
查找是否有 apache 的进程在运行。但是这种判断是有点问题的,比如有些情况下就算 apache 启动了,如果它死机了,服务是死掉的,但是进程还在却不能正常相应客户的请求。所以不是特别准确。
方式2:通过 netstat -tlun
判断端口,比如 80 端口,问题同上,不能确定你的 apache 是否能正常提供访问。它只能确定 apache 是启动的,而且不仅 apache 其他应用也可能会占用 80 端口。
方式3:最有效的办法是通过 nmap 命令,是一个远程扫描命令,是一个扫描工具,用它来扫描当前计算机,如果该工具能够正确连接 apache 的端口,它就会返回 apache 的状态是 open,证明 apache 是打开的而且是可访问的。推荐使用这个,比较准确一点。
3、多分支 if 条件语句1
2
3
4
5
6
7
8
9
10
11if [ 条件判断式1 ]
then
当条件判断式 1 成立时,执行程序 1
elif [ 条件判断式2 ]
then
当条件判断式 2 成立时,执行程序 2
......
else
# 注意没有 then 了
当所有条件都不成立时,最后执行此程序
fi
举例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22# 判断用户输入的是什么文件
#!/bin/bash
# 接受键盘的输入,并赋给变量 file
read -p "Please input a filename: " file
# 判断 file 是否为空
if [ -z "$file" ]
then
echo "Error,please input a filename."
exit 1
elif [ ! -e "$file" ]
then
echo "Your input is not a file."
exit 2
elif [ -f "$file" ]
then
echo "$file is a regulare file."
elif [ -d "$file" ]
then
echo "$file is a director."
else
echo "$file is an other file."
fi
注意:对于 if else 中的报错程序,如果报错了一定要退出程序,否则程序还会继续向下执行。
case 语句
多分支 case 条件语句:case 语句和 if…elif…else 语句一样都是多分支条件语句,不过和 if 多分支条件语句不通的是,case 语句只能判断一种条件关系,而 if 语句可以判断多种条件关系。
1 | case $变量名 in |
举例:1
2
3
4
5
6
7
8
9
10
11
12
13#!/bin/bash
read -p "Please choose yes/no: " -t 30 cho
case $cho in
"yes")
echo "Your choose is yes."
;;
"no")
echo "Your choose is no."
;;
*)
echo "Your choose is error."
;;
esac
for 循环
1、语法一1
2
3
4
5# 值之间的分隔符为空格
for 变量 in 值1 值2 值3 ...
do
程序
done
注意:后面有几个值,for 循环执行几次,每次执行都会把值赋给变量。注意:in 后面的值不管是空格还是换行,只要是分开的,系统就把它当场一个内容即一个值。
举例:1
2
3
4
5#!/bin/bash
for time in morning noon afternoon evening
do
echo "This time is $time."
done
这种循环很笨,但是还存在的理由:就是更加利于系统管理,因为 in 后面的值可以从内容中获取,即循环的次数可以不确定,比如下面的例子:1
2
3
4
5
6
7
8
9#!/bin/bash
# 批量解压缩脚本
cd /test
ls *.tar.gz > ls.log
for i in $(cat ls.log)
do
tar -zxf $i &> /dev/null
done
rm -rf /test/ls.log
2、语法二1
2
3
4for ((初始值;循环控制条件;变量变化))
do
程序
done
在 Linux 中,只有用双小括号括起来,才可以进行加减乘除这种数值运算。
两种循环的区别:第一种循环不知道循环次数,第二种循环知道循环次数。
示例1:1
2
3
4
5
6
7
8#!/bin/bash
# 从 1 加到 100
s = 0
for (( i=1;i<=100;i=i+1 ))
do
s=$(( $s+$i ))
done
echo "The sum result is: + $s"
示例2:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20#!/bin/bash
# 批量添加指定数量的用户
read -p "Please input username: " -t 30 name
read -p "Please input the number of users: " -t 30 number
read -p "Please input the password of users: " -t 30 password
if [ ! -z "$name" -a ! -z "$number" -a ! -z "$password" ]
then
# 判断 number 是否合法,即是否为纯数字
# 把以任意数字开头且以任意数字结尾的替换为空,即将纯数字替换为空
y = $(echo $number | sed 's/^[0-9]*$//g')
if [-z "$y"]
then
for (( i=1;i<=$number;i=i+1 ))
do
/usr/sbin/useradd $name$i $>/dev/null
echo $password | /usr/bin/passwd --stdin $name$i &>/dev/null
done
fi
fi
while 循环
1、while 循环是不定循环,也称作条件循环。只要条件判断式成立,循环就会一直继续,直到条件判断式不成立,循环才会停止。这就和 for 的固定循环不太一样了。注意:在 while 的执行语句中需要对其条件判断式不断改变,否则要是永远成立,则死循环了。
1 | while [ 条件判断式 ] |
举例:1
2
3
4
5
6
7
8
9
10#!/bin/bash
# 从 1 加到 100
i=1
s=0
while [ $i -le 100 ]
do
s=$(( $s+$i ))
i=$(( $i+1 ))
done
echo "The sum is: $s"
2、until 循环,和 while 循环相反,until 循环时只要条件判断式不成立则进行循环,并执行循环程序。一旦循环条件成立,则终止循环。
1 | until [ 条件判断式 ] |
举例:1
2
3
4
5
6
7
8
9
10#!/bin/bash
# 从 1 加到 100
i=1
s=0
until [ $i -gt 100 ]
do
s=$(( $s+$i ))
i=$(( $i+1 ))
done
echo "The sum is: $s"
注意:Shell 是脚本语言,脚本语言的好处就是所见即所得,即写完的所有脚本语言都不需要编译就可以直接运行,但是它不是不需要编译,而是在执行的同时进行编译,省略简化了编译过程,好处是编程更加简单,坏处是效率要更慢。Shell 语言不适合进行大量的数据运算,其最大的作用是帮助管理员减少重复操作。