Bootstrap

Shell编程

目录

1、概念

2、shell脚本

3、变量

(1)、系统预定义变量

(2)、自定义变量

(3)、特殊变量

1)、$n

2)、$#

3)、$*、$@

4)、$?

正在上传…重新上传取消

4、运算符

5、条件判断

1)、语法结构:

2)、常用的判断条件

两个整数之间比较

按照文件权限进行判断

​​​​​​​按照文件类型进行判断

​​​​​​​多条件判断

6、流程控制

1)、if判断

2)、case语句

3)、for 循环

4)、while 循环

7、读取控制台的输入

8、函数

(1)、系统函数

1)、basename 获取文件名

2)、dirname 获取文件的路径

(2)、自定义函数

9、正则表达式

(1)、常规匹配

(2)、常用特殊字符

1)、特殊字符:^

2)、特殊字符:$

3)、特殊字符:.

4)、特殊字符:*

5)、特殊区间(中括号):[ ]

6)、特殊字符:\

10、文本处理工具

(1)、cut

(2)、awk

11、综合应用案例


1、概念

Shell就是一个命令行解释器,它接收应用程序/用户命令,然后调用操作系统内核。

----使用cat /etc/shells 查看shell的解析器

----使用ls -l /bin/ | grep bash或者echo $SHELL,可以查看当前默认的解析器

(目前我们学习的shell编程大部分都是使用bass解析器)

2、shell脚本

·Shell脚本首行为#!/bin/sh  ---> 当前默认使用的解析器

·shell脚本的拓展名为sh

​​​​​​​​​​​​​​

问题1:在赋权操作后,执行脚本文件hello.sh,为什么会显示未找到命令?

原因:系统把hello.sh当成命令了,但是调用命令时,该命令不存在于/bin目录下

解决办法:

  1. 使用绝对路径调用该脚本
  2. 使用相对路径调用该脚本
  3. ./hello.sh
  4. 将脚本存放在/bin目录下
  5. source hello.sh
  6. . hello.sh

问题2:./hello.sh与. hello.sh的区别?

答:“./hello.sh”中的“./”表示当前目录下,该调用脚本的方法相当于使用相对路径调用脚本

“. hello.sh”中的“.”是命令

3、变量

(1)、系统预定义变量

$HOME、$PWD、$SHELL、$USER等

使用set看到当前定义的所有变量以及函数

(2)、自定义变量

1)、基本语法

  1. 定义变量:变量名=变量值
  2. 撤销变量:unset 变量名
  3. 申明静态变量:readonly 变量,注意:不能unset

当我们使用shell进行运算时

第一种方式:

a=$((1+5))

第二种方式:

b=$[4+6]

定义常量:

readonly b=5

注意:之后我们再对b进行复制会报-bash:b:只读变量

2)、命名规则

  1. 变量名称可以由字母、数字和下划线组成,但是不能以数字开头,环境变量名建议大写
  2. 等号两侧不能有空格
  3. 在bash中,变量默认类型都是字符串类型,无法直接进行数值运算
  4. 变量的值如果有空格,需要使用双引号或单引号括起来

(3)、特殊变量

1)、$n

功能:n为数字,$0代表脚本名称,$1-$9代表第一到第九个参数,十以上的参数需要使用大括号包含,如${10}

例如:

·创建一个parameter.sh

·脚本内容:

#!/bin/sh

echo ‘============$n===============’ 

#单引号中的内容将原封不动的输出,如果为双引号则会传入参数

echo $0  #$0表示脚本的名称

Echo $1

Echo $2

Echo ’======== ====$#===============’

Echo $#        ##号表示传入参数的个数

 ·在控制台输入:

./parameter.sh abc def

·输出结果:

./parameter.sh

abc

def

2

2)、$#

功能:获取所有传入参数的个数,常用于循环,判断参数的个数是否正确以及加强脚本的健壮性。

3)、$*、$@

功能:

$*:这个变量代表命令中所有参数,$*把所有的参数看成一个整体

$@:这个变量也代表命令行中所有参数,不过$@把每个参数区分对待

4)、$?

