兄弟连Linux教程(李明,沈超) 第11章 Shell编程


兄弟连Linux教程(李明,沈超) 第11章 Shell编程


正文

11.1 基础正则表达式

1、正则表达式与通配符

正则表达式用来在文件中匹配符合条件的字符串,正则是包含匹配。grep、awk、sed等命令可以支持正则表达式。

通配符用来匹配符合条件的文件名,通配符是完全匹配。ls、find、cp这些命令不支持正则表达式,所以只能使用shell自己的通配符来进行匹配了。

2、基础正则表达式

元字符    作 用
*    前一个字符匹配0次或任意多次。
.    匹配除了换行符外任意一个字符。
^    匹配行首。例如:^hello会匹配以hello开头的行。
$    匹配行尾。例如:hello&会匹配以hello结尾的行。
[]    匹配中括号中指定的任意一个字符,只匹配一个字符。
      例如:[aoeiu] 匹配任意一个元音字母,[0-9] 匹配任意一位数字, [a-z][0-9]匹配小写字和一位数字构成的两位字符。
[^]    匹配除中括号的字符以外的任意一个字符。例如:[^0-9] 匹配任意一位非数字字符,[^a-z] 表示任意一位非小写字母。
\    转义符。用于取消讲特殊符号的含义取消。
\{n\}    表示其前面的字符恰好出现n次。例如:[0-9]\{4\} 匹配4位数字,[1][3-8][0-9]\{9\} 匹配手机号码。
\{n,\}    表示其前面的字符出现不小于n次。例如: [0-9]\{2,\} 表示两位及以上的数字。
\{n,m\}    表示其前面的字符至少出现n次,最多出现m次。例如: [a-z]\{6,8\} 匹配6到8位的小写字母。

* 前一个字符匹配0次,或任意多次

grep "a*" test_rule.txt 
#匹配所有内容,包括空白行
grep "aa*" test_rule.txt
#匹配至少包含有一个a的行
grep "aaa*" test_rule.txt 
匹配最少包含两个连续a的字符串
grep "aaaaa*" test_rule.txt
#则会匹配最少包含四个个连续a的字符串

. 匹配除了换行符外任意一个字符

grep  "s..d" test_rule.txt 
#“s..d”会匹配在s和d这两个字母之间一定有两个字符的单词
grep "s.*d" test_rule.txt 
#匹配在s和d字母之间有任意字符
grep ".*" test_rule.txt 
#匹配所有内容

^ 匹配行首,“$”匹配行尾

grep "^M" test_rule.txt
#匹配以大写“M”开头的行
grep "n$" test_rule.txt
#匹配以小写“n”结尾的行
grep -n "^$" test_rule.txt
#会匹配空白行

[] 匹配中括号中指定的任意一个字符,只匹配一个字符

grep "s[ao]id" test_rule.txt
#匹配s和i字母中,要不是a、要不是o
grep "[0-9]" test_rule.txt
#匹配任意一个数字
grep "^[a-z]" test_rule.txt
#匹配用小写字母开头的行

[^] 匹配除中括号的字符以外的任意一个字符

grep "^[^a-z]" test_rule.txt 
#匹配不用小写字母开头的行
grep "^[^a-zA-Z]" test_rule.txt
#匹配不用字母开头的行

\ 转义符

grep "\.$" test_rule.txt
#匹配使用“.”结尾的行

\{n\} 表示其前面的字符恰好出现n次

grep "a\{3\}" test_rule.txt
#匹配a字母连续出现三次的字符串
grep "[0-9]\{3\}" test_rule.txt
#匹配包含连续的三个数字的字符串

\{n,\} 表示其前面的字符出现不小于n次

grep "^[0-9]\{3,\}[a-z]" test_rule.txt
#匹配最少用连续三个数字开头的行

\{n,m\} 匹配其前面的字符至少出现n次,最多出现m次

grep "sa\{1,3\}i" test_rule.txt
#匹配在字母s和字母i之间有最少一个a,最多三个a

