基础
- shell脚本就是命令的集合,
ping -c1 www.baidu.com &> /dev/null && echo "baidu is up" || echo "baidu is down"
- 前后两个&的含义不同,&>包括标准输出和错误输出,重定向到null去
- 程序就是由:指令(逻辑)+数据组成的
- /usr/bin/cat <<-EOF
111
test222
EOF -的作用是支持EOF的tab操作。不一定是EOF,可以换成其他的,只是格式样的
- 同样的,这种用法可以将你在屏幕上输入的内容重定向到一新文件中,命令行添加 > file1
- 系统级,/etc/profile /etc/bashrc
- 用户级,~/.bash_profile ~/.bashrc,当退出时会执行,~/.bash_logout ~/.bash_history
- su - xiaobai,也就是login shell 时,执行以上四个文件
- su xiaobai,也就是nologin shell时,执行 /etc/bashrc ~/.bashrc
- bash shell特点
- 命令历史记忆功能:!number,!string,!$上一条命令最后一个参数,!!上一个命令,^R搜索历史命令
data > test.txt
data |tee test.txt
tee不会节流,将结果打印出来
- 通配符(元字符)
- [] 表示只匹配满足其中条件的一个
- ()在子shell中执行 (cd /boot;ls) (umask 077;touch file1000)
- 集合,
cp -rv /etc/sysconfig/network-scripts/ifcfg-eth0{,.old}
- 意思是创建ifcfg-eth0和ifcfg.old两个文件
- \ 转义符,让元字符回归本意。只有紧跟的那个参数才会被转转义
- 只是使命令中的特定文字为红色,不影响命令外面的颜色
echo -e "\e[1;31mThis is a red text.\e[0m"
- 前景色,从31..37
- 背景色,从40..47
- 脚本中使用变量
read -p “Please input a username: “ user
当然,先运行一下 id $user &>/dev/null也是可以
if id $user &>/dev/null;then echo “user $user already exists” else useradd $user if [ $? -eq 0 ];then echo “$user is created” fi fi
- **""弱引用;''强引用,不管是不是变量**
- disk_free= `df -Ph |grep '/$' |awk '{print $4}'`
- disk_free2=$(df -Ph |grep '/$' |awk '{print $4}')
6. 数字运算, `echo $[2**10]` ,或用expr,或是 `echo $((2**3))` ,或是 `let num=1+2;echo $num`
6. 变量内容的删除和替换
- ${url#*si} 删除变量url,从开始到si,取si后面的作为新的变量内容
- ${url#*.} 从前往后,最短匹配;${url##*.} 从前往后,最长匹配贪婪匹配。一直到最后一个.的内容都被匹配上
- ${url%.*};${url%%.*} 从后往前匹配
1. 索引及切片,${url} 字符串是从0开始
- ${url:5:5};${url:5}<br />
```bash
#!/bin/bash
mem_used=`free -m | grep '^Mem:' |awk '{print $3}'`
mem_total=`free -m | grep '^Mem:' |awk '{print $2}'`
mem_present=$((mem_used*100/mem_total))
war_file=/tmp/mem_war.txt
rm -rf $war_file
if (($mem_percent>80));then
#if [ $mem_percent -ge 80 ];then可以改成ge大于等于
echo "`date +%F-%H` memory:${mem_precent}%" > $war_file
fi
if [ -f $war_file ];then
mail -s "mem warning..." alice < $war_file
rm -rf $war_file
fi
- 字符串最好引号引起来,`[ “$USER” = “root” ];echo $?
#...........
#...........
read -p "Please input number: " num
while true
do
if [[ !"$num" =~ ^[0-9]+$ || "$num" =~ ^0+$]];then #避免输入以0开头或多个0的
break #跳出这个循环
else
read -p "不是数字,请重新输入数值:" num
fi
done
#同样,下面的判断也可以做
read -p "Please input prefix: " prefix
if [ -z "$prefix" ];then #字符串长度不能为0,为0的话就退出
echo "error prefix"
exit
fi
for i in `seq $num`
do
user=$prefix$i
useradd $user
echo "123" |passwd --stdin $user &>/dev/null
if [ $? -eq 0 ];then
echo "$user is created."
fi
done
- ()子shell中执行
- (())数值比较,运算C语言
- $()命令替换
- $(())整数运算
- []条件测试
- [[]]条件测试,支持正则 =~
- $[]整数运算
- {}
- ${}
- until和while语法结构类似,不过两者含义相反。都适合写循环次数不固定的语句
- until当条件测试成立(条件测试为假),执行循环体
until [ 1 -eq 2 ]
do
date;sleep 1
done
vim install_apache.sh
#!/bin/bash
#install apache in centos7
#v1.0 by xxx 2020-xx-xx
gateway=192.168.10.1
if ping -c1 www.baidu.com &>/dev/null;then
yum install -y ....
sed -ri '/^SELINUX=/cSELINUX=disabled' /etc/selinux/config
curl http://127.0.0.1 &>/dev/null
if [ $? -eq 0 ];then
echo "Apacha ok..."
fi
elif ping -c1 $gateway &>/dev/null;then
echo "网关正常,请检查dns或其他..."
exit
else
echo "check ip address!"
fi
fi
###
read -p "确认开始安装KVM[y]" kvm_install
if [ !"${kvm_install}"="y"];then
echo -e "$red_col输入不正确!$reset_col"
exit
fi
删除用户
read -p "Please input a username: " user
id $user &>/dev/null
if [ $? -ne 0 ];then
echo "no such user: $user"
exit 1 #第一种错,返回1
fi
read -p "Are you sure[y/n]: " action
#if [ "$action" != "y" -a "$action" != "Y" -a "$action" != "yes" -a "$action" != "YES" ];then
# echo "good!"
# exit
#fi
case "$action" in
y|Y|yes|YES)
userdel -r $user
echo "$user is deleted!"
;;
*)
echo "error"
esac
#userdel -r $user && echo "$user is deleted!"
简单的JumpServer
跳板机属于内控堡垒机范畴,是一种用于单点登陆的主机应用系统。
使用alice用户登录到跳板机,然后脚本就运行了,所以脚本放在用户的家目录,
vim jumpserver.sh
- 业务服务器不允许直接连接,通过允许从跳板机连接
- 业务服务器不允许root用户直接登录
#!/bin/bash
#jumpserver
trap "" HUP INT OUIT TSTP #捕捉键盘信号,然后什么操作也没有
web1=192.168.10.1
web2=192.168.10.2
mysql1=192.168.10.3
clear
while :
do
cat <<-EOF
+----------------------------------------------------+
|JumpServer |
|1. web1 |
|2. web2 |
|3. mysql1 |
+----------------------------------------------------+
EOF
# read -p "input number: " num
echo -en "\e[1;32minput number: \e[0m" #n表示不要换行
read num
case "$num" in
1)
ssh alice@$web1
;;
2)
ssh alice@$web2
;;
3)
ssh alice@$mysql1
;;
"")
;;
*)
echo "error"
esac
done
将执行脚本的语句,加入到alice的bash_profile里,就可以登录时自动运行该脚本,
vim .bash_profile
- 在ssh连接时,使用秘钥的方式,可以不需用户输入密码就可登录到其他Server
- ssh-copy-id root@192.168.10.1,相当于以root身份登录到192.168.10.1去
- 当然,脚本里也要改
实现系统工具箱
#!/bin/bash
#system manage
#v1.0
menu() {
cat <<-EOF
h. help
f. disk partition
d. filesystem mount
m. memory
u. system load
q. exit
EOF
} #函数,一个代码块,为了实现重复调用
menu
while : #表示为真,true也可以,循环。但exit同样可以退出
do
read -p "Please input[h for help]: " action
case "$action" in
h) clear;menu;;
f) fdisk -l;;
d) df -Th;;
m) free -m;;
u) uptime;;
q)
#exit #退出程序更直接
break #退出循环
;;
"") ;;
*) echo "error"
esac
done
echo "Finish..."
- 实现多版本php安装。
vim main_install.sh
通过一个主要的脚本来调用其他的安装脚本。就类似于ansible的剧本文件,main.yml来调用其他的剧本文件批量主机ping探测
#!/bin/bash
>ip.txt
for i in {2..254} #`seq 2 254` 产生一个序列
#for i in `cat ip.txt` ping的对象来自特定的一部分主机
do
{
ip=192.168.122.$i
ping -c1 -W1 $ip &>/dev/null #-W表示超时1s就会停
if [ $? -eq 0 ];then
echo "$ip" |tee -a ip.txt
fi
}& #意思是把括号内的程序放到后台去执行
done
wait #也是命令,意思是等待当前shell中的他前面的所有的后台进程结束
echo "finish..."
批量创建用户
#!/usr/bin/bash
#v1.0
while :
do
read -p "Please enter prefix & passwod & num[tianyu 123 5]: " prefix pass num
printf "user information:
-------------------------
user prefix: $prefix
user password: $pass
user number: $num
-------------------------
"
read -p "Are you sure?[y/n]: " action
if [ "$action" != "y" ];then
break
fi
done
for i in `seq -w $num` #seq是等位补齐的
do
user=$prefix
id $user &>/dev/null
if [ $? -eq 0 ];then
echo "user $user already exists."
else
useradd $user
echo "$pass" |passwd --stdin $user &>/dev/null #进行创建用户并授予密码
if [ $? -eq 0 ];then
echo "$user is created."
fi
fi
done
#!/usr/bin/bash
#...
if [ $# -eq 0 ];then
echo "usage: `basename $0` file."
exit 1
fi
if [ ! -f $1 ];then
echo "error file!"
exit 21
fi
#希望for处理文件按回车分割,而不是空格或tab。需要重新定义分隔符。
#IFS内部字段分隔符,定义为回车。使用while循环就没这些
IFS=$'\n' #或是直接敲回车也可
for line in `cat $1` #line读取文件的每一行,相当于每一次把文件的一行赋值给line
do
if [ ${#line} -eq 0 ];then #判断是否存在空行
continue; #满足条件,后面的循环代码不再执行。是跳过本次循环,不是跳出循环
fi
user=`echo "$line" |awk '{print $1}'` #把user和pass筛选出来
pass=`echo "$line" |awk '{print $2}'` #然后下面就一样了
#id $user &>/dev/null
if [ $? -eq 0 ];then
echo "user $user already exists!"
else
useradd $user
echo "$pass" | passwd --stdin $user &>/dev/null
if [ $? -eq 0 ];then
echo "$user is created."
fi
fi
done
bash -vx create_user.sh user01.txt
查看脚本运行过程使用while语法,以下:
#! /usr/bin/bash
#v1.0
while read line #是处理文件时,最常用的,逐行处理。不同于上面的for
do
if [ ${#line} -eq 0 ]; then
continue; #跳出本次循环,继续下一轮的循环操作。break;exit
fi
user=`echo $line |awk'{print $1}'`
pass=`echo $line |awk'{print $2}'`
id $user &>/dev/null
if [ $? -eq 0 ]; then
echo "user $user already exists."
else
useradd $user
echo "$pass" |passwd --stdin $user &>/dev/null
if [ $? -eq 0 ]; then
echo "$user is created."
fi
fi
done <$1 #输入重定向
echo "ALL OK."
bash -vx create_user02.sh usertest.txt
调试脚本ssh非交互登录
- 通过使用expect程序,
yum -y install expect
- ssh登录的时候提示error,在 vim /root/.ssh/known_hosts 文件中删除之前登录过的主机的指纹信息,再次连接就可。相当于重新记录客户机的指纹信息fingerprint
- 在脚本中将每一步遇到的交互和进行的操作都写上,
锦囊 vim expect_ssh.sh
```bash!/usr/bin/expect
set ip [lindex $argv 0] #第1个未知参数,相当于shell的$1 set user [lindex $argv 1] #第2个未知参数.需要在调用文件时传参 set password centos set timeout 5 #设置超时时间5s spawn ssh $user@$ip expect { “yes/no” { send “yes\r”; exp_continue } “password”: { send “$password\r” }; }interact #意思是停在服务端那边
expect “#” #当出现#符号的时候 send “useradd xiaobai\r” send “pwd\r” send “exit\r” expect eof #这行意思是,expect做完事之后就结束就退出了
#
下面以拷贝文件为例
spawn scp -r /etc/hosts $user@$ip:/tmp expect { “yes/no” { send “yes\r”; exp_continue } “password”: { send “$password\r” }; } expect eof
<a name="5IoYs"></a>
### expect实现批量主机公钥推送
- `vim gepip_push.sh`
```bash
#! /usr/bin/bash
>ip.txt
password=centos
rpm -q expect &>/dev/null
if [ $? -ne 0 ]; then #检查下expect是否安装,-ne不等于
echo "installing expect..."
yum -y install expect &>/dev/null
fi
if [ ! -f ~/.ssh/id_rsa ]; then
echo "正在创建公钥,请稍候。。。"
ssh-keygen -P "" -f ~/.ssh/id_rsa
fi
for i in {2..254}
do
{
ip=192.168.122.$i
ping -c1 -W1 $ip &>/dev/null
if [ $? -eq 0 ]; then
echo "$ip" >> ip.txt
/usr/bin/expect <<-EOF #EOF包含的语句都用expect去解释
set timeout 10
spawn ssh-copy-id $ip #注意一定是tab键位,vim用set list可查看使用的什么键位
expect {
"yes/no" { send "yes\r"; exp_continue }
"password:" { send "$password\r" }
}
expect eof
EOF
fi
}& #在后台执行的
done
wait
echo "already finish~"
批量主机密码修改
vim passwd_chg.sh
#! /usr/bin/bash
#v1.0 by 2200-4-20
read -p "Please enter a new password: " pass
for ip in $(cat ip.txt)
do
{
ping -c1 -W1 $ip &>/dev/null
if [ $? -eq 0 ]; then
ssh $ip 'echo $pass |passwd --stdin root'
if [ $? -eq 0 ]; then
echo "$ip" >>ok_`date +%F`.txt
else
echo "$ip" >>fail_`date +%F`.txt
fi
else
echo "$ip" >>fail_`date +%F`.txt
fi
}& #在后台执行的
done
wait
echo "already finish~"
批量远程主机ssh配置
vim modify_sshcon.sh
#! /usr/bin/bash
$v1.0 by xiaobai
for ip in `cat ip.txt`
do
{
ping -c1 -W1 $ip >/dev/null
if [ $? -eq 0 ]; then
ssh $ip "sed -ri '/^#UseDNS/cUseDNS no' /etc/ssh/sshd_config" #其实往文件中追加内容也可
ssh $ip "sed -ri '/^#GSSAPIAuthentication/cGSSAPIAuthentication no' /etc/ssh/sshd_config"
#c 含义是替换,上面两行是避免影响ssh连接远程的速度
ssh $ip "systemctl stop firewalld;systemctl disable firewalld"
ssh $ip "sed -ri '/^#SELINUX=/cSELINUX=disabled' /etc/sysconfig/selinux"
ssh $ip "setenforce 0"
fi
}& #以上命令都是shell并发执行的
done
wait
echo "all ok~"
bash -n modify_sshcon.sh
检查下语法chmod a+x modify_sshcon.sh
- 通过在shell中执行脚本命令来复查上面的运行结果
for ip in cat ip.txt
do
ssh $ip “grep ‘DNS’ /etc/ssh/sshd_config”
done
for_while_unit
#! /usr/bin/bash
for i in {1..100}
do
let sum=$sum+$i #let运算
#let sum+=$i
done
echo "sum: $sum"
#########################################
i=1
while [ $i -le 100 ]
do
let sum=$sum+$i
let i++
done
echo "sum: $sum"
#########################################
i=1
until [ $i -gt 100 ]
do
let sum+=$i
let i++
done
echo "sum: $sum"
命名管道实现并发控制(数量)
#! /usr/bin/bash
jc=2 #进程数量
tmp_fifofile=/tmp/$$.fifo #以当前pid命名的
mkfifio $tmp_fifofile
exec 8<> $tmp_fifofile
rm $tmp_fifofile #删掉文件了,但不影响上面的描述符 8
for i in `seq $jc`
do
echo >&8 #相当于操作文件描述符 8,从fd=8的文件中拿。循环的前2个是可以读取的;结束循环后会还回去
done
for i in {1..254}
do
read -u 8 #u后面的是文件描述符fd
{
ip=192.168.0.$i
ping -c1 -W1 $ip &>/dev/null
if [ $? -eq 0 ]; then
echo "$ip is up~"
fi
echo >&8 #有借有还,借的就是上面read过来的fd。公园划船一个意思
}&
done
wait
exec 8>& #释放fd,因为上面有打开,就有释放
echo "all finish..."
- exec给文件一个描述符;exec关闭文件(释放文件句柄);如果FD未被释放,即使删除了原文件一样可以copy出来
- 管道也是文件。上面例子用的是匿名管道
shell数组变量
- 普通数组:只能使用整数作为数组索引(python:列表)
- books={linux shell awk openstack docker}
- echo ${books[3]}
- 关联数组:可以使用字符串作为数组索引(python:字典)
- declare -A info
- info=([name]=xiaobai [sex]=male [age]=20 [height]=180)
- echo ${info[age]}
- 获得数组的索引,
echo ${!info[@]}
,通过索引进行遍历