功能:最后一次执行的命令的返回状态。如果这个变量的值为0,证明上一个命令正确执行;如果这个给变量的值为非0(具体值是什么,由命令自己决定),则证明上一个命令执行不正确。

 

4、运算符

上述我们提到过数值运算。

按上述所说的两种方式中,运算符之间不能有空格。

  1. 第三种方式:

exper 1 + 4

因为此处调用exper函数,运算符两边

需要有空格,表示传入三个参数,进行运算。

当我们在进行乘法运算时,不能直接传入*,而是写成\*。

exper 1 \* 5

原因:在Linux中*作为通配符

当我们使用exper进行运算并赋值时

a=exper 1 \* 5 (错误)

而是

a=$(exper 1 \* 5)

也可以使用反引号

a=`exper 1 \* 5`

随后打印结果
echo $a

例如:编写一个加法脚本

#!/bin/bash

sum=$[$1+$2]

echo sum=$sum

5、条件判断

1)、语法结构:

test condition

注意:condition前后都要有空格

2)、常用的判断条件

​​​​​​​两个整数之间比较

-eq 等于 (equal)

-lt 小于 (less than)

-gt 大于 (greater than)
-ne 不等于 (not equal)

-le 小于等于 (less equal)

-ge 大于等于 (greater equal)

注意:如果是字符串之间的比较,用等号”=”判断相等,用“!=”判断不等

我们在进行判断时,例如

a=hello

test $a =hello或[ $a = hello ]

echo $?    

//通过$?的返回值进行判断,如果返回0,为true,若返回非0,则为false

注意:

使用[ $a = hello ]方式进行判断时,等号两边要有空格,中括号旁边也要有空格。

使用[  ]这种判断方式,还可以判断是否为空值。

例如:

[ asdasda ]

echo $?

显示0

[  ]

echo $?

显示1

//通过$?的返回值进行判断,如果返回0,为true,若返回非0,则为false

​​​​​​​按照文件权限进行判断

-r 有读权限(read)

-w 有写权限(write)

-x 有执行权限(execute)

例如:

判断hello.sh有没有写的权限

[ -w hello.sh ]

echo $?

​​​​​​​按照文件类型进行判断

-e 文件存在(existence)

-f 文件存在并且是一个常规的文件(file)

-d 文件存在并且是一个目录(directory)

例如:判断是否存在hello.txt这个文件

[ -e /opt/module/hello.txt ]

​​​​​​​多条件判断

使用||和&&

&&表示前一条命令执行成功时,才能执行后一条命令,||表示上一条命令执行失败后,才执行下一条命令

例如:

>>number=15

>>[ $number -lt 20 ] $$ echo “$number<20” || echo ”$number>=20”

6、流程控制

1)、if判断

    1. 基本语法:
      1. 单分支:

If [条件判断]

then

程序

fi

      1. 多分支:

If [条件判断];then

程序

elif[条件判断]

then

程序

else

程序

fi

例如

>>a=3

>>if (($a > 2 )) ;then echo OK ; else echo notOK ; fi

2)、case语句

基本语法:

case &变量名 in

“值1”)

如果变量的值等于值1,则执行程序1

;;

“值2”)

如果变量的值等于值2,则执行程序2

;;

......省略其他分支......

*)

如果变量的值都不是以上的值,则执行此程序

;;

esac

3)、for 循环

基本语法:

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

do

程序

done

例如:

>>for i in {1..100};do sum=$[ $sum+$i ];done;echo $sum

或者

>>for(( i=1; i <= $1; i++ ))

>> do

>> sum=$[ $sum + $i ]

>>done

>>echo $sum

4)、while 循环

语法结构:

while [条件判断]

do

程序

done

例子:创建一个shell脚本:从1加到指定的数

>>a=1

>>while [ $a + 1 ]

>>do

>> sum=$[ $sum + $a ]  ------->   let sum+=a

>> a=$[ $a + 1 ]        ------->   let a++

>>done

>>echo $sum

7、读取控制台的输入

语法结构:

read (选项) (参数)

选项:

-p:指定读取值时的提示