11.2 字符截取命令

11.2.1 cut字段提取命令

[root@localhost ~]# cut [选项] 文件名
选项:
    -f 列号:        提取第几列
    -d 分隔符:        按照指定分隔符分割列
[root@localhost ~]# vi student.txt
ID      Name    gender  Mark
1        Liming  M         86
2        Sc          M         90
3        Gao        M         83
[root@localhost ~]# cut -f 2 student.txt

[root@localhost ~]# cut -f 2,3 student.txt

[root@localhost ~]# cut -d ":" -f 1,3 /etc/passwd

cut命令的局限

[root@localhost ~]# df -h | cut -d " " -f 1,3

11.2.2 printf命令

printf  ’输出类型输出格式’    输出内容
输出类型:
    %ns:        输出字符串。n是数字指代输出几个字符
    %ni:        输出整数。n是数字指代输出几个数字
    %m.nf:        输出浮点数。m和n是数字,指代输出的整数位数和小数位数。如%8.2f代表共输出8位数,其中2位是小数,6位是整数。
输出格式:
    \a:            输出警告声音
    \b:            输出退格键,也就是Backspace键
    \f:            清除屏幕
    \n:            换行
    \r:            回车,也就是Enter键
    \t:            水平输出退格键,也就是Tab键
    \v:            垂直输出退格键,也就是Tab键
[root@localhost ~]# printf %s 1 2 3 4 5 6
123456
[root@localhost ~]# printf %s %s %s 1 2 3 4 5 6
%s%s123456
[root@localhost ~]# printf  '%s %s %s' 1 2 3 4 5 6
1 2 34 5 6
[root@localhost ~]# printf '%s %s %s\n' 1 2 3 4 5 6
1 2 3
4 5 6
[root@localhost ~]# vi student.txt 
ID      Name      PHP     Linux   MySQL   Average
1       Liming     82         95         86            87.66
2       Sc             74         96         87            85.66
3       Gao           99         83         93           91.66
printf  '%s' $(cat student.txt)
#不调整输出格式
IDNamePHPLinuxMySQLAverage1Liming82958687.662Sc74968785.663Gao99839391.66

printf '%s\t %s\t %s\t %s\t %s\t %s\t \n' $(cat student.txt)
#调整格式输出
ID       Name    PHP     Linux   MySQL   Average
1        Liming  82      95      86      87.66
2        Sc      74      96      87      85.66
3        Gao     99      83      93      91.66

在awk命令的输出中支持print和printf命令

  • print:print会在每个输出之后自动加入一个换行符(Linux默认没有print命令)
  • printf:printf是标准格式输出命令,并不会自动加入换行符,如果需要换行,需要手工加入换行符

11.2.3 awk命令

# awk ‘条件1{动作1} 条件2{动作2}…’ 文件名
条件(Pattern):
    一般使用关系表达式作为条件
    x > 10    判断变量 x是否大于10
    x>=10    大于等于
    x<=10    小于等于
动作(Action):
    格式化输出
    流程控制语句
[root@localhost ~]# vi student.txt 
ID      Name      PHP     Linux   MySQL   Average
1        Liming    82        95         86             87.66
2        Sc            74        96         87             85.66
3        Gao          99        83        93              91.66
# awk '{printf $2 "\t" $6 "\n"}' student.txt 
输出:
Name    Average
Liming  87.66
Sc      85.66
Gao     91.66

# df -h | awk '{print $1 "\t" $3}'
输出:
文件系统        已用
devtmpfs        0
tmpfs   0
tmpfs   8.9M
tmpfs   0
/dev/mapper/centos-root 4.1G
/dev/sda1       194M
develop 50G
tmpfs   0
overlay 4.1G
overlay 4.1G

BEGIN

# awk 'BEGIN{printf "This is a transcript \n" } 
{printf $2 "\t" $6 "\n"}' student.txt

明细:

