目录
shell脚本利用循环调用expect在CentOS和Ubuntu上批量创建用户
使用read命令来接受输入
每个管道符的输出都是运行在独立的子进程中
[root@CentOS ~]# echo 1 3 | read a c ; echo a=$a c=$c
a= c=
[root@CentOS ~]# echo 1 3 | { read a c;echo $a $c; }
1 3
[root@CentOS ~]# echo 1 3 |(read a c;echo $a $c)
1 3
使用read来把输入值分配给一个或多个shell变量,read从标准输入中读取值,给每个单词分配一个变量,所有剩余单词都被分配给最后一个变量。需要精确把控
-p 指定要显示的提示
-s 静默输入,一般用于密码
-n N 指定输入的字符长度N
-d '字符' 输入结束符
-t N TIMEOUT为N秒
[root@CentOS ~]# read name wang wei
123 456 789
[root@CentOS ~]# echo $wei
789
[root@CentOS ~]# echo $wang
456
[root@CentOS ~]# echo $name
123
if语句
单分支
if 判断条件; then
条件为真的分支代码
fi
双分支
if 判断条件; then
条件为真的分支代码
else
条件为假的分支代码
fi
多分支
if 判断条件1; then
条件1为真的分支代码
elif 判断条件2; then
条件2为真的分支代码
elif 判断条件3; then
条件3为真的分支代码
...
else
以上条件都为假的分支代码
fi
条件判断 case 语句 就是判断对错是否成立
#!/bin/bash
read -p "please input a word ,and user enter to check:" KEY
#输入关键词
case "$KEY" in
[a-z]|[A-Z]) #判断他的格式为字符
echo "what you input is word";;
[0-9]) #判断他的格式为数字
echo "what you input is number";;
*) #判断他的格式为符号
echo "what you input is symbol";;
esac
case支持glob风格的通配符:
* 任意长度任意字符
? 任意单个字符
[] 指定范围内的任意单个字符
| 或者,如: a|b
循环while
这种循环就很简单直白了,如果循环表达式为真就执行循环体, 每次执行循环体前都要先判断下,知道循环表达式为假就结束循环。
true (敲他永远为真) filse(敲他永远为假)
int n=10;
while(n>0)
{
printf("看到这里的小可爱最帅/最美\n");
n--;
}
报警通知
dd if=/dev/zero of=/boot/f1.img bs=1M count=700
#!/bin/bash
WAPNING=80
while :;do
USE=`df |sed -rn '/^\/dev\/sd/s#.* ([0-9]+)%.*#\1#p' |sort -nr|head -n1`
if [ $USE -gt $WAPNING ];then
echo DISk will de full from `hostname -I` | mail -s "disk warning" 704318431@qq.com
fi
sleep 10
done
循环 until
正确true退出, 错误false循环
until COMMANDS; do COMMANDS; done
until CONDITION; do
循环体
done无限循环
until false; do
循环体
Done
循环 for
使用glob,如:*.sh
变量引用,如:$@,$*,$# #
- $@是整体 全部参数合为一个字符串
- $*是单个 每个参数是单独字符串
- $# 传递给脚本的参数个数
同理位置参数它可以代表多个参数
[root@CentOS shell]# bash for.sq 2 3 5
sum=10
[root@CentOS shell]# cat for.sq
#!/bin/bash
sum=0
for i in $@; do
let sum+=i
done
echo sum=$sum
for NAME [in WORDS ... ] ; do COMMANDS; done
#方式1
for 变量名 in 列表;do
循环体
done
#方式2
for 变量名 in 列表
do
循环体
done
[root@CentOS wang]# for i in $(seq 10);do echo i=$i;done
i=1
i=2
i=3
i=4
i=5
i=6
i=7
i=8
i=9
i=10
[root@CentOS wang]# for i in `seq 10`;do echo i=$i;done 和上面一个意思
[root@CentOS wang]# sum=0;for i in {1..100};do let sum+=i;done;echo sum=$sum
sum=5050
[root@CentOS wang]# seq -s+ 100|bc
5050
9*9乘法表
for i in {1..9};do
for j in `seq $i`;do
echo -e "${j}x${i}=$[j*i]\t\c"
done
echo
done
-e 是用于启用 echo 命令的转义字符解释功能的选项
\t 是转义字符,表示制表符,用于在乘法表达式和结果之间创建一些间距。
\c 是另一个转义字符,用于告诉 echo 命令在输出行的末尾不要添加换行符(即不换行),这样可以在同一行上打印多个乘法表达式和结果。
echo:这个命令在每次内部循环完成后输出一个换行符,以开始新的行。
倒叙的
for i in {1..9};do
for j in $(seq `echo $[10-$i]`);do
echo -ne "${j}x`echo $[10-i]`=$(((10-i)*j))\t"
done
echo
done
检测正在运行的主机
[root@CentOS shell]# cat for_scan_host.sh
#!/bin/bash
NET=10.0.0
for ID in {1..254};do
{
ping -c1 -W1 NET.NET.ID &> /dev/nbll && echo NET.NET.ID is up || echo NET.NET.ID is down
}&
done
wait
打印三角形
#!/bin/bash
read -p "请输入三角形的行数" line
for ((i=1;i<=$line;i++));do
for ((k=0;k<=$line-i;k++));do
echo -e ' \c'
done
for((j=1;j<=2*i-1;j++));do
echo -e '*\c'
done
echo
done
*
***
*****
*******
*********
***********
*************
***************
*****************
*******************
循环中本轮退出循环continue
[root@CentOS shell]# vim aa.sh
[root@CentOS shell]# cat aa.sh
for ((i=0;i<10;i++));do
for((j=0;j<10;j++));do
[ $j -eq 5 ] && continue
echo $j
done
echo ---------------------------
done
---------------------------
0
1
2
3
4
6
7
8
9
---------------------------、
[root@CentOS shell]# cat aa.sh
for ((i=0;i<10;i++));do
for((j=0;j<10;j++));do
[ $j -eq 5 ] && continue 2 2就是跳过外层循环 所以才0-4
echo $j
done
echo ---------------------------
done
[root@CentOS shell]# bash aa.sh
0
1
2
3
4
0
1
2
3
4
提前结束第N层整个循环 break
最内层为第1层
[root@CentOS shell]# cat aa.sh
for ((i=0;i<10;i++));do
for((j=0;j<10;j++));do
[ $j -eq 5 ] && break #break是结束当前整轮循环,所以是0-4,这是循环套循环,里面的循环结束就执行外面的循环所以才有------
echo $j
done
echo ---------------------------
done
aa.sh
0
1
2
3
4
---------------------------
0
1
2
3
4
---------------------------
循环逐个参数处理 shift
shift [n] 用于将参量列表 list 左移指定次数,缺省为左移一次。
参量列表 list 一旦被移动,最左端的那个参数就从列表中删除。while 循环遍历位置参量列表时,常用到 shift
PASS=123456
while [ "$1" ];do
useradd $1 && echo $1 is created || echo $1 is exist
echo $PASS | passwd --stdin $1 &> /dev/null
shift
done
[root@CentOS wang]# bash user.sh dd gg bb
dd is created
更改用户 dd 的密码 。
passwd:所有的身份验证令牌已经成功更新。
gg is created
更改用户 gg 的密码 。
passwd:所有的身份验证令牌已经成功更新。
useradd:用户“bb”已存在
bb is exist
更改用户 bb 的密码 。
passwd:所有的身份验证令牌已经成功更新。
[root@CentOS wang]# tail /etc/passwd
wei:x:1003:1003::/home/wei:/bin/bash
ss:x:1004:1004::/home/ss:/bin/bash
xx:x:1005:1005::/home/xx:/bin/bash
防止Dos攻击的脚本
WARNING=10
touch deny_hosts.txt
while true;do
ss -nt | sed -nr '1!s#.* ([0-9.]+):[0-9]+ *#\1#p'|sort |uniq -c|sort |
while read count ip;do
if [ $count -gt $WARNING ];then
echo $ip is deny
grep -q "$ip" deny_hosts.txt || { echo $ip >> deny_hosts.txt;
iptables -A INPUT -s $ip -j REJECT; }
fi
done
sleep 10
done
fork 炸弹
是一种恶意程序,它的内部是一个不断在 fork 进程的无限循环,实质是一个简单的递归程序。由于程序是递归的,如果没有任何限制,这会导致这个简单的程序迅速耗尽系统里面的所有资源
参考:https://en.wikipedia.org/wiki/Fork_bomb
一个意思
:(){ :|:& };:
bomb() { bomb | bomb & }; bomb
循环与菜单 selec
- select 循环主要用于创建菜单,按数字顺序排列的菜单项显示在标准输出上
- 用户输入菜单列表中的某个数字,会将对应的WORD值赋值给NAME变量
- 用户输入被保存在内置变量 REPLY 中
- PS3显示提示信息
[root@CentOS ~]# select MENU in 北京烤鸭 佛跳墙 小龙虾 羊蝎子 火锅 点菜结束;do echo $MENU;done
1) 北京烤鸭
2) 佛跳墙
3) 小龙虾
4) 羊蝎子
5) 火锅
6) 点菜结束
#? 2
佛跳墙
cat select.sh
#!/bin/bash
#
sum=0
PS3="请点菜(1-6): "
select MENU in 北京烤鸭 佛跳墙 小龙虾 羊蝎子 火锅 点菜结束;do
case $REPLY in
1)
echo $MENU 价格是 100
let sum+=100
;;
2)
echo $MENU 价格是 88
let sum+=88
;;
3)
echo $MENU价格是 66
let sum+=66
;;
4)
echo $MENU 价格是 166
let sum+=166
;;
5)
echo $MENU 价格是 200
let sum+=200
;;
6)
echo "点菜结束,退出"
break
;;
*)
echo "点菜错误,重新选择"
;;
esac
done
echo "总价格是: $sum"
[root@CentOS wang]# bash select.sh
1) 北京烤鸭
2) 佛跳墙
3) 小龙虾
4) 羊蝎子
5) 火锅
6) 点菜结束
请点菜(1-6): 2
佛跳墙 价格是 88
请点菜(1-6): 3
小龙虾价格是 66
请点菜(1-6): 1
北京烤鸭 价格是 100
请点菜(1-6): 6
点菜结束,退出
总价格是: 254
函数介绍
- 函数function是由若干条shell命令组成的语句块,实现代码重用和模块化编程
- 它与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,而是shell程序的一部分
语法
#语法一:
func_name (){
...函数体...
}
#语法二:
function func_name {
...函数体...
}
#语法三:
function func_name () {
...函数体...
}
查看函数
#查看当前已定义的函数名
declare -F
#查看当前已定义的函数定义
declare -f
#查看指定当前已定义的函数名
declare -f func_name
#查看当前已定义的函数名定义
declare -F func_name
类拟于环境变量,也可以定义环境函数,使子进程也可使用父进程定义的函数
export -f function_name
declare -xf function_name
查看环境函数:
export -f
declare -xf
删除函数
unset func_name
范例
:实现判断CentOS的主版本
[root@centos8 ~]#centos_version() {
> sed -rn 's#^.* +([0-9]+)\..*#\1#p' /etc/redhat-release
> }
[root@centos8 ~]#centos_version
8
在脚本中定义及使用函数
disable_selinux(){
sed -i.bak 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
echo "SElinux已禁用,重新启动后才可生效"
}
disable_firewall(){
systemctl disable --now firewalld &> /dev/null
echo "防火墙已禁用"
}
set_ps1() {
echo "PS1='\[\e[1;35m\][\u@\h \W]\\$\[\e[0m\]'" > /etc/profile.d/reset.sh
echo "提示符已修改成功,请重新登录生效"
}
set_eth(){
sed -i.bak '/GRUB_CMDLINE_LINUX=/s#"$# net.ifnames=0"#' /etc/default/grub
grub2-mkconfig -o /boot/grub2/grub.cfg &> /dev/null
echo "网络名称已修改成功,请重新启动才能生效"
}
PS3="请选择相应的编号(1-6): "
MENU='
禁用SELinux
关防火墙
修改提示符
修改网卡名
以上全实现
退出
'
select M in $MENU ;do
case $REPLY in
1)
disable_selinux
;;
2)
disable_firewall
;;
3)
set_ps1
;;
4)
set_eth
;;
5)
disable_selinux
disable_firewall
set_ps1
set_eth
;;
6)
break
;;
*)
echo "请输入正确的数字"
esac
done
[root@CentOS wang]# bash hanshu.sh
1) 禁用SELinux 3) 修改提示符 5) 以上全实现
2) 关防火墙 4) 修改网卡名 6) 退出
请选择相应的编号(1-6): 1
SElinux已禁用,重新启动后才可生效
请选择相应的编号(1-6): 2
防火墙已禁用
请选择相应的编号(1-6): 3
提示符已修改成功,请重新登录生效
请选择相应的编号(1-6): 4
网络名称已修改成功,请重新启动才能生效
请选择相应的编号(1-6): 6
函数变量
在函数中定义本地变量的方法
注意:
- 如果函数中定义了普通变量,且名称和局部变量相同,则使用本地变量
- 由于普通变量和局部变量会冲突,建议在函数中只使用本地变量
local NAME=VALUE 在当前进程有效。子进程无效
显示成功和失败的颜色 sentos有 ubuntu没有
[root@CentOS ~]#cd /etc/init.d/
[root@CentOS init.d]#. functions
[root@CentOS init.d]#action "rm -rf /data"
rm -rf /data [ 确定 ]
[root@CentOS init.d]#action "rm -rf /data" false
rm -rf /data [失败]
函数返回值
使用exit退出的是函数和脚本,使用return退出的是函数return只能用函数
信号捕捉 trap 修改信号原来的功能,实现自定义功能
#进程收到系统发出的指定信号后,将执行自定义指令,而不会执行原操作
trap '触发指令' 信号
#忽略信号的操作
trap '' 信号
#恢复原信号的操作
trap '-' 信号
#列出自定义信号操作
trap -p
#当脚本退出时,执行finish函数
trap finish EXIT
当脚本正常或异常退出时,也会执行finish函数
退出了在屏幕显示finish并且在 /root/finish.log追加finish
finish(){
echo finish| tee -a /root/finish.log
}
trap finish exit
while true ;do
echo running
sleep 1
done
创建临时文件 mktemp
mktemp 命令用于创建并显示临时文件,可避免冲突
root@CentOS wang]#mktemp
/tmp/tmp.clr8lTS870-d #创建临时目录
-p DIR或--tmpdir=DIR #指明临时文件所存放目录位置实例
[root@CentOS wang]#mktemp 123XXX
123ooz
[root@CentOS wang]#mktemp XXX.log
uQk.log
安装复制文件 install
install 功能相当于cp,chmod,chown,chgrp ,mkdir 等相关工具的集合
-m MODE,默认755
-o OWNER
-g GROUP
-d DIRNAME 目录
交互式转化批处理工具 expect
expect 是由Don Libes基于 Tcl( Tool Command Language )语言开发的,主要应用于自动化交互式操作的场景
可以将交互过程如:ssh登录,ftp登录等写在一个脚本上,使之自动化完成
-c:从命令行执行expect脚本,默认expect是交互地执行的
-d:可以调试信息
expect中相关命令
- spawn 启动新的进程
- expect 从进程接收字符串
- send 用于向进程发送字符串
- interact 允许用户交互
- exp_continue 匹配多个字符串在执行动作后加此命令
单一分支模式语法: 匹配到hi后,会输出“you said hi”,并换行
[root@centos8 test]#expect
expect1.1> expect "hi" {send "You said hi\n"}
hahahixixi
You said hi
多分支模式语法:
[root@centos8 ~]#expect
expect1.1> expect "hi" { send "You said hi\n" } "hehe" { send "Hehe yourself\n"
} "bye" { send "Good bye\n" }
hehe
Hehe yourself
expect1.2> expect "hi" { send "You said hi\n" } "hehe" { send "Hehe yourself\n"
} "bye" { send "Good bye\n" }
bye
Good bye
expect1.3> expect "hi" { send "You said hi\n" } "hehe" { send "Hehe yourself\n"
} "bye" { send "Good bye\n" }
hi
You said hi
expect1.4>
[root@centos8 ~]#expect
expect1.1> expect {
+> "hi" { send "You said hi\n"}
+> "hehe" { send "Hehe yourself\n"}
+> "bye" { send " Good bye\n"}
+> }
bye
Good bye
非交互式复制文件 不是shell不能用bash
#!/usr/bin/expect
spawn scp /etc/redhat-release 10.0.0.7:/data
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "magedu\n" }
}
expect eof
自动登录
#!/usr/bin/expect
spawn ssh 10.0.0.7
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "magedu\n" }
}
interact
expect 变量
#!/usr/bin/expect
set ip 10.0.0.7 #就是shell的ip=10.0.0.7
set user root
set password magedu
set timeout 10 #等待10秒
spawn ssh $user@$ip #spawn交互
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "$password\n" }
}
interact
expect 位置参数
[root@centos8 ~]#cat expect4
#!/usr/bin/expect
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
spawn ssh $user@$ip
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "$password\n" }
}
interact
[root@centos8 ~]#./expect4 10.0.0.7 root magedu
shell脚本调用 expect
#!/bin/bash
ip=$1
user=$2
password=$3
expect <<EOF
set timeout 20
spawn ssh $user@$ip
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "$password\n" }
}
expect "]#" { send "useradd hehe\n" }
expect "]#" { send "echo magedu |passwd --stdin hehe\n" }
expect "]#" { send "exit\n" }
expect eof
EOF
#./ssh5.sh 192.168.8.10 root magedu
shell脚本利用循环调用expect在CentOS和Ubuntu上批量创建用户
NET=10.0.0
user=root
password=magedu
IPLIST="
7
18
101
"
for ID in $IPLIST;do
ip=$NET.$ID
expect <<EOF
set timeout 20
spawn ssh $user@$ip
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "$password\n" }
}
expect "#" { send "useradd test\n" }
expect "#" { send "exit\n" }
expect eof
EOF
done
NET=10.0.0
user=root
password=magedu
IPLIST="
7
18
"
for ID in $IPLIST ;do
ip=$NET.$ID
expect <<EOF
set timeout 20
spawn ssh $user@$ip
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "$password\n" }
}
expect "#" { send "sed -i 's/^SELINUX=enforcing/SELINUX=disabled/'
/etc/selinux/config\n" }
expect "#" { send "setenforce 0\n" }
expect "#" { send "exit\n" }
expect eof
EOF
done
数组
- 变量:存储单个元素的内存空间
- 数组:存储多个元素的连续的内存空间,相当于多个变量的集合
declare -a 显示全部数组
echo ${#alpha[@]} 显示数组的个数
[root@CentOS wang]#read -a menu
zhangsan lisi wangwu zhaoliu
[root@CentOS wang]#echo menu
menu
[root@CentOS wang]#declare -a
declare -a menu='([0]="zhangsan" [1]="lisi" [2]="wangwu" [3]="zhaoliu")'
[root@CentOS wang]#echo ${menu[*]}
zhangsan lisi wangwu zhaoliu
[root@CentOS wang]#echo ${menu[1]}
lisi
[root@CentOS wang]#echo ${menu[0]}
zhangsan
删除数组
[root@centos8 ~]#echo ${title[*]}
ceo coo cto
[root@centos8 ~]#unset title[1]
[root@centos8 ~]#echo ${title[*]}
ceo cto
[root@centos8 ~]#unset title
[root@centos8 ~]#echo ${title[*]}
[root@centos8 ~]#
数组数据切片处理
[root@centos8 ~]#num=({0..10})
[root@centos8 ~]#echo ${num[*]:2:3}
2 3 4
[root@centos8 ~]#echo ${num[*]:6}
6 7 8 9 1
向数组中追加元素:
[root@CentOS wang]#num=({0..5})
[root@CentOS wang]#echo ${#num[@]}
6
[root@CentOS wang]#num[6]=6
[root@CentOS wang]#echo ${#num[@]}
7
[root@CentOS wang]#num[${#num[@]}]=7
[root@CentOS wang]#echo ${#num[@]}
8
[root@CentOS wang]#echo ${num[@]}
0 1 2 3 4 5 6 7
[root@CentOS wang]#echo ${num[-2]}
6
生成10个随机数保存于数组中,并找出其最大值和最小值
#!/bin/bash
declare -i min max
declare -a nums
for ((i=0;i<10;i++));do
nums[$i]=$RANDOM
[ $i -eq 0 ] && min=${nums[0]} && max=${nums[0]}&& continue
[ ${nums[$i]} -gt $max ] && max=${nums[$i]} && continue
[ ${nums[$i]} -lt $min ] && min=${nums[$i]}
done
echo "All numbers are ${nums[*]}"
echo Max is $max
echo Min is $min
基于模式取子串
#其中word可以是指定的任意字符,自左而右,查找var变量所存储的字符串中,第一次出现的word, 删除字
符串开头至第一次出现word字符串(含)之间的所有字符,即懒惰模式,以第一个word为界删左留右
${var#*word}
#从var变量的值中删除以word开头的部分
${var#word}
#同上,贪婪模式,不同的是,删除的是字符串开头至最后一次由word指定的字符之间的所有内容,即贪婪模
式,以最后一个word为界删左留右
${var##*word}
${var##word}
[root@centos8 ~]#file="var/log/messages"
[root@centos8 ~]#echo ${file#*/}
log/messages
[root@centos8 ~]#echo ${file##*/}
messages
从右往左
[root@CentOS wang]#echo ${file%%/*}
var
[root@CentOS wang]#echo ${file%/*}
var/log