shell脚本一学就会:
提示:通过此博文可快速掌握shell的基本用法
:
- 用心学习,一天即可掌握shell
一、SHELL基础:
什么是shell
shell是用户与linux内核之间的解释器
[root@ztj ~]# cat /etc/shells #查看shell解释器
/bin/sh
/bin/bash
/sbin/nologin
/usr/bin/sh
/usr/bin/bash
/usr/sbin/nologin
[root@ztj ~]# usermod -s /bin/tcsh test
使用chsh更改解释器
[root@ztj ~]# chsh -s /bin/bash test #test用户解释器修改为bash
Changing shell for test.
Shell changed.
覆盖重定向
[root@ztj ~]# ls > a.txt
追加重定向
[root@ztj ~]# ls >> a.txt
收集正确信息
[root@ztj ~]# ls xxyyzz.txt > b.txt #失败,> 收集正确信息
ls: 无法访问xxyyzz.txt: 没有那个文件或目录
收集错误信息
[root@ztj ~]# ls xxyyzz.txt 2> b.txt #正确,2> 收集错误信息
收集正确和错误的信息
[root@ztj ~]# ls a.txt xxyzz.txt &> b.txt #收集所有信息
管道
[root@ztj ~]# yum list | grep bash #过滤跟bash相关的包
临时使用ksh解释器
[root@ztj ~]# ksh #临时使用ksh解释器
# ls #查看目录没有颜色
# ls /
# clear #清屏,并且无法使用上下左右键
# exit #退出
二、脚本的设计与运行
脚本构成
[root@ztj ~]# chmod +x /root/shell/test01/first.sh #添加执行权限
[root@ztj test01]# vim /root/shell/test01/first.sh
#!/bin/bash #指定解释器
#This a test program for shell. #这是一个测试性的程序
echo "Hello World"
规范脚本的构成
脚本声明(需要的解释器、作者信息等)
注释信息(步骤、思路、用途、变量含义等)
可执行语句(操作代码)
脚本在执行的时候要有执行(x)权限,否则会报错
[root@ztj test01]# chmod -x first.sh
[root@ztj test01]# ./first.sh #报错
-bash: ./first.sh: 权限不够
[root@ztj test01]# /root/shell/test01/first.sh #报错
-bash: /root/shell/test01/first.sh: 权限不够
不需要文件有可执行权限,来执行脚本
[root@ztj test01]# sh first.sh //开启子进程
[root@ztj test01]# bash first.sh //开启子进程
[root@ztj test01]# ksh first.sh //开启子进程
[root@ztj test01]# vim tmp.sh
#!/bin/bash
sleep 100
[root@ztj test01]# sh tmp.sh #运行脚本
定义变量
[root@ztj ~]# a=11
[root@ztj ~]# echo $a #调用变量
[root@ztj ~]# a=33 #变量名已经存在,再次赋值,里面的内容会被覆盖
[root@ztj ~]# echo $a
名变量的命令
变量名不能使用特殊符号,会报错,不能以数字开头,可以是数字,字母,下划线
[root@ztj ~]# echo ${x}6.5
[root@ztj ~]# echo ${x}7.7
取消变量
[root@ztj ~]# unset x
[root@ztj ~]# echo $x
P2 系统预设变量
环境变量
[root@ztj ~]# echo $PATH #命令搜索的路径变量
[root@ztj ~]# echo $PWD #返回当前工作目录
/root
[root@ztj ~]# echo $USER #显示当前登录的用户
root
[root@ztj ~]# echo $UID #显示当前用户的uid
0
[root@ztj ~]# echo $HOME #显示当前用户的家目录
/root
[root@ztj ~]# echo $SHELL #显示当前的SHELL
/bin/bash
位置变量
存储脚本时执行的参数
$1 $2 $3 …$9 ${10} ${11} … #从10开始位置变量需要加{}
[root@ztj ~]# vim /root/shell/test01/vars.sh
#!/bin/bash
echo $1
echo $2
echo $3
[root@ztj ~]# chmod +x /root/shell/test01/vars.sh
[root@ztj ~]# /root/shell/test01/vars.sh aa bb cc #aa bb cc 分别对应脚本里面的$1 $2 $3
aa
bb
cc
编写一个user标本,使用它创建系统用户
[root@ztj ~]# vim /root/shell/test01/user.sh
#!/bin/bash
useradd "$1"
echo "$2" | passwd --stdin $1
[root@ztj ~]# chmod +x /root/shell/test01/user.sh
[root@ztj ~]# /root/shell/test01/user.sh tom 123
[root@ztj ~]# /root/shell/test01/user.sh jim 123
预定义变量
用来保存脚本程序的执行信息,可以直接使用这些变量,但是不能为这些变量赋值
$?:执行上一条命令的返回状态,0为正确,非0为错误
[root@ztj ~]# ls /etc/hosts
/etc/hosts
[root@ztj ~]# echo $? #返回值为0,正确
0
[root@ztj ~]# ls /xxxxxyyyy #返回值为非0,失败
ls: 无法访问/xxxxxyyyy: 没有那个文件或目录
[root@ztj ~]# echo $?
2
其他几个预定义变量的测试
[root@ztj ~]# vim /root/shell/test01/pre.sh
#!/bin/bash
echo $0 #执行脚本的名字
echo $$ #当前脚本的进程号
echo $# #位置变量的个数
echo $* #所有位置变量
三、变量的扩展应用
多种引号的区别
1)双引号的应用
使用双引号可以界定一个完整字符串。
[root@ztj ~]# touch a b c #创建了三个文件
[root@ztj ~]# touch "a b c" #创建1一个文件
[root@ztj ~]# ls -l
[root@ztj ~]# rm -rf a b c
[root@ztj ~]# rm -rf "a b c"
2)单引号的应用
界定一个完整的字符串,并且可以实现屏蔽特殊符号的功能。
当双引号里面有变量时,会被扩展出来,也就是会取变量的值
[root@ztj ~]# hi="world"
[root@ztj ~]# echo "$hi" #成功
world
[root@ztj ~]# echo '$hi' #失败,当成一个字符串
$hi
当没有特殊符号时,单引号和双引号的含义是一样的
[root@ztj ~]# touch "a b c"
[root@ztj ~]# touch 'c d e'
3)反撇号或 ( ) 的 应 用 使 用 反 撇 号 或 ()的应用 使用反撇号或 ()的应用使用反撇号或()时,可以将命令执行的标准输出作为字符串存储,因此称为命令替换。
[root@ztj ~]# grep root /etc/passwd
[root@ztj ~]# test=`grep root /etc/passwd`
[root@ztj ~]# echo $test
[root@ztj ~]# test2=$(grep root /etc/passwd)
[root@ztj ~]# echo $test
read命令定义变量
使用read命令从键盘读取变量值
1)read基本用法
执行后从会等待并接受用户输入(无任何提示的情况),并赋值给变量:
[root@ztj ~]# read iname
123
[root@ztj ~]# echo $iname
123
[root@ztj ~]# read iname
test
[root@ztj ~]# echo $iname
test
虽然可以赋值。但是屏幕上没有任何提示信息,在未来写脚本的时候不太方便,可以加上-p选项,给出提示
案例:创建一个脚本,通过read定义变量创建用户,更改密码
[root@ztj ~]# vim /root/shell/test01/read.sh
#!/bin/bash
read -p "请输入用户名:" name
read -p "请输入密码:" pass
useradd $name
echo "$pass" | passwd --stdin $name
[root@ztj ~]# chmod +x /root/shell/test01/read.sh
[root@ztj ~]# /root/shell/test01/read.sh
请输入用户名:user2
请输入密码:a
更改用户 user2 的密码 。
passwd:所有的身份验证令牌已经成功更新。
但是此时密码是名为显示的,不安全,可以使用-s参数,不显示终端输入的信息
[root@ztj ~]# vim /root/shell/test01/read.sh
……
read -s -p "请输入密码:" pass
……
-t可指定超时秒数
[root@ztj ~]# read -t 3 iname #3秒不输入直接退出
全局or局部
局部变量
[root@ztj ~]# x=11
[root@ztj ~]# echo $x #成功
11
[root@ztj ~]# bash #开启子进程,测试
[root@ztj ~]# echo $x #没有数据
[root@ztj ~]# exit
全局变量
[root@ztj ~]# export Y=22
[root@ztj ~]# echo $Y
22
[root@ztj ~]# bash #开启子进程,测试
[root@ztj ~]# echo $Y #成功
22
[root@ztj ~]# bash #开启子进程,测试
[root@ztj ~]# echo $Y #成功
22
[root@ztj ~]# exit
[root@ztj ~]# exit
四、shell中的运算
整数运算
使用$[ ]或$(( ))表达式,支持取变量的值进行运算
[root@ztj ~]# x=2
[root@ztj ~]# y=3
[root@ztj ~]# echo $[x+y]
5
[root@ztj ~]# echo $[x*y]
6
[root@ztj ~]# echo $((x*y))
6
[root@ztj ~]# echo $((3*2))
6
变量的自增/减等操作
[root@ztj ~]# x=2
[root@ztj ~]# echo $[x+=4]
6
[root@ztj ~]# echo $x
6
[root@ztj ~]# echo $[x+=1]
7
[root@ztj ~]# echo $[x*=3]
21
[root@ztj ~]# echo $x
21
关于自增/减也可以使用let命令,但是他不会返回计算的结果,但是可以结合echo命令来查看
[root@ztj ~]# i=2
[root@ztj ~]# let i+=2
[root@ztj ~]# echo $i
4
[root@ztj ~]# let i*=3
[root@ztj ~]# echo $i
12
小数运算
bc计算器
[root@ztj ~]# echo "1.2+3.3" | bc
4.5
[root@ztj ~]# echo "1.2+3.3;3.8+2.1" | bc #多组运算
4.5
5.9
[root@ztj ~]# echo "scale=2;10/2" | bc #指定小数点位数
5.00
小数的比较 表达式成立返回1,否则返回0
[root@ztj ~]# echo "1>2" | bc
0
[root@ztj ~]# echo "5>2" | bc
1
[root@ztj ~]# echo "5==2" | bc #等于
0
[root@ztj ~]# echo "5!=2" | bc #不等于
1
设置字体颜色
[root@ztj ~]# echo -e "\033[32mOK\033[0m" # \033[开启颜色属性,32m为黑色,\033[0m 关闭颜色,还以黑色显示
设置背景颜色
[root@ztj ~]# echo -e "\033[42mOK\033[0m"
字体颜色
字体颜色是由一个ANSI 转义编码来控制的。该控制编码会嵌入字符流中并发送给终端仿真器。但是,该控制编码不会被“打印”到屏幕上,而是会被终端解释为一个指令。正如我们在上表看到的字符序列, 这个 [ 和 ] 序列被用来封装这些非打印字符。一个 ANSI 转义编码以一个八进制033(这个编码是由 退出按键产生的)开头,其后跟着一个可选的字符属性(0:正常、1:黑体、4:下划线、5:闪烁、7:反向(前景色和背景色反转)),在之后是一个指令。
序列 文本颜色 序列 文本颜色
\033[0;30m 黑色 \033[1;30m 深灰色
\033[0;31m 红色 \033[1;31m 浅红色
\033[0;32m 绿色 \033[1;32m 浅绿色
\033[0;33m 棕色 \033[1;33m 黄色
\033[0;34m 蓝色 \033[1;34m 浅蓝色
\033[0;35m 粉红 \033[1;35m 浅粉色
\033[0;36m 青色 \033[1;36m 浅青色
\033[0;37m 浅灰色 \033[1;37m 白色
背景颜色
除了字体颜色,我们也可以设置字体的背景颜色。同样是通过转义的控制编码来实现,下表是背景颜色的控制编码。
\033[0;40m 黑色 \033[1;44m 蓝色
\033[0;41m 红色 \033[1;45m 粉红
\033[0;42m 绿色 \033[1;46m 青色
\033[0;43m 棕色 \033[1;47m 浅灰色
五、条件测试
条件测试的基本用法
语法格式
使用“test 表达式”或者[ 表达式 ]都可以,表达式两边至少要留一个空格。
字符串测试 是否为空 [ -z 字符串 ]
#是,返回值为0 #否,返回值1
等于[ 字符串1 == 字符串2 ]
不等于[ 字符串1 != 字符串2 ]
整数值比较
格式:[ 整数值1 操作符 整数值2 ]
-eq等于 -ne不等于 -gt大于 -ge大于等于 -lt小于 -le小于等于
参与比较的必须是整数(可以调用变量),比较非整数值时会出错
文件状态的测试 [ 操作符 文件或目录 ]
e 判断对象是否存在(不管是目录还是文件),存在则结果为真
-d 判断对象是否为目录(存在且是目录),是则为真
-f 判断对象是否为文件(存在且是文件)是则为真
-r 判断对象是否可读,是则为真
-w 判断对象是否可写,是则为真
-x 判断对象是否具有可执行权限,是则为真
控制操作符
组合多个命令: ; && ||
; 按照顺序进行命令的执行,命令之间没有任何逻辑关系
&& 当符号前的命令执行成功,才执行符号后的命令
|| 当符号前的命令执行失败,才执行符号后的命令,前面命令执行成功,后面命令不执行,二选一执行
if语句
if单分支的语法组成:
if 条件测试
then
命令序列
fi
if双分支的语法组成:既可以执行成功的命令,也可以执行失败的命令
if 条件测试
then
命令序列1
else
命令序列2
fi
if多分支的语法组成: 针对多个条件分别执行不同的操作
if 条件测试1 ;then
命令序列1
elif 条件测试2 ;then
命令序列2
……
else
命令序列n
fi
for循环
for循环语法格式
根据变量的不同取值,重复执行命令序列(for循环使用场合,在知道循环次数的时候使用)
for循环的语法结构:
第一种格式
for 变量名 in 值列表
do
命令序列
done
第二种格式
for ((初值;条件;步长))
do
命令序列
done
案例:
[root@ztj test02]# vim for_demo2.sh
#!/bin/bash
for ((i=1;i<=5;i++))
do
echo "I am $i"
done
while循环
语法结构及特点
反复测试条件,只要成立就执行命令序列(死循环或者条件循环时使用【不知道循环次数时】)
while循环的语法结构:
while 条件测试
do
命令序列
done
死循环语法结构
while :
do
命令序列
done
case语句
case语法格式
case $x in
A|b|c)
echo "redhat";;
Redhat|ESXi)
echo "esxi";;
*)
数组
数组也是一个变量,是一个有点特殊的变量。存储多个数据的集合就是数组。
[root@ztj test03]# test=(aa bb cc) #定义数组
[root@ztj test03]# echo ${test[0]} #调用数组的值,数组起始下标为0
aa
六、SHELL函数
语法格式
在Shell脚本中,将一些需重复使用的操作,定义为公共的语句块,即可称为函数。通过使用函数,可以使脚本代码更加简洁,增强易读性,提高Shell脚本的执行效率。
格式1:
function 函数名 {
命令序列
.. ..
}
格式2:
函数名() {
命令序列
.. ..
}
函数的调用
直接使用“函数名”的形式调用,如果该函数能够处理位置参数,则可以使用“函数名 参数1 参数2 … …”的形式调用。注意:函数的定义语句必须出现在调用之前,否则无法执行。
加法器
传递参数计算两个数字之和
[root@ztj test03]# add(){
> echo $[$1+$2]
> }
grep扩展:
grep -q # -q无论找得到数据还是找不到数据,都不显示在屏幕上面
grep -w #看做的是一个独立的参数
[root@ztj test03]# echo "abc" | grep -w a #找不到
七、字符处理与变换初始化
字符串处理机制
子串截取
格式:${变量名:起始位置:长度}
使用${}方式截取字符串时,起始位置是从0开始的。
子串替换
格式:${变量名/old/new} #替换1个结果
格式:${变量名//old/new} #替换全部结果
- 注:此时替换是只是显示替换的值在屏幕上面,实际上,变量的值并没有替换
如果想要把替换的值保存下来,重新赋值给某个变量即可
子串掐头
格式:${变量名#关键词} # #号代表从左向右,最短匹配删除
格式:${变量名##关键词} # ##号代表从左向右,最长匹配删除
- 注:对变量掐头不会改变变量原有的值
[root@ztj ~]# A=`head -1 /etc/passwd` #定义一个变量
[root@ztj ~]# echo ${A}
root:x:0:0:root:/root:/bin/bash
[root@ztj ~]# echo ${A#*:} #从左到右,最短匹配删除,看第一个冒号位置,*通配符
x:0:0:root:/root:/bin/bash
[root@ztj ~]# echo ${A##*:} #从左到右,最长匹配删除,看最后一个冒号位置
/bin/bash
子串去尾
格式:${变量名%关键词} # %号代表从右向左,最短匹配删除
格式:${变量名%%关键词} # %%号代表从右向左,最长匹配删除
- 注:对变量去尾不会改变变量原有的值
[root@ztj ~]# echo ${A}
root:x:0:0:root:/root:/bin/bash
[root@ztj ~]# echo ${A%:*} #从右向左,最短匹配删除
root:x:0:0:root:/root
[root@ztj ~]# echo ${A%%:*} #从右向左,最长匹配删除
root
变量初始化
判断一个变量是否有值,如果有值则返回该变量的值,如果无值则返回赋予的初始化的值
格式:${变量:-关键词}
[root@ztj test03]# X=123 #变量赋值
[root@ztj test03]# echo ${X:-abc123} #变量X有值,返回变量的值
123
[root@ztj test03]# echo ${XXAB:-123456} #变量XXAB无值,赋予初始化的值
123456
随机密码
随机密码案例1
#所有密码的可能性是26+26+10=62(0-61是62个数字)
[root@ztj test03]# vim pass.sh
#!/bin/bash
key="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
pass=""
for i in {1..10}
do
num=$[RANDOM%${#key}]
tmp=${key:num:1}
pass=${pass}${tmp}
done
echo $pass
随机密码案例2
[root@ztj test03]# uuidgen #生成的UUID号可以作为密码
[root@ztj test03]# openssl rand -base64 10 #rand是随机的意思,10和密码的长度有关,数字越大,密码越长,但是不是密码的位数
随机密码案例3
使用随机设备文件/dev/random /dev/urandom
[root@ztj test03]# strings /dev/random #随机提取字符串,random提取的较慢
[root@ztj test03]# strings /dev/urandom #urandom提取的较快
tr命令可对数据进行替换,删除等操作
-c:取反 -d:删除
[root@ztj test03]# tr -cd "0-9a-zA-Z" < /dev/urandom | head -c 10
进度条脚本(如拷贝文件的脚本):
先定义一个函数,函数里面是一个死循环;给空格加个底色,调用函数放到后台,是脚本继续执行后面的命令;$!代表整个脚本中,最后一个后台进程的进程号
[root@ztj test03]# vim progress.sh
#!/bin/bash
bar(){
while :
do
echo -en "\033[42m \033[0m"
sleep 0.5
done
}
bar &
cp -r $1 $2
kill $!
echo
fork炸弹
无限循环自己,消耗系统资源
[root@ztj test03]# .(){.|.&};.
.(){ #定义函数,函数为.
.|.& #在函数内调用自己,并放后台
}
. #执行函数
八、正则表达式
基本正则的使用
[root@ztj ~]# grep root /etc/passwd #查找包含root的行
[root@ztj ~]# grep ^root /etc/passwd #查找以root开头的行
[root@ztj ~]# grep bash$ /etc/passwd #查找以bash结尾的行
[root@ztj ~]# grep "[abc]" /etc/passwd #查找包含a或者b或者c的行
[root@ztj ~]# grep "[^abc]" /etc/passwd #查找不包含a或者b或者c的其他内容
[root@ztj ~]# grep . /etc/passwd #查找任意单个字符
[root@ztj ~]# grep r.*t /etc/passwd #查找以r开头以t结尾的
[root@ztj ~]# grep "[0-9]*" /etc/passwd #查找包含数字的,*代表任意次
[root@ztj ~]# grep "[0-9]\{3,4\}” /etc/passwd #查找包含数字3-4次的
[root@ztj ~]# grep "[0-9]\{3\}" /etc/passwd #查找包含3位数的
扩展正则的使用
[root@ztj ~]# grep -E "0{2,3}" /etc/passwd #查找0出现2-3次
[root@ztj ~]# grep -E "[a-z]+" /etc/passwd #查找a-z等字母至少出现一次
[root@ztj ~]# grep -E "s?bin" /etc/passwd #查找sbin或者bin(?匹配前面的s字符0-1次)
[root@ztj ~]# grep -E "(root|daemon)" /etc/passwd #查找root或者daemon
[root@ztj ~]# echo "ababab" | grep ab #查找ab
ababab
[root@ztj ~]# echo "ababab" | grep -E "(ab)" #查找ab
[root@ztj ~]# echo "ababab" | grep -E "(ab){2}" #将ab组合,匹配两次
Perl兼容正则的使用
[root@ztj ~]# grep -P "bin" /etc/passwd #匹配包含bin的行,只要包含bin字符的都出现
[root@ztj ~]# grep -P "\bbin\b" /etc/passwd # \b单词边界,b前面不能有内容,n后面也不能有内容,只匹配bin
[root@ztj ~]# grep -P "\w" /etc/passwd #查找字母数字下划线
[root@ztj ~]# grep -P "\W" /etc/passwd #查找不是字母数字下划线部分
[root@ztj ~]# grep -P "\s" /etc/passwd #查找空白,空格,tab键都算
[root@ztj ~]# grep -P "\d" /etc/passwd #查找数字
[root@ztj ~]# grep -P "\D" /etc/passwd #查找非数字
grep语法格式
用法: grep [选项] 匹配模式 [文件]…
常用选项:
-i 忽略大小写
-V 取反匹配
-W 匹配单词,和正则里面的\b是一样的
-q 静默匹配,不将结果显示在屏幕(无论结果是否匹配成功)
九、sed基础
什么是sed?
sed非交互式修改文档,逐行处理,可以对文本进行增删改查等操作
sed语法格式
语法:命令 | sed 选项 (定位符)指令
sed 选项 (定位符)指令 文件名 #(定位符)指令 想对文件的哪一行进行操作
选项
-n 屏蔽默认输出
-r 支持扩展正则
-i 写入文件
数据定位
定位符:行号定位
sed可以使用行号来定位自己需要修改的数据内容
[root@ztj test04]# sed "2p" /etc/hosts #不加-n选项,默认全文打印,第二行打印了两边,因为有一遍是人为要求打印的
[root@ztj test04]# sed -n "2p" /etc/hosts # -n 屏蔽默认输出,打印第二行内容
[root@ztj test04]# sed -n "3p" /etc/passwd #打印第三行
[root@ztj test04]# sed -n "1,3p" /etc/passwd #打印一到三行
[root@ztj test04]# sed -n "1~2p" /etc/passwd #输出(奇数行)第1行开始步长为2
[root@ztj test04]# sed -n "2~2p" /etc/passwd #输出(偶数行)第2行开始步长为2
[root@ztj test04]# sed -n "2,+3p" /etc/passwd #输出第2行,以及后面的3行
正则定位
sed可以使用正则匹配需要的数据,然后在编辑对应的内容,sed里面在使用正则是需要两个/ / 括起来
过滤root开头的行,使用sed打印
[root@ztj test04]# grep "^root" /etc/passwd
[root@ztj test04]# sed -n "/^root/p" /etc/passwd
过滤包含三个数字的行
[root@ztj test04]# grep -E "[0-9]{3}" /etc/passwd
[root@ztj test04]# sed -rn "/[0-9]{3}/p" /etc/passwd #使用扩展正则加r选项
使用sed修改配置
常用sed指令:
p(print):打印行
d(delete):删除行
c(replace):替换行
s(substitution):替换关键词
=:打印行号
过滤数据(print指令)
[root@ztj test04]# sed -n "/IPADDR/p" /etc/sysconfig/network-scripts/ifcfg-eth0 #过滤网卡IP地址
[root@ztj test04]# free | sed -n "/Mem/p" #过滤内存信息
[root@ztj test04]# df -h | sed -n "/\/$/p" #过滤磁盘根分区信息
[root@ztj test04]# sed -n "1p;3p;6p" /etc/passwd #打印不连续的多行
[root@ztj test04]# sed -n '2!p' /etc/passwd #不打印第二行,其他都打印
删除数据(delete指令,不适用-i选项,源文件不会被修改)
[root@ztj test04]# sed 'd' /etc/hosts #删除/etc/hosts全文,没有定位条件匹配所有行
[root@ztj test04]# sed '1d' /etc/hosts #删除第一行
[root@ztj test04]# cat /etc/fstab > /tmp/fstab
[root@ztj test04]# sed '1,3d' /tmp/fstab #删除1到3行
[root@ztj test04]# sed '/dev/!d' /tmp/fstab #不包含dev的行都删除
[root@ztj test04]# sed '/^#/d' /tmp/fstab #删除以#开头的行
[root@ztj test04]# sed '/^$/d' /tmp/fstab #删除空行
使用i进行永久删除
[root@ztj test04]# sed -i '1,4d' /tmp/fstab #删除1-4行
替换行(c以行为单位)
[root@ztj test04]# sed 'c 123456' /tmp/fstab #将所有行替换为123456
[root@ztj test04]# sed '/IPADDR/c IPADDR=1.1.1.1' /etc/sysconfig/network-scripts/ifcfg-eth0
#查找IPADDR的行替换为IPADDR=1.1.1.1(替换IP地址)
[root@ztj test04]# sed '/127/c 127.0.0.1 localhost' /etc/hosts #查找127的行替换为127.0.0.1 localhost
[root@ztj test04]# sed '4c XXXX' /etc/shells #将/etc/shells文件的第四行替换为XXXX
替换关键词(格式: s/旧的/新的/)
[root@ztj test04]# vim test.txt
2046 2048 2046 2046
1001 2046 2999 1888
2046 2046 2046 2046
[root@ztj test04]# sed 's/2046/XXXX/' test.txt #将每一行的第一个2046替换为XXXX
[root@ztj test04]# sed 's/2046/XXXX/g' test.txt #将每一行的所有2046替换为XXXX
[root@ztj test04]# sed 's/2046/XXXX/2' test.txt #将每一行的第二个2046替换为XXXX
[root@ztj test04]# sed 's/2046/(&)/' test.txt #将每一行的第一个2046替换为(2046) (&指替换的旧的内容,在这里指2046)
[root@ztj test04]# sed '2s/2046/XXXX/g' test.txt #将替换第二行的所有2046替换为XXXX
[root@ztj test04]# sed '2s/2046//g' test.txt #替换为空,等同于删除
[root@svr5 ~]# sed -n '2s/2046/XXXX/p' test.txt #n屏蔽输出,p打印显示。将第二行的2046替换为XXXX
将替换符改为其他字符,也可以达到同样的操作
[root@svr5 ~]# sed 's#2046#XXXX#g' test.txt #以#为sed的指令替换符
[root@svr5 ~]# sed 's,2046,XXXX,g' test.txt #以,为sed的指令替换符
正则符号()具有保留功能
[root@ztj test04]# echo "hello the world" | sed -r 's/^(.)(.*)(.)$/\3\2\1/'
#扩展正则需要加r,.代表任意单个字符 \3\2\1代表第三个第二个第一个内容,\数字格式固定
打印行号 =
[root@ztj test04]# sed -n '1=' /etc/passwd #打印第一行的行号
[root@ztj test04]# sed -n '/root/=' /etc/passwd #打印包含root的行
[root@ztj test04]# sed -n '/bash$/=' /etc/passwd #打印以bash结尾的行
[root@ztj test04]# sed -n '$=' /etc/passwd #统计行数
[root@ztj test04]# wc -l /etc/passwd #统计行数(会显示文件名)
sed多行文本处理
常用指令
i ( insert) :插入 (-i选项才会修改源文件)
a ( append) :追加
r (read) :读取文件|导入文件内容
w (write) :文件另存为|导出文件内容
插入指令:i ( insert)行前写入
[root@ztj test04]# sed '2i ABC_XYZ' test.txt #在第二行前插入ABC_XYZ
[root@ztj test04]# sed '3i ABC_XYZ' test.txt #在第三行前ABC_XYZ
[root@ztj test04]# sed '/2046/i ABC\nXYZ' test.txt #在含有2046的行前面插入两行ABC和XYZ(\n换行回车)
[root@ztj test04]# sed '/1888/i ABC\nXYZ' test.txt #在含有1888的行前面插入两行ABC和XYZ
追加指令:a ( append)行后写入
[root@ztj test04]# sed '2a ABC_ XYZ' test.txt #在第二行后插入ABC_XYZ
[root@ztj test04]# sed '3a ABC_ XYZ' test.txt #在第三行后ABC_XYZ
[root@localhost ~]# sed '/2046/a ABC\nXYZ' test.txt #在含有2046的行后面插入两行ABC和XYZ(\n换行回车)
[root@localhost ~]# sed '/1888/a ABC\nXYZ' test.txt #在含有1888的行后面插入两行ABC和XYZ
导入指令:r (read,将其他文件的内容导入)
[root@ztj test04]# sed '2r /etc/hosts' test.txt #将/etc/hosts导入到test.txt的第二行后面
[root@ztj test04]# sed 'r /etc/hosts' test.txt #将/etc/hosts导入到test.txt的每一行后面
[root@ztj test04]# sed '/1888/r /etc/hosts' test.txt #将/etc/hosts导入到test.txt的含有1888的行后面
导出指令:w (write,将文件内容导出另存到其他文件)
[root@ztj test04]# sed 'w copy_test.txt' test.txt #将test.txt文件的所有内容另存为一个新文件copy_ test.txt
[root@ztj test04]# sed '/1888/w 1888.txt' test.txt #将test.txt文件中所有包含1888的行另存为新文件1888. txt
[root@ztj test04]# sed '2,3w line.txt' test.txt #将test.txt文件的2到3行另存为新文件line.txt
十、awk基础语法
awk基础知识
作用:awk在shell脚本中主要是做数据分析和数据过滤
使用方法
命令 | awk [选项] ‘[条件]{指令}’ #[条件]{指令}多条指令可以以分号分隔
awk 选项 ‘[条件]{指令}’ 文本
awk文本过滤的基本用法
格式:awk [选项] ‘[条件]{指令}’ 文件
直接过滤文件内容:
[root@ztj ~]# vim test.txt
hello the world
welcome to beijing
[root@ztj ~]# awk '{print $1}' test.txt #打印第1列
hello
welcome
[root@ztj ~]# awk '{print $1,$3}' test.txt #打印第1列和第3列
[root@ztj ~]# awk '{print $3,$1}' test.txt #打印第3列和第1列
语法格式: -F 指定分隔符,默认分隔符为(空格或者tab键)
[root@ztj ~]# awk '{print $1}' /etc/passwd #默认以空格或tab键打印
[root@ztj ~]# awk -F: '{print $1}' /etc/passwd #以:为分隔打印
awk常用内置变量:
FS 保存或设置字段分隔符,例如FS=’:’,与-F功能一样
$0 文本当前行的全部内容
$1 文本的第1列
$2 文件的第2列
$3 文件的第3列,依此类推
NR 文件当前行的行数
NF 文件当前行的列数(有几列)
[root@ztj ~]# awk -F: '{print NF}' /etc/passwd #以冒号为分隔,每行有几列
[root@ztj ~]# awk -F: '{print $NF}' /etc/passwd #以冒号为分隔,每行有几列,$NF为这列的内容是什么
[root@ztj ~]# awk -F: '{print $(NF-1)}' /etc/passwd #打印倒数第2列内容
[root@ztj ~]# awk '{print NR}' /etc/passwd #打印行数
awk不仅可以打印变量,还可以打印常量
[root@ztj ~]# awk -F: '{print "用户名是:"$1,"UID是:"$3}' /etc/passwd
awk过滤的升级
awk会逐行处理文本,支持在处理第一行之前做一些准备工作,以及在处理完最后一行之后做一些总结性质的工作。在命令格式上分别体现如下:
awk [选项] '[条件]{指令}' 文件
awk [选项] ' BEGIN{指令} {指令} END{指令}' 文件
BEGIN{ } 行前处理,读取文件内容前执行,指令执行1次
{ } 逐行处理,读取文件过程中执行,指令执行n次
END{ } 行后处理,读取文件结束后执行,指令执行1次
awk做计算机
[root@ztj ~]# awk 'BEGIN{a=34;print a+12}'
[root@ztj ~]# awk 'BEGIN{a=34;b=33;print a+b}'
[root@ztj ~]# awk 'BEGIN{a=3.4;b=3.3;print a+b}'
awk统计/ect/passwd里面有多少个可以登录的用户
[root@ztj ~]# awk 'BEGIN{x=0} /bash$/{x++} END{print x}' /etc/passwd
输出打印之前的行数,以及读取文件之后的行数
[root@ztj ~]# awk -F: 'BEGIN {print NR} END{print NR}' /etc/passwd
awk条件判断
条件判断概述
条件表达式
正则表达式
数值/字符比较
逻辑比较
条件
使用正则设置条件
/正则表达式/ ~匹配 !~不匹配
[root@ztj ~]# awk -F: '/root/{print}' /etc/passwd #打印包含root的行
[root@ztj ~]# awk -F: '$1~/root/{print}' /etc/passwd #打印第一列是root的行
[root@ztj ~]# awk -F: '$7!~/bash$/{print}' /etc/passwd #打印第7列不是bash结尾的行
使用数值/字符串比较设置条件
比较符号:==(等于) !=(不等于) >(大于) >=(大于等于) <(小于) <=(小于等于)
[root@ztj ~]# awk -F: 'NR==2' /etc/passwd #打印第二行
[root@ztj ~]# awk -F: 'NR==2{print}' /etc/passwd #打印第二行
字符串的比较
[root@ztj ~]# awk -F: '$1=="root"{print}' /etc/passwd #打印第1列等于root的行
[root@ztj ~]# awk -F: '$1=="root"{print $1,$3}' /etc/passwd #打印第1列等于root的字符串,显示第1列和第3列
[root@ztj ~]# awk -F: '$3>=1000{print $1,$3}' /etc/passwd #打印用户UID大于1000的用户名称和UID信息
逻辑比较 &&逻辑与:期望多个条件成立 ||逻辑或:只要一个条件成立既满足要求
[root@ztj ~]# awk -F: '$3>=0 && $3<2{print $1,$3}' /etc/passwd #打印用户UID大于等于0并且小于2的用户信息
[root@ztj ~]# awk -F: '$3==1 || $3==7{print $1,$3}' /etc/passwd #输出用户UID等于1或用户UID等于7的用户信息
运算符
[root@ztj ~]# awk 'NR%2==0' /etc/passwd #打印偶数行,即NR行数对2取余得0
[root@ztj ~]# awk 'NR%2==1{print}' /etc/passwd #打印奇数行,即NR行数对2取余得1
[root@ztj ~]# seq 200 #产生1-200之间的整数
判断这些数(1-200)能被3和13整除的数有多少个
[root@ztj ~]# seq 200 | awk 'BEGIN{i=0} $1%3==0 && $1%13==0{i++} END{print i}' #满足条件执行i++,不满足执行下一个,最后打印i
awk流程控制
单分支if判断
单分支: awk ‘{ if() {指令} }’ 文件 #if(),()里面写条件,条件满足,执行{指令},不满足则不执行
统计/etc/passwd文件中UID大于等于1000的用户个数:
[root@ztj ~]# awk -F: '{ if($3>=1000){i++} } END{print i}' /etc/passwd
统计/etc/passwd文件中用户名为root的用户名和uid:
[root@ztj ~]# awk -F: '{ if($1=="root"){print $1,$3} }' /etc/passwd
查看CPU负载,15分钟负载是否大于等于0.01,大于则输出CPU负载:
[root@ztj ~]# uptime | awk '{ if($NF>=0.01){print "CPU的负载为:"$NF} }'
双分支if判断
双分支: awk ‘{ if() {指令} else{} }’ 文件
统计/etc/passwd文件,普通用户(UID大于等于1000)和系统用户(小于1000)
[root@ztj ~]# awk -F: '{ if($3>=1000){i++}else{j++} } END{print "普通用户:"i, "系统用户:"j}' /etc/passwd
统计/etc目录有多少普通文件和目录
[root@ztj ~]# ls -l /etc/ | awk '{ if($1~/^-/){x++}else{y++} } END{print "普通文件:"x,"目录个数:"y}'
多分支if判断
多分支: awk ‘{ if() {指令} else if(){}… … else{} }’ 文件
[root@ztj ~]# ls -l /etc/| awk '{ if($1~/^-/){x++}else if($1~/^d/){y++}else{z++} } END{print "普通文件:"x,"目录个数:"y,"其他:",z}'
for循环
for循环格式: for (表达式1 ;表达式2;表达式3) {指令} #表达式1(初始值) ;表达式2(条件);表达式3(步长)
[root@ztj ~]# awk 'BEGIN{ for (i=1;i<=5;i++){print i}}' #屏幕打印12345
[root@ztj ~]# awk 'BEGIN{ for (i=5;i>=1;i--) {print i}}' #屏幕打印54321
十一、Shell脚本综合练习案例
1、循环嵌套
打印星星矩阵
[root@ztj ~]# mkdir /root/shell/test06
[root@ztj ~]# cd /root/shell/test06
[root@ztj test06]# vim star.sh #输出一行5个星星
#!/bin/bash
for i in {1..5}
do
echo -n "* "
done
[root@ztj test06]# chmod +x star.sh
[root@ztj test06]# ./star.sh
[root@ztj test06]# vim star.sh #打印星星矩阵
#!/bin/bash
for i in {1..5}
do
for j in {1..5}
do
echo -n "* "
done
echo
done
排列与组合(打印1-3的所有排列组合方式)
[root@ztj test06]# vim compose.sh
#!/bin/bash
for i in {1..3}
do
for j in {1..3}
do
echo ${i}${j}
done
done
[root@ztj test06]# chmod +x compose.sh
[root@ztj test06]# ./compose.sh
打印形状(打印正的阶梯色块)
[root@ztj test06]# vim sharp.sh
#!/bin/bash
for((i=1;i<=6;i++))
do
for((j=1;j<=i;j++))
do
echo -ne "\033[42m \033[0m"
done
echo
done
[root@ztj test06]# chmod +x sharp.sh
[root@ztj test06]# ./sharp.sh
打印倒着的阶梯色块
[root@ztj test06]# vim sharp.sh
#!/bin/bash
for((i=1;i<=6;i++))
do
for((j=6;j>=i;j--))
do
echo -ne "\033[42m \033[0m"
done
echo
done
[root@ztj test06]# ./sharp.sh
将前面两个打印出来的色块拼接
[root@ztj test06]# vim sharp.sh
#!/bin/bash
for((i=1;i<=6;i++))
do
for((j=1;j<=i;j++))
do
echo -ne "\033[42m \033[0m"
done
echo
done
for((i=1;i<=5;i++))
do
for((j=5;j>=i;j--))
do
echo -ne "\033[42m \033[0m"
done
echo
done
[root@ztj test06]# ./sharp.sh
2、带菜单的脚本
带菜单的脚本
[root@ztj test06]# vim menu.sh
#!/bin/bash
echo "1.查看剩余内存容量."
echo "2.查看根分区剩余容量."
echo "3.查看CPU十五分钟负载."
echo "4.查看系统进程数量."
echo "5.查看系统账户数量."
echo "6.退出."
while :
do
read -p "请输入选项[1-6]:" key
case $key in
1)
free | awk '/Mem/{print $NF}' ;;
2)
df | awk '/\/$/{print $4}' ;;
3)
uptime | awk '{print $NF}' ;;
4)
ps aux | wc -l ;;
5)
sed -n '$=' /etc/passwd ;;
6)
[root@ztj test06]# chmod +x menu.sh
[root@ztj test06]# ./menu.sh
3、备份数据
备份日志
[root@ztj test06]# vim bak_log.sh
#!/bin/bash
date=`date +%Y%m%d`
if [ ! -f /tmp/log-$date.tar.gz ];then
tar -czf /tmp/log-$date.tar.gz /var/log/
fi
[root@ztj test06]# chmod +x bak_log.sh
[root@ztj test06]# ./bak_log.sh
备份数据库,逻辑备份
[root@ztj test06]# yum -y install mariadb mariadb-server
[root@ztj test06]# systemctl restart mariadb
[root@ztj test06]# vim mysqldump.sh
#!/bin/bash
date=$(date +%Y%m%d)
iuser=root
ipass=
db=mysql
if [ ! -f /tmp/$db-$date.sql ];then
mysqldump -u$iuser --password="$ipass" $db > /tmp/$db-$date.sql
fi
[root@ztj test06]# chmod +x mysqldump.sh
[root@ztj test06]# ./mysqldump.sh
物理备份
[root@ztj test06]# vim bak_mysql.sh
#!/bin/bash
date=$(date +%Y%m%d)
db_dir="/var/lib/mysql"
db=mysql
[ ! -d /tmp/$db ] && mkdir /tmp/$db
for i in $(ls $db_dir/$db)
do
tar -czf /tmp/$db/$i-$date.tar.gz $db_dir/$db/$i
done
[root@ztj test06]# chmod +x bak_mysql.sh
[root@ztj test06]# ./bak_mysql.sh
差异备份(Inotify+sync)
[root@ztj test06]# vim insync.sh
#!/bin/bash
FROM_DIR="/var/www/html/"
RSYNC_CMD="rsync -az --delete $FROM_DIR root@192.168.4.207:/var/www/html"
while inotifywait -rqq -e modify,move,create,delete,attrib $FROM_DIR
do
$RSYNC_CMD
done &
[root@ztj test06]# ./insync.sh
4、安全脚本
HSAH值
HSAH值与文件名称、时间、大小等信息无关,仅与内容有关
查看/etc/passwd文件HSAH值
[root@svr5 ~]# md5sum /etc/passwd #文件内容不改变校验结果一样
[root@svr5 ~]# cp /etc/passwd /root/pass
[root@svr5 ~]# md5sum /root/pass #校验结果和/etc/passwd检验结果一样
[root@svr5 ~]# vim /root/pass #任意修改一行内容
[root@svr5 ~]# md5sum /root/pass #校验结果和/etc/passwd检验结果不同
[root@svr5 ~]# sha512sum /etc/passwd #计算hash值的长度不同
[root@svr5 ~]# sha256sum /etc/passwd
数据安全监测脚本
[root@ztj ~]# cd /root/shell/test06/
[root@ztj test06]# vim data.sh
#!/bin/bash
for i in $(ls /etc/*.conf)
do
md5sum $i >> /tmp/data.log
done
[root@ztj test06]# chmod +x data.sh
[root@ztj test06]# ./data.sh
通过脚本修改SSH配置文件
sshd主配置文件: /etc/ssh/sshd_config
Port 3389 //改用非标准端口
PermitRootLogin no //禁止root登录
UseDNS no //不解析客户机地址
AllowUsers 账户名 //设置远程连接的白名单,多个用户空格分割
[root@ztj test06]# cp /etc/ssh/sshd_config ./ #拷贝配置文件到当前目录
[root@ztj test06]# vim ssh_config.sh
#!/bin/bash
conf="./sshd_config"
sed -i '/^#Port/s/22/1122/' $conf
sed -i '/^#PermitRootLogin/s/yes/no/' $conf
sed -i '/^#UseDNS/s/yes/no/' $conf
sed -i '$a AllowUsers tom' $conf
systemctl restart sshd
[root@ztj test06]# chmod +x ssh_config.sh
[root@ztj test06]# ./ssh_config.sh
格式化输入passwd
[root@svr5 ~]# awk -F: 'BEGIN{print "用户名 UID 家目录"}'
[root@svr5 ~]# awk -F: 'BEGIN{print "用户名 UID 家目录"} {print $1 ,$3,$6}' /etc/passwd | column -t # column -t 主要作用使用tab键缩进排版
过滤系统账户中对应的密码
在awk中可以通过-v选项调用shell中的变量
[root@svr5 ~]# hello="nihao"
[root@svr5 ~]# awk 'BEGIN{print hello}' #无输出结果
[root@svr5 ~]# awk 'BEGIN{print "hello"}' #打印字符串hello
[root@svr5 ~]# awk 'BEGIN{hello=123;print hello}'
123
[root@svr5 ~]# hello="nihao"
[root@svr5 ~]# awk -v x=$hello 'BEGIN{print x}'
nihao
从/etc/passwd中将所有能登陆的账户名提取出来
从/etc/shadow中提取账户对应的密码
[root@ztj test06]# vim userpass.sh
#!/bin/bash
USER=$(awk -F: '/bash$/{print $1}' /etc/passwd)
for i in $USER
do
awk -F: -v x=$i '$1==x{print $1,$2}' /etc/shadow
done
[root@ztj test06]# chmod +x userpass.sh
[root@ztj test06]# ./userpass.sh
5、综合脚本
部署PXE+kicstart环境
DHCP服务,TFTP服务,HTTP服务,kickstart配置
[root@svr5 ~]# cat /root/shell/test06/pxe.sh
#!/bin/bash
DHCP_NET=192.168.4.0
DHCP_NETMASK=255.255.255.0
DHCP_MINIP=192.168.4.100
DHCP_MAXIP=192.168.4.200
DHCP_ROUTER=192.168.4.1
DHCP_NEXT_SERVER=192.168.4.7
HTTP_IP=192.168.4.7
#安装软件包.
yum -y install httpd dhcp tftp-server syslinux
#临时停用SELinux
setenforce 0
#配置DHCP服务
cat > /etc/dhcp/dhcpd.conf << EOF
default-lease-time 600;
max-lease-time 7200;
subnet $DHCP_NET netmask $DHCP_NETMASK {
range $DHCP_MINIP $DHCP_MAXIP;
option routers $DHCP_ROUTER;
next-server $DHCP_NEXT_SERVER;
filename "pxelinux.0";
}
EOF
systemctl start dhcpd
systemctl enable dhcpd
#配置httpd共享服务器.
if [ ! -e /dev/cdrom ];then
echo "未检测到系统光盘/dev/cdrom,请插入光盘后再试."
exit
fi
[ -d /var/www/html/cdrom ] || mkdir /var/www/html/cdrom
mount /dev/cdrom /var/www/html/cdrom
systemctl start httpd
#配置kickstart文件.
cat > /var/www/html/ks.cfg << EOF
install
keyboard 'us'
rootpw --plaintext redhat
url --url="http://$HTTP_IP/cdrom"
lang en_US
firewall --disabled
auth --useshadow --passalgo=sha512
text
selinux --disabled
skipx
network --bootproto=dhcp --device=eth0
reboot
timezone Asia/Shanghai
bootloader --location=mbr
zerombr
clearpart --all --initlabel
part /boot --fstype="xfs" --size=500
part / --fstype="xfs" --grow --size=1
%packages
@minimal
@core
%end
EOF
#配置tftp服务器.
cp /usr/share/syslinux/pxelinux.0 /var/lib/tftpboot/
cp /var/www/html/cdrom/isolinux/* /var/lib/tftpboot/
[ -d /var/lib/tftpboot/pxelinux.cfg ] || mkdir /var/lib/tftpboot/pxelinux.cfg/
cat > /var/lib/tftpboot/pxelinux.cfg/default <<EOF
default vesamenu.c32
timeout 600
label linux
menu label ^Install CentOS 7
kernel vmlinuz
append initrd=initrd.img ks=http://$HTTP_IP/ks.cfg
EOF
systemctl start tftp
iptables -F
END…
shell学习总结:
提示:以上为本人学习shell的过程笔记,学会以上技能基本上可以胜任80%场景的shell自动化需求;千里之行始于足下,共勉!
学习进度小打卡:
- SHELL基础
- shell脚本的设计与运行
- 变量的扩展运用
- shell中的运算
- 条件测试
- shell函数
- 字符处理与变换初始化
- 正则表达式与grep语法格式
- sed基础
- awk基础语法
- 综合案例练习