[root@10 sf_develop]# awk 'BEGIN{printf "This is a transcript \n" }
> {printf $2 "\t" $6 "\n"}' student.txt
This is a transcript
Name    Average
Liming  87.66
Sc      85.66
Gao     91.66
[root@10 sf_develop]#

END

# awk 'END{printf "The End \n" }                            
{printf $2 "\t" $6 "\n"}' student.txt

明细:

[root@10 sf_develop]# awk 'END{printf "The End \n" }
> {printf $2 "\t" $6 "\n"}' student.txt
Name    Average
Liming  87.66
Sc      85.66
Gao     91.66
The End
[root@10 sf_develop]#

FS内置变量

# cat /etc/passwd | grep "/bin/bash" | \
awk 'BEGIN {FS=":"} {printf $1 "\t" $3 "\n"}'

明细:

[root@10 sf_develop]# cat /etc/passwd | grep "/bin/bash" | \
> awk 'BEGIN {FS=":"} {printf $1 "\t" $3 "\n"}'
root    0
admin   1000
[root@10 sf_develop]#

关系运算符

在awk命令的输出中支持print和printf命令
print:print会在每个输出之后自动加入一个换行符(Linux默认没有print命令)
printf:printf是标准格式输出命令,并不会自动加入换行符,如果需要换行,需要手工加入换行符

11.2.4 sed命令

sed命令

sed 是一种几乎包括在所有 UNIX 平台(包括 Linux)的轻量级流编辑器。sed主要是用来将数据进行选取、替换、删除、新增的命令。

[root@localhost ~]# sed [选项] ‘[动作]’ 文件名
选项:
    -n:        一般sed命令会把所有数据都输出到屏幕,如果加入此选择,则只会把经过sed命令处理的行输出到屏幕。
    -e:        允许对输入数据应用多条sed命令编辑
    -i:        用sed的修改结果直接修改读取数据的文件,而不是由屏幕输出
动作:
    a \:        追加,在当前行后添加一行或多行。添加多行时,除最后 一行外,每行末尾需要用“\”代表数据未完结。
    c \:        行替换,用c后面的字符串替换原数据行,替换多行时,除最后一行外,每行末尾需用“\”代表数据未完结。
    i \:        插入,在当期行前插入一行或多行。插入多行时,除最后一行外,每行末尾需要用“\”代表数据未完结。
    d:        删除,删除指定的行。
    p:        打印,输出指定的行。
    s:        字串替换,用一个字符串替换另外一个字符串。格式为“行范围s/旧字串/新字串/g”(和vim中的替换格式类似)。

学生成绩表

[root@localhost ~]# vi student.txt 
ID      Name      PHP     Linux   MySQL   Average
1        Liming    82        95         86             87.66
2        Sc            74        96         87             85.66
3        Gao         99        83         93             91.66

行数据操作

[root@localhost ~]# sed '2p' student.txt 
#查看文件的第二行

ID      Name      PHP     Linux   MySQL   Average
1        Liming    82        95         86             87.66
1        Liming    82        95         86             87.66
2        Sc            74        96         87             85.66
3        Gao         99        83         93             91.66

[root@localhost ~]# sed -n '2p' student.txt
1        Liming    82        95         86             87.66
[root@localhost ~]# sed '2,4d' student.txt 
#删除第二行到第四行的数据,

[root@localhost ~]# sed '2a hello' student.txt
#在第二行后追加hello

[root@localhost ~]# sed '2i hello \
world' student.txt
#在第二行前插入两行数据
# sed '2c No such person‘ student.txt
#数据替换

字符串替换

# sed 's/旧字串/新字串/g' 文件名

# sed '3s/74/99/g' student.txt 
#在第三行中,把74换成99
#sed -i '3s/74/99/g' student.txt 
#sed操作的数据直接写入文件

# sed -e 's/Liming//g ; s/Gao//g' student.txt 
#同时把“Liming”和“Gao”替换为空

11.3 字符处理命令

1、排序命令sort

