目录
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目录下
解决办法:
- 使用绝对路径调用该脚本
- 使用相对路径调用该脚本
- ./hello.sh
- 将脚本存放在/bin目录下
- source hello.sh
- . hello.sh
问题2:./hello.sh与. hello.sh的区别?
答:“./hello.sh”中的“./”表示当前目录下,该调用脚本的方法相当于使用相对路径调用脚本
“. hello.sh”中的“.”是命令
3、变量
(1)、系统预定义变量
$HOME、$PWD、$SHELL、$USER等
使用set看到当前定义的所有变量以及函数
(2)、自定义变量
1)、基本语法
- 定义变量:变量名=变量值
- 撤销变量:unset 变量名
- 申明静态变量:readonly 变量,注意:不能unset
当我们使用shell进行运算时
第一种方式:
a=$((1+5))
第二种方式:
b=$[4+6]
定义常量:
readonly b=5
注意:之后我们再对b进行复制会报-bash:b:只读变量
2)、命名规则
- 变量名称可以由字母、数字和下划线组成,但是不能以数字开头,环境变量名建议大写
- 等号两侧不能有空格
- 在bash中,变量默认类型都是字符串类型,无法直接进行数值运算
- 变量的值如果有空格,需要使用双引号或单引号括起来
(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、运算符
上述我们提到过数值运算。
按上述所说的两种方式中,运算符之间不能有空格。
- 第三种方式:
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判断
-
- 基本语法:
- 单分支:
- 基本语法:
If [条件判断]
then
程序
fi
-
-
- 多分支:
-
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]*:匹配任意长度的数字字符串
- 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