-t:指定读取值时的等待时间(秒) 如果不加-t表示一直等待

参数:

变量:指定读取值的变量名称

例如:

>>#!/bin/bash

>>read -t 10 -p “请输入你的芳名:” name

>>echo “welcome,$name”

8、函数

函数相对于脚本来说,更为简洁方便

(1)、系统函数

我们在编写shell脚本调用系统函数时,格式:$(函数名 参数)

例如:编写脚本获取时间戳

>>#!/bin/bash

>>read -p “输入文件名:” filename

>>file=”$filename”_log_$(date +%s)

>>echo $file

1)、basename 获取文件名

语法结构:

Basename [string/pathname] [suffix]  

功能:

Basename命令会删除所有的前缀包括最后一个“/”字符,然后将字符串显示出来

选项:

[suffix]为后缀,如果suffix被制定了,basename会将pathname或string中的suffix去掉

实操案例:

>>#!/bin/bash

>>echo filename: $(basename $0 .sh)

2)、dirname 获取文件的路径

语法结构:

dirname 文件的绝对路径

功能:

从给定的包含绝对路径的文件名中去除文件名(非目录的部分),然后返回剩下的路径(目录部分)

实操案例:

>>dirname /a/b/c.txt

返回的结果为:

/a/b

案例实操:执行脚本,返回其绝对路径

思路:使用dirname获取脚本的路径,随后cd进入该目录内,最后使用pwd返回绝对路径

>>#!/bin/bash

>>echo path:$(cd $(dirname $0);pwd)

(2)、自定义函数

语法结构:

[function] funname[()]

{

Action;

[return int;]

}

案例实操:

>>#!/bin/bash

>>function add(){

>> s=$[$1 + $2]

>> echo “两数之和为:” $s

>>}

>>

>>read -p ”请输入第一个整数:” a

>>read -p ”请输入第二个整数:” b

>>sum=$(add $a $b)

>>echo “和的平方为:” $[$sun * $sum]

案例实操:

创建一个定制打包文件的脚本

>>#!/bin/bash

>>#首先先判断输入的参数个数是否为1

>>if [ $# -ne 1 ]

>>then

>> echo “参数个数错误!请输入一个所要归档的目录做为参数”

>> exit

>>fi

>>

>>#从参数中获取目录名称

>>if [ -d $1]

>>then

>> echo

>>else

>> echo

>> echo “目录不存在”

>> echo

>> exit

>>fi

>>

>>DIR_NAME=$(basename $1)

>>DIR_PATH=$(cd $(dirname $1);pwd)

>>

>>#获取当前日期

>>DATE=$(date +%y%m%d)

>>

>>#定义生产的归档文件

>>FILE=archive_${DIR_NAME}_$DATE.tar.gz

>>DEST=/a/b/$FILE

>>

>>开始归档目录文件

>>echo “开始归档....”

>>echo

>>tar -zcf $DEST $DIR_PATH/$DIR_NAME

>>if [ $? -eq 0 ]

>>then

>> echo ”归档成功”

>> echo”归档文件为:$DEST”

随后执行crontab -e 创建定时任务

0 2 * * * /脚本的绝对路径 /所要归档的文件的绝对路径

9、正则表达式

(1)、常规匹配

一串不包含特殊字符的正则表达式匹配他自己

ls -l | grep root

(2)、常用特殊字符

1)、特殊字符:^

匹配一行的开头:

cat a.txt | grep ^a

2)、特殊字符:$

匹配一行的结束:

cat a.txt | grep z$

cat -n a.txt | grep ^$

查看空行的行号

cat a.txt | grep ^abc$

查看哪些段落里有abc

3)、特殊字符:.

匹配任意一个字符

r.t

可以匹配

rat、rbt、rct……

4)、特殊字符:*

匹配0个或多个字符

r*t

可以匹配

rt、rat、root、racat……

cat a.txt | grep ^a*b$

可以匹配以a开头b结尾的行

5)、特殊区间(中括号):[ ]

[6,8]:匹配6或8

[6-8]:匹配一个6-8的数字