[root@localhost ~]# sort [选项] 文件名
选项:
    -f:        忽略大小写
    -n:        以数值型进行排序,默认使用字符串型排序
    -r:        反向排序
    -t:        指定分隔符,默认是分隔符是制表符
    -k n[,m]:    按照指定的字段范围排序。从第n字段开始,        m字段结束(默认到行尾)
[root@localhost ~]# sort /etc/passwd
#排序用户信息文件

[root@localhost ~]# sort -r /etc/passwd
#反向排序
[root@localhost ~]# sort -t ":" -k 3,3 /etc/passwd
#指定分隔符是“:”,用第三字段开头,第三字段结尾排序,就是只用第三字段排序

[root@localhost ~]# sort -n -t ":" -k 3,3 /etc/passwd

2、统计命令wc

[root@localhost ~]# wc [选项] 文件名
选项:
    -l:    只统计行数
    -w:    只统计单词数
    -m:    只统计字符数

11.4 条件判断

1、按照文件类型进行判断

测试选项    作用
-b 文件    判断该文件是否存在,并且是否为块设备文件(是块设备文件为真)
-c文件    判断该文件是否存在,并且是否为字符设备文件(是字符设备文件为真)
-d 文件    判断该文件是否存在,并且是否为目录文件(是目录为真)
-e 文件    判断该文件是否存在(存在为真)
-f 文件    判断该文件是否存在,并且是否为普通文件(是普通文件为真)
-L 文件    判断该文件是否存在,并且是否为符号链接文件(是符号链接文件为真)
-p 文件    判断该文件是否存在,并且是否为管道文件(是管道文件为真)
-s 文件    判断该文件是否存在,并且是否为非空(非空为真)
-S 文件    判断该文件是否存在,并且是否为套接字文件(是套接字文件为真)

两种判断格式

[root@localhost ~]# test -e /root/install.log

[root@localhost ~]# [ -e /root/install.log ]
[ -d /root ] && echo "yes" || echo "no" 
#第一个判断命令如果正确执行,则打印“yes”,否则打印“no”

2、按照文件权限进行判断

测试选项    作 用
-r 文件    判断该文件是否存在,并且是否该文件拥有读权限(有读权限为真)
-w 文件    判断该文件是否存在,并且是否该文件拥有写权限(有写权限为真)
-x 文件    判断该文件是否存在,并且是否该文件拥有执行权限(有执行权限为真)
-u 文件    判断该文件是否存在,并且是否该文件拥有SUID权限(有SUID权限为真)
-g 文件    判断该文件是否存在,并且是否该文件拥有SGID权限(有SGID权限为真)
-k 文件    判断该文件是否存在,并且是否该文件拥有SBit权限(有SBit权限为真)
[ -w student.txt ] && echo "yes" || echo "no"                  
#判断文件是拥有写权限的

3、两个文件之间进行比较

测试选项           作 用
文件1 -nt 文件2    判断文件1的修改时间是否比文件2的新(如果新则为真)
文件1 -ot 文件2    判断文件1的修改时间是否比文件2的旧(如果旧则为真)
文件1 -ef 文件2    判断文件1是否和文件2的Inode号一致,可以理解为两个文件是否为同一个文件。这个判断用于判断硬链接是很好的方法
ln /root/student.txt /tmp/stu.txt
#创建个硬链接吧

[ /root/student.txt -ef /tmp/stu.txt ] && echo "yes" || echo "no"                 
yes
#用test测试下,果然很有用

4、两个整数之间比较

测试选项           作 用
整数1 -eq 整数2    判断整数1是否和整数2相等(相等为真)
整数1 -ne 整数2    判断整数1是否和整数2不相等(不相等位置)
整数1 -gt 整数2    判断整数1是否大于整数2(大于为真)
整数1 -lt 整数2    判断整数1是否小于整数2(小于位置)
整数1 -ge 整数2    判断整数1是否大于等于整数2(大于等于为真)
整数1 -le 整数2    判断整数1是否小于等于整数2(小于等于为真)
[ 23 -ge 22 ] && echo "yes" || echo "no"                                  
yes
#判断23是否大于等于22,当然是了

