shell编程

一、正则表达式

1. 基础正则表达式

元字符                作用

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

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

^                 匹配行首。例:^hello会匹配以hello开头的行

$                 匹配行尾。例:hello$会匹配以hello结尾的行
  
[]               匹配中括号内的任意一个字符,只匹配一个字符。例:[a-z]匹配任意一个小写字母,[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位的小写字母

 在/etc/profile.d/*.sh文件中建立别名

[root@centos2 ~]# vim /etc/profile.d/custom.sh 

alias sudo='sudo '

alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
alias grep='grep --color=auto'
alias ls='ls --color=auto'
alias l.='ls -d .* --color=auto'
alias ll='ls -l --color=auto'
alias l='ls -laF'

alias cp='cp -i'
alias mv='mv -i'
alias rm='rm -i'

alias now='date +%Y%m%d%H%M%S'

2.1 建立练习文件

[root@centos2 ~]# vim test.txt

aaaaaaaaaaa
bbbbbbbbbb
ccccccccc
dddddd
123abc
hello world
faiz
faaaiiiz kabuto
555 build wwwww joker HELLO WORLD zio

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

[root@centos2 ~]# grep "a*" test.txt 
aaaaaaaaaaa
bbbbbbbbbb
ccccccccc
dddddd
123abc
hello world
faiz
faaaiiiz kabuto
555 build wwwww joker HELLO WORLD zio

  如果正则表达式写成"aa*"代表这行字符串一定要有一个a,但是后面有没有a都可以。也就是说会匹配至少含有一个a的行

[root@centos2 ~]# grep "aa*" test.txt 
aaaaaaaaaaa
123abc
faiz
kabuto

   如果正则表达式是"aaa*",则会匹配至少包含两个连续a的字符串

[root@centos2 ~]# grep "aaa*" test.txt 
aaaaaaaaaaa
[root@centos2 ~]# grep "555*" test.txt 
555
[root@centos2 ~]# grep "5555*" test.txt 
555
[root@centos2 ~]# grep "55555*" test.txt 
[root@centos2 ~]# 
#由于此文件中并没有4个连续的5的存在,故匹配不到

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

  正则表达式"."只能匹配一个字符,这个字符可以是任意字符

[root@centos2 ~]# grep "f..z" test.txt 
faiz
#“f..z”会匹配在f和z这两个字母之间一定有两个字符的单词


[root@centos2 ~]# grep "f.*z" test.txt 
faiz
faaaiiiz
[root@centos2 ~]# grep ".*" test.txt 
aaaaaaaaaaa
bbbbbbbbbb
ccccccccc
dddddd
123abc
hello world
faiz
faaaiiiz
kabuto
555
build
wwwww
joker
HELLO WORLD
zio

2.4 "^"匹配行首,"$"匹配行尾

  "^"匹配行首,如"^f"会匹配以小写"f"开头的行

[root@centos2 ~]# grep "^f" test.txt 
faiz
faaaiiiz

  "$"匹配行尾,如"d$"会匹配以小写"d"结尾的行

[root@centos2 ~]# grep "d$" test.txt 
dddddd
hello world
build

  而"^$"会匹配空白行

[root@centos2 ~]# grep -n "^$" test.txt 

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

  "[]"会匹配中括号中指定的任意一个字符,注意只能匹配一个字符。比如[ab]不是匹配一个a字符,就是匹配一个b字符

[root@centos2 ~]# grep "f[az]iz" test.txt 
faiz

  "[0-9]"会匹配任意一个数字

[root@centos2 ~]# grep "[0-9]" test.txt 
123abc
555

  "[A-Z]"会匹配一个大写字母

[root@centos2 ~]# grep "[A-Z]" test.txt 
HELLO WORLD

  "^[a-z]"则会匹配以小写字母开头的行

[root@centos2 ~]# grep "^[a-z]" test.txt 
aaaaaaaaaaa
bbbbbbbbbb
ccccccccc
dddddd
hello world
faiz
faaaiiiz
kabuto
build
wwwww
joker
zio

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

[root@centos2 ~]# grep "^[^a-z]" test.txt 
123abc
555
HELLO WORLD

  "^[^a-zA-Z]"会匹配不以字母开头的行

[root@centos2 ~]# grep "^[^a-zA-Z]" test.txt 
123abc
555

2.7 ""转义符

[root@centos2 ~]# grep ".$" test.txt
#匹配以"."结尾的行

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

[root@centos2 ~]# grep "a{3}" test.txt 
aaaaaaaaaaa
faaaiiiz
#第一行包含有连续的三个a,故会被匹配。

  如果想要只显示三个连续的a,

[root@centos2 ~]# grep "fa{3}" test.txt 
faaaiiiz

  如果正则是"[0-9]{3}"会匹配包含连续是三个数字的行

[root@centos2 ~]# grep "[0-9]{3}" test.txt 
123abc
555

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

  "{n,}"会匹配前面的字符出现最少n次。比如"fa{3,}z"这个正则就会匹配以f开头,z结尾,中间最少有三个a的字符串,"^[0-9]{3,}[a-z]"这个正则会匹配最少有连续三个数字开头的字符串

oot@centos2 ~]# grep "^[0-9]{3,}[a-z]" test.txt 
123abc

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

[root@centos2 ~]# grep "f[a-z]{1,3}" test.txt 
faiz
faaaiiiz
[root@centos2 ~]# grep "f[a-z]{1,3}z" test.txt 
faiz

[root@centos2 ~]# grep "f[a-z]{2,3}z" test.txt 
faiz
[root@centos2 ~]# grep "f[a-z]{2,3}" test.txt 
faiz
faaaiiiz

[root@centos2 ~]# grep "f[a-z]{3,6}z" test.txt 
faaaiiiz

3. 扩展正则表达式

  在正则表达式中还可以支持一些元字符,比如"+","?","|","()"。但是grep命令默认不支持。如果想要支持这些元字符,必须使用egrep命令或grep -E选项。故这些元字符被称为扩展元字符。

扩展元字符                    作用
   +                前一个字符匹配1次或任意多次
                    如"go+gle"会匹配"gogle""google""gooogle",当前如果"o"有更多个,也能匹配
   ?               前一个字符匹配0次或1次
                    如"colou?r"可以匹配"colour""color"
   |                匹配两个或多个分支选择
                    如"was|his" 会匹配既包含"was"的行,也匹配包含"his"的行
   ()               匹配其整体为一个字符,即模式单元。可以理解为由多个单个字符组成的大字符
            如"(dog)+"会匹配“dog”、“dogdog”、“dogdogdog”等,因为被()包含的字符会被当成一个整体,但"hello (world|earth)"会匹配到"hello world""hello earth"

二、字符截取和替换命令

1. cut列截取命令

[root@centos2 ~]# cut [选项] 文件名
选项
    -f 列号            提取第几列
    -d 分隔符        按照指定分隔符分割列
    -c 字符范围      不依赖分隔符来区分列,而是通过字符范围(行首为0)来进行字段提取。"n-"表示从第n个字符到行尾;"n-m"从第n个字符到第m个字符;"-m"表示从第一个字符到第m个字符

   cut命令默认分隔符是制表符,就是"tab"键,但是对空格符的支持不好。

[root@centos2 ~/sh]# vim student.txt
ID      NAME     gender Mark
1       zs      M       88
2       ls      M       92
3       ww      M       83
[root@centos2 ~/sh]# cut -f 2 student.txt 
NAME
zs
ls
ww
#提取第二列内容

  想要提取多列,只需列号之间使用","隔开即可

[root@centos2 ~/sh]# cut -f 2,3 student.txt 
NAME     gender
zs    M
ls    M
ww    M

  cut也可以按照字符提取,需要注意"8-"代表的是提取所有行的第八个字符开始到行尾,而"10-20"代表提取所有行的第十个字符到第二十个字符,而"-8"代表提取所有行从行首到第八个字符

[root@centos2 ~/sh]# cut -c 8- student.txt 
     gender    Mark
88
92
83
#提取第八个字符开始到行尾
[root@centos2 ~/sh]# cut -d ":" -f 1,3 /etc/passwd
root:0
...
#以“:”作为分隔符,提取/etc/passwd文件的第一列和第三列
[root@centos2 ~/sh]# df -h | cut -d " " -f 1,3
文件系统 
devtmpfs 
tmpfs 
tmpfs 
tmpfs 
/dev/mapper/centos-root 
/dev/sda1 
tmpfs 
tmpfs 
/dev/sr0 

#cut对空格符的支持不好

2. awk

2.1 printf格式化输出

[root@centos2 ~/sh]# printf '输出类型输出格式' 输出内容
输出类型
    %ns        输出字符串。n是数字指代输出几个字符
    %ni        输出整数。n是数字指代输出几个数字
    %m.nf     输出浮点数。m和n是数字,指代输出的整数位数和小数位数。如%8.2f代表共输出8位数,其中2位小数,6位整数

输出格式
    a        输出警告音
            输出退格键,就是Backspace键
    f        清除屏幕
    
        换行
    
        回车,就是enter键
    	        水平输出退格键,就是tab键
    v        垂直输出退格键,就是tab键

修改student.txt文件

[root@centos2 ~/sh]# vim student.txt 
  
ID      NAME    PHP     LINUX   MYSQL   AVERAGE
1       zs      80      90      70      80
2       ls      75      85      86      82
3       ww      80      90      85      85

使用printf命令输出这个文件的内容

[root@centos2 ~/sh]# printf '%s' $(cat student.txt)
IDNAMEPHPLINUXMYSQLAVERAGE1zs809070802ls758586823ww80908585[root@centos2 ~/sh]#

printf命令如果不指定输出格式,就会把所有输出内容连在一起输出。其实文本的输出本身就是这样的,cat等文本输出命令之所以可以按照格式输出,是因为cat命令已经设定了输出格式。为了printf输出合理的格式,

[root@centos2 ~/sh]# printf '%s	 %s	 %s	 %s	 %s	 %s	 
' $(cat student.txt)
#注意printf命令的单引号中,只能识别格式输出符号,手动输入的格式是无效的
ID     NAME     PHP     LINUX     MYSQL     AVERAGE     
1     zs     80     90     70     80     
2     ls     75     85     86     82     
3     ww     80     90     85     85    

如果不把成绩当成字符串输出,而是按照整形和浮点型输出

[root@centos2 ~/sh]# printf '%i	 %s	 %i	 %i	 %i	 %8.2f	 
' $(cat student.txt | grep -v NAME) 

1     zs     80     90     70        80.00     
2     ls     75     85     86        82.00     
3     ww     80     90     85        85.00     

2.2 awk基本使用

[root@centos2 ~/sh]# awk '条件1{动作1} 条件2{动作2}...' 文件名
条件(Pattern)
    一般使用关系表达式作为条件。例
    x > 10    判断变量x是否大于10
    x == y    判断变量x是否等于变量y
    A ~ B    判断字符串A中是否包含能匹配B表达式的子字符串
    A !~ B    判断字符串A中是否不包含能匹配B表达式的子字符串
动作(Action)
    格式化输出
    流程控制语句
[root@centos2 ~/sh]# awk '{printf $2 "	" $6 "
"}' student.txt 
NAME    AVERAGE
zs    80
ls    82
ww    85
#输出第二列和第六列

截取df命令的结果

[root@centos2 ~/sh]# df -h | awk '{print $1 "	" $3}'
文件系统    已用
devtmpfs    0
tmpfs    0
tmpfs    11M
tmpfs    0
/dev/mapper/centos-root    7.5G
/dev/sda1    237M
tmpfs    0
tmpfs    44K
/dev/sr0    11G


[root@centos2 ~/sh]# du -sh student.txt | awk '{print $1 }'
4.0K
[root@centos2 ~/sh]# du -sh student.txt | awk '{print $1 }' | cut -d 'K' -f 1
4.0

2.3 awk的条件

条件类型 条件 说明
awk保留字 BEGIN 在awk程序一开始时,尚未读取任何数据之前执行。BEGIN后的动作只在程序开始时执行一次
END 在awk程序处理完所有数据,即将结束时执行。END后的动作只在程序结束时执行一次
关系运算符 > 大于
< 小于
>= 大于等于
<= 小于等于
== 等于。用于判断两个值是否相等,如果给变量赋值,只有"="号
!= 不等于
A~B 判断字符串A中是否包含能匹配B表达式的子字符串
A!~B 判断字符串A中是否不包含能匹配B表达式的子字符串
正则表达式 /正则/ 如果在"//"中可以写入字符,也可以支持正则表达式

2.3.1 BEGIN

  BEGIN是awk的保留字,是一种特殊的条件类型。BEGIN的执行时机是在"awk程序一开始时,尚未读取任何数据之前执行"。一旦BEGIN后的动作执行一次,当awk开始从文件中读入数据,BEGIN的条件就不再成立,故BEGIN定义的动作只能被执行一次。例

[root@centos2 ~/sh]# awk 'BEGIN{printf "this is a transcript 
"} {printf $2 "	" $6 "
"}' student.txt 
#awk命令只要检测不到完整的单引号不会执行
#这里定义了两个动作
#第一个动作使用BEGIN条件,所有会在读入文件数据前打印"this is a transcript"(之后执行一次)
#第二个动作会打印文件的第二列和第六列
this is a transcript NAME AVERAGE zs
80 ls 82 ww 85

2.3.2 END

  END也是awk的保留字,只不过和BEGIN刚好相反。END是在awk程序处理完所有数据,即将结束时执行。END后的动作只在程序结束时执行一次。例

[root@centos2 ~/sh]# awk 'END{printf "the end 
"} {printf $2 "	" $6 "
"}' student.txt 
#在输出结尾输入“the end”,这并不是文档本身的内容,而且只会执行一次
NAME    AVERAGE
zs    80
ls    82
ww    85
the end 

2.3.3 关系运算符

  例

[root@centos2 ~/sh]# cat student.txt | grep -v NAME 
> | awk '$6 >= 85 {printf $2 "
"}'
#使用cat输出文件内容,用grep取反包含“NAME”的行
#判断第六列(平均成绩)大于等于85分的行,如果条件成立,则打印第六列

ww

  加入了条件之后,只有条件成立动作才会执行,如果条件不满足,则动作不会执行。虽然awk是列提取命令,但是也要按行来读入。这个命令的执行过程如下:

  1)如果有BEGIN条件,则会执行BEGIN定义的动作

  2)如果没有BEGIN条件,则读入第一行,把第一行的数据一次赋予$0、$1、$2等变量。其中$0代表此行的整体数据,$1代表第一字段,$2代表第二字段

  3)依据条件类型判断动作是否执行。如果条件符合,则执行动作,否则读入下一行数据。如果没有条件,则每行都会执行动作

  4)读入下一行数据,重复执行一行步骤

  例,查看zs用户的平均成绩

[root@centos2 ~/sh]# awk '$2 ~ /zs/ {printf $6 "
"}' student.txt 
#如果第二字段中输入包含有"zs"字符,则打印第六列数据
80

  这里要注意在awk中,使用"//"包含的字符串,awk命令才会查找。也就是说字符串必须用"//"包含,awk才能正确识别。

2.3.4 正则表达式

  如果想让awk识别字符串,必须使用"//"包含。例

[root@centos2 ~/sh]# awk '/ls/ {print}' student.txt 
#打印ls的成绩
2    ls    75    85    86    82

  当使用df命令查看分区使用情况时,如果我只想查看真正的系统分区的使用状况,而不想看光盘和临时分区的使用状况,可以

[root@centos2 ~/sh]# df -h |awk '/sda[0-9]/ {print $1 "	" $5 "
"}'
#查询包含sda数字的行,并打印第一第五列

/dev/sda1    24%

2.4 awk内置变量

awk内置变量 作用
$0 代表目前awk所读入的整行数据。已知awk是一行一行读入数据的,$0就代表当前读入行的整行数据
$n 代表目前读入行的第n个字段
NF 当前行拥有的字段(列)总数
NR 当前awk所处理的行,是总数据的第几行
FS 用户定义分隔符。awk默认的分隔符是任何空格,如果想要使用其他分隔符(如":"),需要FS变量定义
ARGC 命令行参数个数
ARGV 命令行参数数组
FNR 当前文件中的当前记录数(对输入文件起始为1)
OFMT 数值的输出格式(默认为%.6g)
OFS 输出字段的分隔符(默认为空格)
ORS 输出记录分割符(默认为换行符)
RS 输入记录分隔符(默认为换行符)
[root@centos2 ~]# cat /etc/passwd |grep "/bin/bash" | 
> awk '{FS=":"} {printf $1 "	" $3 "
"}'
#查询可以登录的用户的用户名和UID

root:x:0:0:root:/root:/bin/bash    
shw    1000
sss    1001

  这里":"分隔符生效了,但是在第一行却没起作用,需加上"BEGIN"条件

[root@centos2 ~]# cat /etc/passwd |grep "/bin/bash" | 
> awk 'BEGIN{FS=":"} {printf $1 "	" $3 "
"}'
root    0
shw    1000
sss    1001
[root@centos2 ~]# cat /etc/passwd | grep "/bin/bash" | 
> awk 'BEGIN{FS=":"} {printf $1 "	" $3 "	 行号: " NR "	 列数: " NF "
"}'
#开始执行{分隔符是“:”} {输出第一字段和第三字段 输出行号(NR值) 列数(NF值)}


root    0     行号: 1     列数: 7
shw    1000     行号: 2     列数: 7
sss    1001     行号: 3     列数: 7

  只想看sshd这个伪用户的相关信息,可以这样

[root@centos2 ~]# cat /etc/passwd | 
> awk 'BEGIN{FS=":"} $1=="sshd" {printf $1 "	" $3 "	 行号: " NR "	 列数:" NF "
"}'
#可以看到sshd伪用户的UID是74,是/etc/passwd文件的第40行,此行有7列(7个字段)
sshd    74     行号: 40     列数:7

2.5 awk流程控制

  首先看一下student.txt这个文件的内容

[root@centos2 ~/sh]# cat student.txt 
ID    NAME    PHP    LINUX    MYSQL    AVERAGE
1    zs    80    90    70    80
2    ls    75    85    86    82
3    ww    80    90    85    85

  先看看如何在awk中定义变量与调用变量的值。假如我想统计LINUX成绩的总分,

[root@centos2 ~/sh]# awk 'NR==2{LINUX1=$4} NR==3{LINUX2=$4} NR==4{LINUX3=$4;totle=LINUX1+LINUX2+LINUX3;print "totle linux is " totle}' student.txt 
#统计LINUX成绩的总分

totle linux is 265

  解释一下这个命令。"NR==2{LINUX1=$4}"(条件是NR==2,动作是LINUX=$4)这句话指如果输入数据是第二行(第一行为标题行),就把第二行的第四列的值赋予变量"LINUX1"。"NR==2{LINUX1=$4}"这句话指如果输入数据是第三行,就把第三行的第四列的值赋予变量"LINUX1"。"NR==4{LINUX=$4;totle=LINUX1+LINUX2+LINUX3;print "totle linux is " totle}"("NR==4"是条件,后面{}中的都是动作)这句话指如果输入数据是第四行,就把第四行的第四列的值赋予变量"LINUX3";然后定义变量totle的值为"LINUX1+LINUX2+LINUX3";然后输出"totle linux is"关键字,然后加变量totle的值。

  在awk中,因为命令语句非常长,在输入格式时需要注意以下内容:

    多个条件{动作}可以用空格分割,也可以用回车分割

    在一个动作中,如果需要执行多个命令,需要用";"分割,或者回车分割

    在awk中,变量的赋值与调用都不需要加入"$"符

    条件中判断两个值是否相同,使用"==",以便和变量赋值进行区分

   再看一下如何实现流程控制,

[root@centos2 ~/sh]# awk '{if (NR>=2) {if ($4>=90) printf $2 " is better
"}}' student.txt 
#程序中有两个if判断,第一个判断行号大于2,第二个判断linux成绩大于等于90分

zs is better
ww is better

  其实在awk中if判断语句,完全可以直接利用awk自带的条件来取代

[root@centos2 ~/sh]# awk 'NR>=2 {test=$4} test>=90 {printf $2 " is better
"}' student.txt 
#先判断行号如果大于2,就把第四个字段赋予变量test
#再判断如果test的值大于等于90,就打印is better
zs is better
ww is better

2.6 awk函数

  awk编程也允许在编程时使用函数。awk函数的自定义方法:

function 函数名 (参数列表) {
函数体
}

  定义一个简单的函数,使用函数打印出student.txt文件的学员姓名和平均成绩

[root@centos2 ~/sh]# awk 'function test(a,b) {printf a "	" b "
"} { test($2,$6) }' student.txt 
#定义函数test,包含两个参数,函数体的内容是输出这两个参数的值
#调用函数test,并向两个参数传递值

NAME    AVERAGE
zs    80
ls    82
ww    85

2.7 awk中调用脚本

  对于小的单行程序来说,将脚本作为命令行自变量传递给awk是非常简单的,而对于多行程序就比较难处理。当程序时多行的时候,使用外部脚本是很合适的。首先在外部文件中写好脚本,然后使用awk的-f选项,使其读入脚本并且执行。

  例,先编写一个awk脚本

[root@centos2 ~/sh]# vim pass.awk
BEGIN   {FS=":"}
{ print $1 "	" $3}

  然后使用"-f"选项调用这个脚本

[root@centos2 ~/sh]# awk -f pass.awk /etc/passwd
root    0
bin    1
daemon    2
...

3. sed命令

  sed主要是用来将数据进行选取、替换、删除、新增的命令

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

  对sed命令需注意,sed所做的修改并不会直接改变文件的内容(如果是用管道符接受的命令的输入,这种情况连文件都没有),而是把修改结果只显示到屏幕上,除非使用"-i"选项才会直接修改文件。

3.1 行数据操作

  查看student.txt文件中的第二行,可使用"p"动作

[root@centos2 ~/sh]# sed '2p' student.txt 
ID    NAME    PHP    LINUX    MYSQL    AVERAGE
1    zs    80    90    70    80
1    zs    80    90    70    80
2    ls    75    85    86    82
3    ww    80    90    85    85

  "p"命令确实输出了第二行数据,但是sed命令还会把所有数据都输出一次,这是就会看到上述这个奇怪的结果。如果想要指定输出某行数据,需使用"-n"选项

[root@centos2 ~/sh]# sed -n '2p' student.txt 
1    zs    80    90    70    80

  如何删除文件的数据

[root@centos2 ~/sh]# sed '2,4d' student.txt 
#删除第二行到第四行的数据
ID    NAME    PHP    LINUX    MYSQL    AVERAGE




[root@centos2 ~/sh]# cat student.txt 
#但是文件本身并没有修改
ID    NAME    PHP    LINUX    MYSQL    AVERAGE
1    zs    80    90    70    80
2    ls    75    85    86    82
3    ww    80    90    85    85

  如何追加和插入行数据

[root@centos2 ~/sh]# sed '2a hello' student.txt 
#在第二行后加入hello
ID    NAME    PHP    LINUX    MYSQL    AVERAGE
1    zs    80    90    70    80
hello
2    ls    75    85    86    82
3    ww    80    90    85    85

  "a"会在指定行后面追加入数据,如果想要在指定行前面插入数据,则需要使用动作"i"

[root@centos2 ~/sh]# sed '2i hello world' student.txt 
#在第二行前面插入数据
ID    NAME    PHP    LINUX    MYSQL    AVERAGE
hello world
1    zs    80    90    70    80
2    ls    75    85    86    82
3    ww    80    90    85    85

  如果想要追加或插入多行数据,除最后一行外,每行的末尾都要加入""代表数据未完结。再来看看"-n"选项的作用

[root@centos2 ~/sh]# sed -n '2i hello 
> world' student.txt
hello 
world

  "-n"只查看sed命令操作的数据,而不是所有的数据。

  如何实现行数据的替换。

[root@centos2 ~/sh]# cat student.txt | sed '2c no such person'
ID    NAME    PHP    LINUX    MYSQL    AVERAGE
no such person
2    ls    75    85    86    82
3    ww    80    90    85    85

  sed命令默认情况是不会修改文件内容的,如果确定要让sed命令直接处理文件的内容,可以使用"-i"选项。该选项需谨慎使用。

[root@centos2 ~/sh]# sed -i '2c No such person' student.txt 

[root@centos2 ~/sh]# cat student.txt 
ID    NAME    PHP    LINUX    MYSQL    AVERAGE
No such person
2    ls    75    85    86    82
3    ww    80    90    85    85

3.2 字符串替换

  "c"动作是进行行替换的,仅仅想要替换行中的部分数据,需使用"s"动作,格式如下:

[root@centos2 ~/sh]# sed -i 's/旧字串/新字串/g' 文件名

  替换的格式和vim非常类似

[root@centos2 ~/sh]# cat students.txt 
ID    NAME    PHP    LINUX    MYSQL    AVERAGE
1    zs    80    90    70    80
2    ls    92    85    86    82
3    ww    80    90    85    85

[root@centos2 ~/sh]# sed -i '4s/90/92/g' students.txt 
#在第四行中,把90替换成92

[root@centos2 ~/sh]# cat students.txt 
ID    NAME    PHP    LINUX    MYSQL    AVERAGE
1    zs    80    90    70    80
2    ls    92    85    86    82
3    ww    80    92    85    85

  想把某行数据注释掉

[root@centos2 ~/sh]# sed -i '4s/^/#/g' students.txt 
#使用正则表达式,"^"代表行首
[root@centos2 ~/sh]# cat students.txt 
ID    NAME    PHP    LINUX    MYSQL    AVERAGE
1    zs    80    90    70    80
2    ls    92    85    86    82
#3    ww    80    92    85    85

  在sed中只能指定行范围

[root@centos2 ~/sh]# sed -e 's/zs//g ; s/ww//g' students.txt 
#同时把“zs”和“ww”替换为空
ID    NAME    PHP    LINUX    MYSQL    AVERAGE
1        80    90    70    80
2    ls    92    85    86    82
#3        80    92    85    85

  "-e"选项可以同时执行多个sed动作,当然如果只是执行一个动作也可以使用"-e"选项,但是没什么意义。注意,多个动作之间要用";"号或回车分割。

[root@centos2 ~/sh]# sed -e 's/zs//g 
> s/ww//g' students.txt
ID    NAME    PHP    LINUX    MYSQL    AVERAGE
1        80    90    70    80
2    ls    92    85    86    82
#3        80    92    85    85

三、字符处理命令

1. 排序命令sort

[root@centos2 ~/sh]# sort [选项] 文件名
选项
    -f                忽略小写
    -b                忽略每行前面的空白部分
    -n                以数值型进行排序,默认使用字符串型排序
    -r                反向排序
    -u                删除重复行。就是uniq命令
    -t                指定分隔符,默认分隔符是制表符
    -k n[,m]         按照指定的字段范围排序、从第n字段开始,m字段结束(默认到行尾)

  sort命令默认是用每行开头第一个字符来进行排序的,例

[root@centos2 ~/sh]# sort /etc/passwd
#排序用户信息文件
abrt:x:173:173::/etc/abrt:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
avahi:x:70:70:Avahi mDNS/DNS-SD Stack:/var/run/avahi-daemon:/sbin/nologin
bin:x:1:1:bin:/bin:/sbin/nologin
chrony:x:992:989::/var/lib/chrony:/sbin/nologin
colord:x:996:993:User for colord:/var/lib/colord:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin

  如果想反向排序,使用"-r"选项

[root@centos2 ~/sh]# sort -r /etc/passwd
#反向排序
usbmuxd:x:113:113:usbmuxd user:/:/sbin/nologin
unbound:x:991:988:Unbound DNS resolver:/etc/unbound:/sbin/nologin
tss:x:59:59:Account used by the trousers package to sandbox the tcsd daemon:/dev/null:/sbin/nologin
tcpdump:x:72:72::/:/sbin/nologin
systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
sss:x:1001:1002::/home/sss:/bin/bash
...

  想要指定排序的字段,需使用"-t"选项指定分隔符,并使用"-k"选项指定字段号,假如想要按照UID字段排序/etc/passwd文件

[root@centos2 ~/sh]# sort -t ":" -k 3,3 /etc/passwd
#指定分隔符是“:”,用第三段开头,第三段结尾排序,就是只有第三字段排序
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
shw:x:1000:1000:shw:/home/shw:/bin/bash
sss:x:1001:1002::/home/sss:/bin/bash
qemu:x:107:107:qemu user:/:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
usbmuxd:x:113:113:usbmuxd user:/:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
pulse:x:171:171:PulseAudio System Daemon:/var/run/pulse:/sbin/nologin
rtkit:x:172:172:RealtimeKit:/proc:/sbin/nologin
abrt:x:173:173::/etc/abrt:/sbin/nologin
systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
...

  仔细看发现daemon用户的UID是2,却排在下面,这是因为sort默认是按照字符排序,前面用户的UID的第一个字符都是1,所有这么排序。要想按照数字排序,请使用"-n"选项

[root@centos2 ~/sh]# sort -n -t ":" -k 3,3 /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
...

  当然"-k"选项可以直接使用"-k 3",代表从第三字段到行尾都排序(第一个字符先排序,如果一致,第二个字符再排序,直到行尾)

 2. uniq

  uniq命令是用来取消重复行的命令,其实和"sort -u"选项都是一样的。格式如下:

[root@centos2 ~/sh]# uniq [选项] 文件名
选项
    -i            忽略大小写

3. wc统计命令

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

四、条件判断

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

测试选项 作用
-b 文件 判断该文件是否存在,并且是否为块设备文件(是块设备文件为真)
-c 文件 判断该文件是否存在,并且是否为字符设备文件(是字符设备文件为真)
-d 文件 判断该文件是否存在,并且是否为目录文件(是目录为真)
-e 文件 判断该文件是否存在(存在为真)
-f 文件 判断该文件是否存在,并且是否为普通文件(是普通文件为真)
-L 文件 判断该文件是否存在,并且是否为符号链接文件(是符号链接文件为真)
-p 文件 判断该文件是否存在,并且是否为管道文件(是管道文件为真)
-s 文件 判断该文件是否存在,并且是否为非空(非空为真)
-S 文件 判断该文件是否存在,并且是否为套接字文件(是套接字文件为真)
[root@centos2 ~/sh]# [ -e /root/sh ]
[root@centos2 ~/sh]# echo $?
0
#判断结果为0,/root/sh目录存在


[root@centos2 ~/sh]# [ -e /root/test ]
[root@centos2 ~/sh]# echo $?
1
#在/root/下并没有test文件或目录,故"$?"的返回值非零

  多命令顺序执行"&&"和"||"。可以再判断一下/root/sh是否为目录

[root@centos2 ~/sh]# [ -d /root/sh ] && echo "yes" || echo "no"
yes
#第一个判断命令如果正确执行,则打印"yes",否则打印"no"

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

  test是非常完善的判断命令,还可以判断文件的权限

测试选项 作用
-r 文件 判断该文件是否存在,并且是否该文件拥有读权限(有读权限为真)
-w 文件 判断该文件是否存在,并且该文件是否拥有写权限(有写权限为真)
-x 文件 判断该文件是否存在,并且该文件是否拥有执行权限(有执行权限为真)
-u 文件 判断该文件是否存在,并且该文件是否拥有SUID权限(有SUID权限为真)
-g 文件 判断该文件是否存在,并且该文件是否拥有SGID权限(有SGID权限为真)
-k 文件 判断该文件是否存在,并且该文件是否拥有SBIT权限(有SBIT权限为真)

[root@centos2 ~/sh]# ll
总用量 12
-rw-r--r-- 1 root root 35 12月  2 21:44 pass.awk
-rw-r--r-- 1 root root 84 12月  2 22:17 students.txt
-rw-r--r-- 1 root root 81 12月  2 22:11 student.txt
[root@centos2 ~/sh]# [ -w student.txt ] && echo "yes" || echo "no"
yes
#判断该文件是否拥有写权限

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

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

  如何判断两个文件是否为硬链接

[root@centos2 ~/sh]# ln /root/sh/students.txt /tmp/stus.txt
#创建硬链接
[root@centos2 ~/sh]# [ /root/sh/students.txt -ef /tmp/stus.txt ] && echo "yes" || echo "no"
yes

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(小于等于为真)

[root@centos2 ~/sh]# [ 23 -gt 22 ] && echo "yes" || echo "no"
yes
#判断23是否大于22
[root@centos2 ~/sh]# [ 23 -le 22 ] && echo "yes" || echo "no"
no
#判断23是否小于等于22

5. 字符串的判断

测试选项 作用
-z 字符串 判断字符串是否为空(为空返回真)
-n 字符串 判断字符串是否为非空(非空返回证)
字串1 == 字串2 判断字符串1是否和字符串2相等(相等返回真)
字串1 != 字串2 判断字符串1是否和字符串2不相等(不相等返回真)

[root@centos2 ~/sh]# name=zs
#为变量赋值
[root@centos2 ~/sh]# [ -z $name ] && echo "yes" || echo "no"
no
#判断name变量是否为空,因为不为空,故返回no

如何判断两个字符串相等

[root@centos2 ~/sh]# aa=11
[root@centos2 ~/sh]# bb=22
#给变量aa、bb赋值
[root@centos2 ~/sh]# [ "$aa" == "$bb" ] && echo "yes" || echo "no"
no
#判断两个变量的值是否相等

6. 多重条件判断

测试选项 作用
判断1 -a 判断2 逻辑与,判断1和判断2都成立,最终的结果才为真
判断1 -o 判断2 逻辑或,判断1和判断2有一个成立,最终的结果为真
! 判断 逻辑非,使原始的判断式取反

[root@centos2 ~/sh]# a=11
#赋值
[root@centos2 ~/sh]# [ -n "$a" -a "$a" -gt 22 ] && echo "yes" || echo "no"
no
#判断变量a是否有值,同时判断变量a是否大于22
#因为变量a的值不大于22,所以虽然第一个判断值为真,返回的结果仍是假
[root@centos2 ~/sh]# a=23
[root@centos2 ~/sh]# [ -n "$a" -a "$a" -gt 22 ] && echo "yes" || echo "no"
yes

逻辑非

[root@centos2 ~/sh]# [ ! -n "$a" ] && echo "yes" || echo "no"
no
#变量a的值非空,返回值为真
#加!后,判断值会取反,故当a有值时,返回值是假

[root@centos2 ~/sh]# [ ! -z "$a" ] && echo "yes" || echo "no"
yes

五、流程控制

1. if条件判断

1.1 单分支if条件语句

  单分支条件语句最简单,就是只有一个判断条件,如果符合条件则执行某个程序,否则什么事情都不做。语法如下:

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

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

    if语句使用fi结尾,和一般语言使用大括号结尾不同

    [ 条件判断式 ]就是使用命令判断,中括号和条件判断式之间必须有空格

    then后面跟符合条件之后执行的程序,可以放在[]之后,用";"分割。也可以换行写入,就不需要";"了,如:

if [ 条件判断式 ]
    then
        程序
fi
[root@centos2 ~/sh]# vi if1.sh
#!/bin/bash
#author by nanshan 20191202

rate=$(df -h | grep /dev/mapper/centos-root |awk '{print $5}' | cut -d "%" -f 1)
#把根分区使用率最为变量值赋予变量rate
if [ $rate -ge 80 ];then
#判断rate的值,如果大于等于80,则执行以下程序
        echo "Warning! /dev/sda3 is full!"
        #打印警告信息。也可以向管理员发送邮件
fi
    

1.2 双分支if条件语句

if [ 条件判断式 ];then
      条件成立时,执行程序1
  else
      条件不成立时,执行程序2
fi

例,数据备份

[root@centos2 ~/sh]# vim if2.sh 
#!/bin/bash
#author by nanshan 20191202

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" >> /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

   在工作中,服务器经常会宕机。通过脚本监听本机的服务,如果服务停止或宕机了,可以自动重启这些服务。以apache为例

[root@centos2 ~/sh]# vim autostart.sh 
#!/bin/bash
#author by nanshan 20191203

PORT=$(nmap -sT 192.168.80.128 | 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/init.d/httpd start &>/dev/null
        echo "$(date) restart httpd !!" >> /tmp/autostart-acc.log
fi

nmap端口扫描命令,格式如下

[root@centos2 ~/sh]# nmap -sT 域名或IP
选项
    -s        扫描
    -T        扫描所有开启的TCP端口

1.3 多分支if条件语句

if [ 条件判断式1 ];then
    条件判断式1成立时,执行程序1
elif [ 条件判断式2 ];then
    条件判断式2成立时,执行程序2
...
else
    所有条件都不成立时,最后执行此程序
fi

  例,使用if多分支条件句判断用户输入的是一个文件还是目录

[root@centos2 ~/sh]# vim if-elif.sh
#!/bin/bash
#by author nanshan 20191203

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

if [ -z "$file" ];then
#判断变量file是否为空
        echo "ERROR,please input a filename"
        #如果为空,执行程序1,输出报错信息
        exit 1
        #退出程序,并返回值为1(返回值赋予变量$?elif [ ! -e "$file" ];then
#判断文件是否存在
        echo "$your input is not a file!"
        #如果不存在,执行程序2
        exit 2
        #退出程序,并定义返回值为2
elif [ -f "$file" ];then
#判断file变量的值是否为普通文件
        echo "$file is a regulare file."
        #如果是,则执行程序3
elif [ -d "$file" ];then
#判断变量file的值是否为目录
        echo "$file is a directory."
        #如果是,则执行程序4
else
        echo "$file is an other file!"
        #如果以上判断都不是,执行最后程序
fi

2. 多分支case条件语句

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

case $变量名 in
    "值1")
        如果变量的值等于值1,则执行程序1
        ;;
    "值2")
        如果变量的值等于值2,则执行程序2
        ;;
    ···略···
    *)
        如果变量的值不等于以上所有值,则执行此程序
        ;;
esac

  这个语句需注意下述内容

    case语句,会取出变量中的值,然后与语句体中的值逐一比较。如果数值符合,则执行对应的程序,如果数值不等,则依次比较下一个值。如果所有的值都不符合,则执行"*)" ("*"代表所有其他值)中的程序

    case语句以"case"开头,以"esac"结尾。

每个分支程序之后要通过";;"双分号结尾,代表该程序段结束(不可忘记)。

例:判断是yes或no;

[root@centos2 ~/sh]# vim case.sh 
#!/bin/bash
#by author nanshan 20191203

read -p "please choose yes or 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

3. for 循环

  for循环是固定循环,也就是循环时已经知道需要进行几次的循环,有时也把for循环称为计数循环。for循环有两种语法:

语法一:

for 变量 in 值1 值2 值3 ···
    do
        程序
    done

  这种语法中for循环的次数,取决于in后面值的个数(空格分隔),有几个就循环几次,并且每次循环都把值赋予变量。

语法二:

for (( 初始值;循环控制条件;变量变化))
    do
        程序
    done

  语法二中需要注意:

    初始值:在循环开始时,需要给某个变量赋予初始值,如i=1;

    循环控制条件:用于指定变量循环的次数,如i<=100,则只要i的值小于等于100,循环就会继续;

    变量变化:每次循环之后,变量要如何变化,如 i=i+1。代表每次循环之后,变量i的值都加1。

3.1 语法一举例

例1,打印时间

[root@centos2 ~/sh]# vim for.sh 
#!/bin/bash
#by author nanshan 20191203

for time in morning noon afternoon eveing
        do
                echo "this time is $time!"
        done

例2,批量解压缩

[root@centos2 ~/sh]# vim auto-tar.sh
#!/bin/bash
#by author nanshan 20191203

cd /lamp
#进入到压缩包目录
ls *.tar.gz > ls.log
#把所有以.tar.gz结尾的文件覆盖到ls.log临时文件中

for i in $(cat ls.log)
#读取ls.log文件中的内容,文件有多少个值,就会循环多少次,每次循环把文件名赋予变量i
        do
                tar -zxf $i &>/dev/null
                #解压,并把所有的输出丢弃
        done

rm -rf /lamp/ls.log
#删除临时文件ls.log

3.2 语法二举例

  语法二和其他语言中的for循环类似,事先决定循环次数的固定循环。

例1. 从1加到100

[root@centos2 ~/sh]# vim for2.sh
  
#!/bin/bash
#by author nanshan 20191203

s=0
for (( i=1;i<=100;i++ ))
        do
                s=$(( $s+$i ))
        done
echo "the sum of 1+2+···+100 is : $s"

例2. 批量添加用户

[root@centos2 ~/sh]# vim useradd.sh
  
#!/bin/bash
#by author nanshan 20191203

read -p "please input user name: " -t 30 name
#让用户输入用户名,把输入保存到变量name
read -p "please input the number of users: " -t 30 num
#让用户输入添加用户的数量,把输入保存到变量num
read -p "please input the password of users: " -t 30 pass
#让用户输入初始密码,把输入保存到变量pass

if [ ! -z "$name" -a ! -z "$num" -a ! -z "$pass" ];then
#判断三个变量是否为空
        y=$(echo $num | sed 's/[0-9]//g')
        #定义变量的值为后续命令的结果
        #把变量num的值替换为空。如果能替换为空,则证明num的值为数字,反之,为非全数字
        if [ -z "$y" ];then
        #如果变量y的值为空
                for (( i=1;i<=$num;i=i+1 ))
                #循环num变量指定的次数
                do
                                /usr/sbin/useradd $name$i &>/dev/null
                                #添加用户,用户名为变量name的值加上变量i的数字
                                echo $pass | /usr/sbin/passwd --stdin $name$i &>/dev/null
                                #给用户设定初始密码位变量pass的值
                        done
        fi
fi
    

例3. 批量删除用户

[root@centos2 ~/sh]# vim userdel.sh
  
#!/bin/bash
#by author nanshan 20191203

user=$(cat /etc/passwd | grep "/bin/bash" | grep -v "root" | cut -d ":" -f 1)
#读取用户信息文件,提取可以登录的用户信息,取消root用户,截取第一列用户名
for i in $user
#循环,有多少个普通用户,循环几次
do userdel -r $i done

4. while循环

while [ 条件判断式 ]
    do
        程序
    done

  对while循环来讲,只有条件判断式成立,循环就会一直继续,直到条件判断式不成立,循环才会停止。

[root@centos2 ~/sh]# vim while.sh
  
#!/bin/bash
#by author nanshan 20191203

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

echo "the sum is: $s"

5. until循环

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

until [ 条件判断式 ]
    do
        程序
    done

[root@centos2 ~/sh]# vim until.sh
  
#!/bin/bash
#by author nanshan 20191203

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

6. 函数

function 函数名 () {
    程序
}

[root@centos2 ~/sh]# vim function.sh 
#!/bin/bash


function sum () {
#定义函数
        s=0
        for (( i=0;i<=$1;i=i+1 ))
        #循环直到i大于$1为止,$1是函数sum的第一个参数
        #在函数中也可以使用位置参数变量,只是这里的$1指的是函数的第一个参数
                do
                        s=$(( $s+$i ))
                done
        echo "the sum of 1+2+3+···+$1 is: $s"
        #输出1加到$1的和
}

read -p "please input a number: " -t 30 num
#接受用户输入的数字,并把值赋予变量num
y=$(echo $num | sed 's/[0-9]//g')
#把变量num的值替换为空,并赋予变量y
if [ -z "$y" ];then
#判断y变量是否为空,以确定变量num是否为纯数字
        sum $num
        #调用sum函数,并把变量num的值作为第一个参数传递给sum函数
else
        echo "ERROR! Please input a number!"
        #如果变量num的值不是纯数字,就输出报错信息
fi

7. 特殊流程控制语句

7.1 exit语句

  系统使用exit命令的,用户退出当前用户的登录状态。在shell脚本中,exit语句是用来退出当前脚本的。就是说,在shell脚本中,只要碰到exit语句,后续的程序就不再执行,而直接退出脚本。语法如下:

exit [返回值]

  如果exit命令之后定义了返回值,那么脚本执行之后的返回值就是我们自己定义的返回值。可以通过"echo $?"这个命令查看返回值。如果exit之后没有定义返回值,脚本执行之后的返回值是执行exit语句之前,最后执行的一条命令的返回值。

[root@centos2 ~/sh]# vim exit.sh 
#!/bin/bash

read -p "please input a number: " -t 30 num

y=$(echo $num | sed 's/[0-9]//g')

[ -n "$y" ] && echo "error! please input a number!" && exit 10
#判断变量y的值,如果为空,输出报错信息,退出脚本,返回值为10

echo "the number is: $num"
#如果没有退出脚本,打印变量num中的数字

7.2 break语句

  当程序执行到break语句时,会结束整个当前循环。而continue语句时结束本次循环。

  break结束整个循环,continue结束此次循环,下次继续

[root@centos2 ~/sh]# vim break.sh
  
#!/bin/bash

for (( i=1;i<=10;i=i+1 ))
        do
                if [ "$i" -eq 4 ];then
                #如果变量i的值等于4
                        break
                #退出整个循环
                fi
                echo $i
                #输出变量i的值
        done

7.3 continue语句

  continue语句也是结束流程控制语句。在循环中,continue语句只会结束单次当前循环。

[root@centos2 ~/sh]# vim continue.sh
#!/bin/bash

for (( i=1;i<=10;i=i+1 ))
        do
                if [ "$i" -eq 4 ];then
                        continue
                        #换成continue
                fi
                echo $i
        done

  continue只会退出当前此次循环,不影响后续的循环。

原文地址:https://www.cnblogs.com/sswind/p/11929002.html