基础

  1. 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
    1. 系统级,/etc/profile /etc/bashrc
    • 用户级,~/.bash_profile ~/.bashrc,当退出时会执行,~/.bash_logout ~/.bash_history
    • su - xiaobai,也就是login shell 时,执行以上四个文件
    • su xiaobai,也就是nologin shell时,执行 /etc/bashrc ~/.bashrc
    1. bash shell特点
    • 命令历史记忆功能:!number,!string,!$上一条命令最后一个参数,!!上一个命令,^R搜索历史命令
    • data > test.txt data |tee test.txt tee不会节流,将结果打印出来
    1. 通配符(元字符)
    • [] 表示只匹配满足其中条件的一个
    • ()在子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
    1. 脚本中使用变量
    • if [ $? -eq 0];then 如果上条语句返回值为0(条件测试),那么执行…
    • $0脚本名,$*所有的参数,$@所有的参数,$#参数的个数,$$当前进程的PID,$!上一个后台进程的PID,$?上一个命令的返回值0;表示成功
    • ${#abc}表示变量的长度,${#abc[@]}表示数组的个数
    • read可以同时输入多个变量,以空格分割来截取
      ```bash

      !/bin/bash

      read -p “输入姓名,性别,年龄[e.g. zhangsan m 20]:” name sex age echo “你输入的姓名是:$name,性别:$sex,年龄:$age”

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

  1. - **""弱引用;''强引用,不管是不是变量**
  2. - disk_free= `df -Ph |grep '/$' |awk '{print $4}'`
  3. - disk_free2=$(df -Ph |grep '/$' |awk '{print $4}')
  4. 6. 数字运算, `echo $[2**10]` ,或用expr,或是 `echo $((2**3))` ,或是 `let num=1+2;echo $num`
  5. 6. 变量内容的删除和替换
  6. - ${url#*si} 删除变量url,从开始到si,取si后面的作为新的变量内容
  7. - ${url#*.} 从前往后,最短匹配;${url##*.} 从前往后,最长匹配贪婪匹配。一直到最后一个.的内容都被匹配上
  8. - ${url%.*};${url%%.*} 从后往前匹配
  9. 1. 索引及切片,${url} 字符串是从0开始
  10. - ${url:5:5};${url:5}<br />
  11. ```bash
  12. #!/bin/bash
  13. mem_used=`free -m | grep '^Mem:' |awk '{print $3}'`
  14. mem_total=`free -m | grep '^Mem:' |awk '{print $2}'`
  15. mem_present=$((mem_used*100/mem_total))
  16. war_file=/tmp/mem_war.txt
  17. rm -rf $war_file
  18. if (($mem_percent>80));then
  19. #if [ $mem_percent -ge 80 ];then可以改成ge大于等于
  20. echo "`date +%F-%H` memory:${mem_precent}%" > $war_file
  21. fi
  22. if [ -f $war_file ];then
  23. mail -s "mem warning..." alice < $war_file
  24. rm -rf $war_file
  25. fi
  1. 字符串最好引号引起来,`[ “$USER” = “root” ];echo $?
  1. #...........
  2. #...........
  3. read -p "Please input number: " num
  4. while true
  5. do
  6. if [[ !"$num" =~ ^[0-9]+$ || "$num" =~ ^0+$]];then #避免输入以0开头或多个0的
  7. break #跳出这个循环
  8. else
  9. read -p "不是数字,请重新输入数值:" num
  10. fi
  11. done
  12. #同样,下面的判断也可以做
  13. read -p "Please input prefix: " prefix
  14. if [ -z "$prefix" ];then #字符串长度不能为0,为0的话就退出
  15. echo "error prefix"
  16. exit
  17. fi
  18. for i in `seq $num`
  19. do
  20. user=$prefix$i
  21. useradd $user
  22. echo "123" |passwd --stdin $user &>/dev/null
  23. if [ $? -eq 0 ];then
  24. echo "$user is created."
  25. fi
  26. done
  • ()子shell中执行
    • (())数值比较,运算C语言
    • $()命令替换
    • $(())整数运算
  • []条件测试
    • [[]]条件测试,支持正则 =~
    • $[]整数运算
  • {}
    • ${}
  1. until和while语法结构类似,不过两者含义相反。都适合写循环次数不固定的语句
    • until当条件测试成立(条件测试为假),执行循环体

until [ 1 -eq 2 ]
do
date;sleep 1
done

  • 也可以在循环外面写执行命令,当满足测试条件时,就不再循环,而执行外面的命令
    • ^AA^

      实例

      安装apache

  • vim install_apache.sh

    1. #!/bin/bash
    2. #install apache in centos7
    3. #v1.0 by xxx 2020-xx-xx
    4. gateway=192.168.10.1
    5. if ping -c1 www.baidu.com &>/dev/null;then
    6. yum install -y ....
    7. sed -ri '/^SELINUX=/cSELINUX=disabled' /etc/selinux/config
    8. curl http://127.0.0.1 &>/dev/null
    9. if [ $? -eq 0 ];then
    10. echo "Apacha ok..."
    11. fi
    12. elif ping -c1 $gateway &>/dev/null;then
    13. echo "网关正常,请检查dns或其他..."
    14. exit
    15. else
    16. echo "check ip address!"
    17. fi
    18. fi
    19. ###
    20. read -p "确认开始安装KVM[y]" kvm_install
    21. if [ !"${kvm_install}"="y"];then
    22. echo -e "$red_col输入不正确!$reset_col"
    23. exit
    24. fi
  • 删除用户

    1. read -p "Please input a username: " user
    2. id $user &>/dev/null
    3. if [ $? -ne 0 ];then
    4. echo "no such user: $user"
    5. exit 1 #第一种错,返回1
    6. fi
    7. read -p "Are you sure[y/n]: " action
    8. #if [ "$action" != "y" -a "$action" != "Y" -a "$action" != "yes" -a "$action" != "YES" ];then
    9. # echo "good!"
    10. # exit
    11. #fi
    12. case "$action" in
    13. y|Y|yes|YES)
    14. userdel -r $user
    15. echo "$user is deleted!"
    16. ;;
    17. *)
    18. echo "error"
    19. esac
    20. #userdel -r $user && echo "$user is deleted!"

    简单的JumpServer

  • 跳板机属于内控堡垒机范畴,是一种用于单点登陆的主机应用系统。

  • 使用alice用户登录到跳板机,然后脚本就运行了,所以脚本放在用户的家目录, vim jumpserver.sh

    • 业务服务器不允许直接连接,通过允许从跳板机连接
    • 业务服务器不允许root用户直接登录
      1. #!/bin/bash
      2. #jumpserver
      3. trap "" HUP INT OUIT TSTP #捕捉键盘信号,然后什么操作也没有
      4. web1=192.168.10.1
      5. web2=192.168.10.2
      6. mysql1=192.168.10.3
      7. clear
      8. while :
      9. do
      10. cat <<-EOF
      11. +----------------------------------------------------+
      12. |JumpServer |
      13. |1. web1 |
      14. |2. web2 |
      15. |3. mysql1 |
      16. +----------------------------------------------------+
      17. EOF
      18. # read -p "input number: " num
      19. echo -en "\e[1;32minput number: \e[0m" #n表示不要换行
      20. read num
      21. case "$num" in
      22. 1)
      23. ssh alice@$web1
      24. ;;
      25. 2)
      26. ssh alice@$web2
      27. ;;
      28. 3)
      29. ssh alice@$mysql1
      30. ;;
      31. "")
      32. ;;
      33. *)
      34. echo "error"
      35. esac
      36. done
  • 将执行脚本的语句,加入到alice的bash_profile里,就可以登录时自动运行该脚本, vim .bash_profile

  • 在ssh连接时,使用秘钥的方式,可以不需用户输入密码就可登录到其他Server
    • ssh-copy-id root@192.168.10.1,相当于以root身份登录到192.168.10.1去
    • 当然,脚本里也要改

      实现系统工具箱

      1. #!/bin/bash
      2. #system manage
      3. #v1.0
      4. menu() {
      5. cat <<-EOF
      6. h. help
      7. f. disk partition
      8. d. filesystem mount
      9. m. memory
      10. u. system load
      11. q. exit
      12. EOF
      13. } #函数,一个代码块,为了实现重复调用
      14. menu
      15. while : #表示为真,true也可以,循环。但exit同样可以退出
      16. do
      17. read -p "Please input[h for help]: " action
      18. case "$action" in
      19. h) clear;menu;;
      20. f) fdisk -l;;
      21. d) df -Th;;
      22. m) free -m;;
      23. u) uptime;;
      24. q)
      25. #exit #退出程序更直接
      26. break #退出循环
      27. ;;
      28. "") ;;
      29. *) echo "error"
      30. esac
      31. done
      32. echo "Finish..."
  1. 实现多版本php安装。
  • vim main_install.sh 通过一个主要的脚本来调用其他的安装脚本。就类似于ansible的剧本文件,main.yml来调用其他的剧本文件

    批量主机ping探测

    1. #!/bin/bash
    2. >ip.txt
    3. for i in {2..254} #`seq 2 254` 产生一个序列
    4. #for i in `cat ip.txt` ping的对象来自特定的一部分主机
    5. do
    6. {
    7. ip=192.168.122.$i
    8. ping -c1 -W1 $ip &>/dev/null #-W表示超时1s就会停
    9. if [ $? -eq 0 ];then
    10. echo "$ip" |tee -a ip.txt
    11. fi
    12. }& #意思是把括号内的程序放到后台去执行
    13. done
    14. wait #也是命令,意思是等待当前shell中的他前面的所有的后台进程结束
    15. echo "finish..."

    批量创建用户

    1. #!/usr/bin/bash
    2. #v1.0
    3. while :
    4. do
    5. read -p "Please enter prefix & passwod & num[tianyu 123 5]: " prefix pass num
    6. printf "user information:
    7. -------------------------
    8. user prefix: $prefix
    9. user password: $pass
    10. user number: $num
    11. -------------------------
    12. "
    13. read -p "Are you sure?[y/n]: " action
    14. if [ "$action" != "y" ];then
    15. break
    16. fi
    17. done
    18. for i in `seq -w $num` #seq是等位补齐的
    19. do
    20. user=$prefix
    21. id $user &>/dev/null
    22. if [ $? -eq 0 ];then
    23. echo "user $user already exists."
    24. else
    25. useradd $user
    26. echo "$pass" |passwd --stdin $user &>/dev/null #进行创建用户并授予密码
    27. if [ $? -eq 0 ];then
    28. echo "$user is created."
    29. fi
    30. fi
    31. done
  1. #!/usr/bin/bash
  2. #...
  3. if [ $# -eq 0 ];then
  4. echo "usage: `basename $0` file."
  5. exit 1
  6. fi
  7. if [ ! -f $1 ];then
  8. echo "error file!"
  9. exit 21
  10. fi
  11. #希望for处理文件按回车分割,而不是空格或tab。需要重新定义分隔符。
  12. #IFS内部字段分隔符,定义为回车。使用while循环就没这些
  13. IFS=$'\n' #或是直接敲回车也可
  14. for line in `cat $1` #line读取文件的每一行,相当于每一次把文件的一行赋值给line
  15. do
  16. if [ ${#line} -eq 0 ];then #判断是否存在空行
  17. continue; #满足条件,后面的循环代码不再执行。是跳过本次循环,不是跳出循环
  18. fi
  19. user=`echo "$line" |awk '{print $1}'` #把user和pass筛选出来
  20. pass=`echo "$line" |awk '{print $2}'` #然后下面就一样了
  21. #id $user &>/dev/null
  22. if [ $? -eq 0 ];then
  23. echo "user $user already exists!"
  24. else
  25. useradd $user
  26. echo "$pass" | passwd --stdin $user &>/dev/null
  27. if [ $? -eq 0 ];then
  28. echo "$user is created."
  29. fi
  30. fi
  31. done
  • bash -vx create_user.sh user01.txt 查看脚本运行过程
  • 使用while语法,以下:

    1. #! /usr/bin/bash
    2. #v1.0
    3. while read line #是处理文件时,最常用的,逐行处理。不同于上面的for
    4. do
    5. if [ ${#line} -eq 0 ]; then
    6. continue; #跳出本次循环,继续下一轮的循环操作。break;exit
    7. fi
    8. user=`echo $line |awk'{print $1}'`
    9. pass=`echo $line |awk'{print $2}'`
    10. id $user &>/dev/null
    11. if [ $? -eq 0 ]; then
    12. echo "user $user already exists."
    13. else
    14. useradd $user
    15. echo "$pass" |passwd --stdin $user &>/dev/null
    16. if [ $? -eq 0 ]; then
    17. echo "$user is created."
    18. fi
    19. fi
    20. done <$1 #输入重定向
    21. echo "ALL OK."
  • bash -vx create_user02.sh usertest.txt 调试脚本

    ssh非交互登录

  1. 通过使用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

  1. <a name="5IoYs"></a>
  2. ### expect实现批量主机公钥推送
  3. - `vim gepip_push.sh`
  4. ```bash
  5. #! /usr/bin/bash
  6. >ip.txt
  7. password=centos
  8. rpm -q expect &>/dev/null
  9. if [ $? -ne 0 ]; then #检查下expect是否安装,-ne不等于
  10. echo "installing expect..."
  11. yum -y install expect &>/dev/null
  12. fi
  13. if [ ! -f ~/.ssh/id_rsa ]; then
  14. echo "正在创建公钥,请稍候。。。"
  15. ssh-keygen -P "" -f ~/.ssh/id_rsa
  16. fi
  17. for i in {2..254}
  18. do
  19. {
  20. ip=192.168.122.$i
  21. ping -c1 -W1 $ip &>/dev/null
  22. if [ $? -eq 0 ]; then
  23. echo "$ip" >> ip.txt
  24. /usr/bin/expect <<-EOF #EOF包含的语句都用expect去解释
  25. set timeout 10
  26. spawn ssh-copy-id $ip #注意一定是tab键位,vim用set list可查看使用的什么键位
  27. expect {
  28. "yes/no" { send "yes\r"; exp_continue }
  29. "password:" { send "$password\r" }
  30. }
  31. expect eof
  32. EOF
  33. fi
  34. }& #在后台执行的
  35. done
  36. wait
  37. echo "already finish~"

批量主机密码修改

  • vim passwd_chg.sh

    1. #! /usr/bin/bash
    2. #v1.0 by 2200-4-20
    3. read -p "Please enter a new password: " pass
    4. for ip in $(cat ip.txt)
    5. do
    6. {
    7. ping -c1 -W1 $ip &>/dev/null
    8. if [ $? -eq 0 ]; then
    9. ssh $ip 'echo $pass |passwd --stdin root'
    10. if [ $? -eq 0 ]; then
    11. echo "$ip" >>ok_`date +%F`.txt
    12. else
    13. echo "$ip" >>fail_`date +%F`.txt
    14. fi
    15. else
    16. echo "$ip" >>fail_`date +%F`.txt
    17. fi
    18. }& #在后台执行的
    19. done
    20. wait
    21. echo "already finish~"

    批量远程主机ssh配置

  • vim modify_sshcon.sh

    1. #! /usr/bin/bash
    2. $v1.0 by xiaobai
    3. for ip in `cat ip.txt`
    4. do
    5. {
    6. ping -c1 -W1 $ip >/dev/null
    7. if [ $? -eq 0 ]; then
    8. ssh $ip "sed -ri '/^#UseDNS/cUseDNS no' /etc/ssh/sshd_config" #其实往文件中追加内容也可
    9. ssh $ip "sed -ri '/^#GSSAPIAuthentication/cGSSAPIAuthentication no' /etc/ssh/sshd_config"
    10. #c 含义是替换,上面两行是避免影响ssh连接远程的速度
    11. ssh $ip "systemctl stop firewalld;systemctl disable firewalld"
    12. ssh $ip "sed -ri '/^#SELINUX=/cSELINUX=disabled' /etc/sysconfig/selinux"
    13. ssh $ip "setenforce 0"
    14. fi
    15. }& #以上命令都是shell并发执行的
    16. done
    17. wait
    18. 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

  1. #! /usr/bin/bash
  2. for i in {1..100}
  3. do
  4. let sum=$sum+$i #let运算
  5. #let sum+=$i
  6. done
  7. echo "sum: $sum"
  8. #########################################
  9. i=1
  10. while [ $i -le 100 ]
  11. do
  12. let sum=$sum+$i
  13. let i++
  14. done
  15. echo "sum: $sum"
  16. #########################################
  17. i=1
  18. until [ $i -gt 100 ]
  19. do
  20. let sum+=$i
  21. let i++
  22. done
  23. echo "sum: $sum"

命名管道实现并发控制(数量)

  1. #! /usr/bin/bash
  2. jc=2 #进程数量
  3. tmp_fifofile=/tmp/$$.fifo #以当前pid命名的
  4. mkfifio $tmp_fifofile
  5. exec 8<> $tmp_fifofile
  6. rm $tmp_fifofile #删掉文件了,但不影响上面的描述符 8
  7. for i in `seq $jc`
  8. do
  9. echo >&8 #相当于操作文件描述符 8,从fd=8的文件中拿。循环的前2个是可以读取的;结束循环后会还回去
  10. done
  11. for i in {1..254}
  12. do
  13. read -u 8 #u后面的是文件描述符fd
  14. {
  15. ip=192.168.0.$i
  16. ping -c1 -W1 $ip &>/dev/null
  17. if [ $? -eq 0 ]; then
  18. echo "$ip is up~"
  19. fi
  20. echo >&8 #有借有还,借的就是上面read过来的fd。公园划船一个意思
  21. }&
  22. done
  23. wait
  24. exec 8>& #释放fd,因为上面有打开,就有释放
  25. echo "all finish..."
  • exec给文件一个描述符;exec关闭文件(释放文件句柄);如果FD未被释放,即使删除了原文件一样可以copy出来
  • 管道也是文件。上面例子用的是匿名管道

shell数组变量

  1. 普通数组:只能使用整数作为数组索引(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[@]} ,通过索引进行遍历