[ 23 -le 22 ] && echo "yes" || echo "no" 
no
#判断23是否小于等于22,当然不是了

5、字符串的判断

测试选项       作 用
-z 字符串    判断字符串是否为空(为空返回真)
-n 字符串    判断字符串是否为非空(非空返回真)
字串1 ==字串2    判断字符串1是否和字符串2相等(相等返回真)
字串1 != 字串2    判断字符串1是否和字符串2不相等(不相等返回真)
name=sc
#给name变量赋值
[ -z "$name" ] && echo "yes" || echo "no" 
no
#判断name变量是否为空,因为不为空,所以返回no
aa=11
bb=22
#给变量aa和变量bb赋值
[ "$aa" == "bb" ] && echo "yes" || echo "no"           
no
#判断两个变量的值是否相等,明显不相等,所以返回no

6、多重条件判断

测试选项    作        用
判断1 -a 判断2    逻辑与,判断1和判断2都成立,最终的结果才为真
判断1 -o 判断2    逻辑或,判断1和判断2有一个成立,最终的结果就为真
! 判断    逻辑非,使原始的判断式取反
aa=11
[ -n "$aa"  -a "$aa" -gt 23 ] && echo "yes" || echo "no"
no
#判断变量aa是否有值,同时判断变量aa的是否大于23
#因为变量aa的值不大于23,所以虽然第一个判断值为真,返回的结果也是假
aa=24
[ -n "$aa"  -a "$aa" -gt 23 ] && echo "yes" || echo "no"
yes

11.5 流程控制

11.5.1 if语句

1、 单分支if条件语句

if [ 条件判断式 ];then
    程序
fi
或者
if [ 条件判断式 ]
    then
        程序
fi

单分支条件语句需要注意几个点

  • if语句使用fi结尾,和一般语言使用大括号结尾不同
  • [ 条件判断式 ]就是使用test命令判断,所以中括号和条件判断式之间必须有空格
  • then后面跟符合条件之后执行的程序,可以放在[]之后,用“;”分割。也可以换行写入,就不需要“;”了

例子:判断分区使用率

#!/bin/bash
#统计根分区使用率
# Author: shenchao (E-mail: shenchao@lampbrother.net)

rate=$(df -h | grep "/dev/sda3" | awk '{print $5}' | cut -d "%" -f1)
#把根分区使用率作为变量值赋予变量rate

if [ $rate -ge 80 ]
    then
        echo "Warning! /dev/sda3 is full!!"
    fi

2、双分支if条件语句

if [ 条件判断式 ]
    then
        条件成立时,执行的程序
    else
        条件不成立时,执行的另一个程序
fi

例子1:备份mysql数据库

#!/bin/bash
#备份mysql数据库。
# Author: shenchao (E-mail: shenchao@lampbrother.net)

ntpdate asia.pool.ntp.org &>/dev/null
#同步系统时间
date=$(date +%y%m%d)
#把当前系统时间按照“年月日”格式赋予变量date
size=$(du -sh /var/lib/mysql)
#统计mysql数据库的大小,并把大小赋予size变量
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
fi

例子2:判断apache是否启动

#!/bin/bash
# Author: shenchao (E-mail: shenchao@lampbrother.net)

port=$(nmap -sT 192.168.1.156 | grep tcp | grep http | awk '{print $2}')
#使用nmap命令扫描服务器,并截取apache服务的状态,赋予变量port
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

3、 多分支if条件语句

if [ 条件判断式1 ]
    then
        当条件判断式1成立时,执行程序1
elif [ 条件判断式2 ]
    then
        当条件判断式2成立时,执行程序2
…省略更多条件…
else
    当所有条件都不成立时,最后执行此程序
fi
#!/bin/bash
#判断用户输入的是什么文件
# Author: shenchao (E-mail: shenchao@lampbrother.net)

read -p "Please input a filename: " file
#接收键盘的输入,并赋予变量file