[0-9]*:匹配任意长度的数字字符串

  1. z]:匹配一个a-z之间的字符

[a-z]*:匹配任意长度的字母字符串

6)、特殊字符:\

当我们要查找文件中的$符号的时候,我们就需要用到转义字符“\”

例如:

>>cat a.txt | grep ‘\$’

问题:为什么查找$符号时,用单引号,而不用双引号?

答:如果使用双引号则双引号中内容会原封不动的输出,使用单引号才表示接下来要做转义。

10、文本处理工具

(1)、cut

从文件的每一行剪切字节、字符和字段并将在这些字节、字符和字段输出

语法结构:

cut [选项参数] filename

说明:

默认分隔符是制表符

选项参数说明:

-f :列号,提取第几列

-d :分隔符,按照指定分隔符分割列,默认是制表符“\t”

-c :按字符进行切割 后加n表示取第几行 比如-c 1

(2)、awk

Awk是gawk的软链接

文本分析工具,以空格作为默认分隔符将每行切片,切开的部分进行分析处理。

语法结构:

awk [选项参数] ‘/pattern1/{action1} /pattern2/{action2}......’  filename

pattern:表示awk在数据中查找的内容

action:在找到匹配内容时所执行的一系列命令

·选项参数:

-F :指定输入文件分隔符

-V :赋值一个用户定义变量

例如:

搜索a.txt中以root开头的所有行,并输出该行的第一列和第七列,中间以“:”分割
>>cat a.txt | awk -F “:” ‘/^root/{print $1”,”$7}’

或者

>>awk -F “:” ‘/^root/{print $1”,”$7}’ a.txt

例如:

只显示a.txt的第一列和第七列,以逗号分割,且在所有行前面添加名user,shell在最后一行添加”xxxx,/bin/xxxx”

>>awk -F “,” ‘BEGIN{print “user,shell”} {print $1”,”$7} END{print “xxxx,/bin/xxxx”}’ a.txt

例如:

将文档中的数字都加1(文档中只有一列数字)

>>cat /opt/module/num.txt | awk -v i=1 ‘{print $1+i }’

或者

>>cat /opt/module/num.txt | awk ‘{print $1+1}’

·awk的内置变量

变量

说明

FILENAME

文件名

NR

已读的记录数(行号)

NF

浏览记录的域的个数(切割后,列的个数)

例如:

>>cat /etc/passwd |awk -F “:” ‘{print “文件名:”FILENAME “行号:”HR”列数:”NF}’

11、综合应用案例

需求分析:

我们可以利用linux自带的mesg和write工具,向其他用户发送消息

需求:

实现一个向某个用户快速发送消息的脚本,输入用户名作为第一个参数,后面直接跟要发送的消息。脚本需要检测用户是否登录在系统中、是否打开信息功能,以及当前发送消息是否为空。

脚本实现:

#!/bin/bash

# 查看用户是否登录

login_user=$(who | grep -i -m 1 $1 | awk '{print $1}')

#-i表示忽略大小写,-m表示筛选出来的结果最多取多少行

if [ -z $login_user ]

#-z表示是否为0(zero)

then

        echo "$1 不在线!"

        echo "退出脚本》》》》》"

        exit

fi

#获取终端,例如pst/0

login_allowed=$(who | grep -i -m 1 $1 | awk '{print $2}')

#-i表示忽略大小写,-m表示筛选出来的结果最多取多少行

if [ $login_allowed !="+" ]

then

        echo "$1 没有开启消息功能"

        echo "退出脚本》》》》》"

        exit

fi

#确认是否有消息发送

if [ -z $2 ]

then

        echo "没有消息发送!"

        echo "退出脚本》》》》》"

        exit

fi

#从参数中获取要发送的消息

whole_msg=$(echo $* | cut -d " " -f 2-)

#获取用户登录的终端

user_terminal=$(who | grep -i -m 1 $1 awk '{print $2}')

#写入要发送的消息

echo $whole_msg | write $login_user $user_terminal

if [ $? != 0 ]

then

        echo "发送成功!"

else

        echo "发送成功!"

fi

exit

;