if [ -z "$file" ]
#判断file变量是否为空
    then
        echo "Error,please input a filename"
        exit 1
elif [ ! -e "$file" ]
#判断file的值是否存在
    then
        echo "Your input is not a file!"
        exit 2
elif [ -f "$file" ]
#判断file的值是否为普通文件
    then
        echo "$file is a regulare file!"
elif [ -d "$file" ]
#判断file的值是否为目录文件
    then
        echo "$file is a directory!"
else
        echo "$file is an other file!"
fi

11.5.2 case语句

多分支case条件语句

case语句和if…elif…else语句一样都是多分支条件语句,不过和if多分支条件语句不同的是,case语句只能判断一种条件关系,而if语句可以判断多种条件关系。

case $变量名 in
    "值1")
        如果变量的值等于值1,则执行程序1
        ;;
    "值2")
        如果变量的值等于值2,则执行程序2
        ;;
    …省略其他分支…
    *)
        如果变量的值都不是以上的值,则执行此程序
        ;;
esac
#!/bin/bash
#判断用户输入
# Author: shenchao (E-mail: shenchao@lampbrother.net)
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

11.5.3 for循环

语法一

for 变量 in 值1 值2 值3…
    do
        程序
    done
#!/bin/bash
#打印时间
# Author: shenchao (E-mail: shenchao@lampbrother.net)

for time in morning noon afternoon evening
        do
                echo "This time is $time!"
        done
#!/bin/bash
#批量解压缩脚本
# Author: shenchao (E-mail: shenchao@lampbrother.net)

cd /lamp
ls *.tar.gz > ls.log
for i in $(cat ls.log)
        do
                tar -zxf $i &>/dev/null
        done
rm -rf /lamp/ls.log

语法二

for (( 初始值;循环控制条件;变量变化 ))
    do
        程序
    done
#!/bin/bash
#从1加到100
# Author: shenchao (E-mail: shenchao@lampbrother.net)

s=0
for (( i=1;i<=100;i=i+1 ))
        do
                s=$(( $s+$i ))
        done
echo "The sum of 1+2+...+100 is : $s"
#!/bin/bash
#批量添加指定数量的用户
# Author: shenchao (E-mail: shenchao@lampbrother.net)
read -p "Please input user name: " -t 30 name
read -p "Please input the number of users: " -t 30 num 
read -p "Please input the password of users: " -t 30 pass
if [ ! -z "$name" -a ! -z "$num" -a ! -z "$pass" ]
        then
        y=$(echo $num | sed 's/[0-9]//g')
            if [ -z "$y" ]
                then
                for (( i=1;i<=$num;i=i+1 ))
                    do  
                        /usr/sbin/useradd $name$i &>/dev/null
                 echo $pass | /usr/bin/passwd --stdin $name$i &>/dev/null
             done
        fi  
fi

11.5.4 while循环

1、while循环

while循环是不定循环,也称作条件循环。只要条件判断式成立,循环就会一直继续,直到条件判断式不成立,循环才会停止。这就和for的固定循环不太一样了。

while [ 条件判断式 ]
    do
        程序
    done
#!/bin/bash
#从1加到100
# Author: shenchao (E-mail: shenchao@lampbrother.net)

i=1
s=0
while [ $i -le 100 ]
#如果变量i的值小于等于100,则执行循环
        do
                s=$(( $s+$i ))
                i=$(( $i+1 ))
        done
echo "The sum is: $s"

2、until循环

until循环,和while循环相反,until循环时只要条件判断式不成立则进行循环,并执行循环程序。一旦循环条件成立,则终止循环。

until [ 条件判断式 ]
    do
        程序
    done
#!/bin/bash
#从1加到100
# Author: shenchao (E-mail: shenchao@lampbrother.net)

i=1
s=0
until [ $i -gt 100 ]
#循环直到变量i的值大于100,就停止循环
        do
                s=$(( $s+$i ))
                i=$(( $i+1 ))
        done
echo "The sum is: $s"






参考资料


返回