Shell简介

Shell 是一个C语言编写的程序,他是用户使用Linux的桥梁。Shell 这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务。
Shell 既是一种命令语言,又是一种程序设计语言。

  • 种类
    • Bourne Shell(/usr/bin/sh 或 bin/sh)
    • Bourne Again Shell(/bin/bash)
    • C Shell(/usr/bin/csh)
    • K Shell(/usr/bin/ksh)
    • Shell for root(/sbin/sh)

程序编程风格:

  • 过程式:以指令为中心,数据服务于命令
  • 对象式:以数据为中心,命令服务于数据

Shell 是一种过程式编程,过程式编程的特征是:顺序执行、循环执行、选择执行

编程语言分类:

  • 编译型语言
  • 解释型语言

Shell是一种解释型语言。
image.png
运行脚本的方法有两种:

  1. 给予文件执行权限,通过具体的文件路径指定文件执行:chmod +x test.sh,./test.sh
  2. 直接运行解释器,将脚本作为解释器的参数执行:bash test.sh
  • bash 退出状态码:
    • 范围是0-255
    • 脚本中一旦遇到 exit 命令,脚本会立即终止,终止退出状态取决于 exit 后面的数字
    • 如果没有给脚本指定退出状态码,整个脚本的退出状态码取决于脚本中执行的最后一条命令的状态

变量

变量命名

  • 规则
    • 只能使用英文字母,数字和下划线,首字母不能是数字
    • 中间不能有空格,可以使用下划线
    • 不能使用标点符号
    • 不能使用 bash 中的关键字(可用 help 命令查看bash保留的关键字) ```bash 有效命名: RUNOOB LD_LIBRARY_PATH _var var2

无效命名: ?var=123 user*name=runoob

  1. 如果要将命令执行的结果赋予变量的话可以使用反引号` `或者是 $() ,如:
  2. ```bash
  3. for file in `ls /etc`
  4. for file in $(ls /etc)

以上语句是将命令ls /etc 的执行结果循环出来。

使用变量

使用一个定义过的变量,只要在变量前面加上美元符号 $ 即可,如:

  1. your_name="qinjx"
  2. echo $your_name
  3. echo ${your_name}

变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界,比如下面这种情况:

  1. for skill in Ada Coffe Action Java; do
  2. echo "I am good at ${skill}Script"
  3. done

如果不给skill变量加花括号,写成echo “I am good at $skillScript”,解释器就会把$skillScript当成一个变量(其值为空),代码执行结果就不是我们期望的样子了。

已定义的变量,可以被重新定义,如:

  1. your_name="tom"
  2. echo $your_name
  3. your_name="alibaba"
  4. echo $your_name

注意第二次复制时不能写成$yourname="alibaba",只有在使用变量的时候才使用 $ 符号。

只读变量

使用 readonly 可以把变量定义为只读变量,只读变量的值不能被改变。
下面的例子尝试更改只读变量,结果报错:

  1. #!/bin/bash
  2. myUrl="https://www.google.com"
  3. readonly myUrl
  4. myUrl="https://www.runoob.com"
  5. # 运行脚本,结果如下:
  6. /bin/sh: NAME: This variable is read only.

删除变量

使用 unset 命令可以删除变量,语法:

  1. unset variable_name

变量被删除后不能再次使用。unset 命令不能删除只读变量。

  1. #!/bin/sh
  2. myUrl="https://www.runoob.com"
  3. unset myUrl
  4. echo $myUrl

以上脚本执行后没有任何输出。

变量类型

运行shell时,会同时存在三种变量:

  • 局部变量,局部变量是在当前命令或脚本中定义的,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量(包括子shell进程)。
    • 变量赋值:name = “value”
  • 环境变量,所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。
    • 变量声明1:export name = “value”
    • 变量声明2:declare -x name = “value”
    • bash中有许多内建的变量环境:SHELL,PATH等等
  • shell变量,shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行
  • 特殊变量:? 0 @ # ```bash $1,$2,…:对应调用第1,第2等参数 $0:命令本身 $:传递给脚本的所有参数(把素有参数当作整体) $@:传递给脚本的所有参数 $#:传递给脚本的参数的个数

案例1:创建myecho.sh

!/bin/bash

echo “命令本身是 $0” echo “第一个参数是 $1” echo “第二个参数是 $2” echo “一共有 $# 个参数” echo “所有参数是 $@”

执行:./myecho.sh 1 2 3 4 5 [root@server1 ~]# bash myecho.sh 1 2 3 4 5 命令本身是 myecho.sh 第一个参数是 1 第二个参数是 2 一共有 5 个参数 所有参数是 1 2 3 4 5

===================================================

案例2:判断所给文件的行数 [root@server1 ~]# wc -l /etc/passwd 21 /etc/passwd [root@server1 ~]# wc -l /etc/passwd | cut -d ‘ ‘ -f1 21

创建linecount.sh

!/bin/bash

linecount=wc -l $1 | cut -d ' ' -f1 echo “$1 文件有 ${linecount}行”

执行判断/etc/passwd有多少行 [root@server1 ~]# bash linecount.sh /etc/passwd /etc/passwd 文件有 21行

  1. PS:这里要区分单引号和双引号。单引号是弱引用,单引号中就是字符串,没有其他意义;双引号是强引用,双引号中的$变量会取到变量的值。
  2. <a name="zHwaT"></a>
  3. # 数组
  4. - 数组赋值
  5. ```bash
  6. 语法格式:array_name=(value1 ... valuen)
  7. 示例:
  8. my_array=(A B "C" D)
  9. array_name[0]=value0
  10. array_name[1]=value1
  11. array_name[2]=value2
  • 读取数组

    1. 读取数组:${array_name[index]}
    2. 获取数组中的所有元素:
    3. my_array[0]=A
    4. my_array[1]=B
    5. my_array[2]=C
    6. my_array[3]=D
    7. echo "数组的元素为: ${my_array[*]}"
    8. echo "数组的元素为: ${my_array[@]}"
  • 获取数组的长度

    1. 获取数组的长度:
    2. my_array[0]=A
    3. my_array[1]=B
    4. my_array[2]=C
    5. my_array[3]=D
    6. echo "数组元素个数为: ${#my_array[*]}"
    7. echo "数组元素个数为: ${#my_array[@]}"

    算术运算

  • 运算符 ```bash

      • / % * … 增强赋值: +=,-=,=,/=,%= 乘法符号有些场景中需要转义 : *\ bash有内建随机数生成器:$RANDOM ```
  • 完成算术运算
    1. (1) let var(变量名)=算术表达式
    2. (2) var=$[算术表达式]
    3. (3) var=$((算术表达式))
    4. (4) var=$(expr argl arg2 arg3 …)

    练习1:计算/etc/passwd文件中第10个用户和第20个用户的ID之和

    思路:
    首先要知道/etc/passwd文件中用户ID在哪
    image.png
    可以发现第三列是用户ID,所以现在的任务是将文件的第三列过滤出来:
    image.png
    最后要把第10个和第20个用户ID提取出来
    image.png ```bash [root@server1 ~]# vim id_sum.sh

    !/bin/bash

    userid1=$(awk -F”:” ‘{print $3}’ /etc/passwd | sed -n ‘10p’) userid2=$(awk -F”:” ‘{print $3}’ /etc/passwd | sed -n ‘20p’) userid_sum=$[$userid1+$userid2] echo $userid_sum

[root@server1 ~]# bash id_sum.sh 1011

  1. <a name="iUMsC"></a>
  2. ## 练习2:传递两个文件路径参数给脚本,计算这两个文件之中所有空白行之和
  3. 思路:首先要提取出文件中的空白行:grep '^$' [文件名]。<br />然后计数有多少行<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/614741/1619243795034-2ee1bd36-1ba5-4c87-8a4c-b3014b6d9451.png#clientId=uf7552f4d-161e-4&from=paste&height=72&id=uaec6a574&margin=%5Bobject%20Object%5D&name=image.png&originHeight=74&originWidth=541&originalType=binary&ratio=1&size=4833&status=done&style=none&taskId=uf4665882-24ce-4266-8a5d-b77ffc71956&width=528.5)
  4. ```bash
  5. [root@server1 ~]# vim blankline_count.sh
  6. #!/bin/bash
  7. blank1=`grep '^$' $1 | wc -l`
  8. blank2=`grep '^$' $2 | wc -l`
  9. let count=$blank1+$blank2
  10. echo "$1 和 $2 两个文件的空白行之和为 $count"
  11. [root@server1 ~]# bash blankline_count.sh /root/anaconda-ks.cfg /root/test1
  12. /root/anaconda-ks.cfg 和 /root/test1 两个文件的空白行之和为 8

练习3:统计/etc/,/var/,/usr/目录下有多少一级子目录和文件

  1. [root@server1 ~]# vim count_file.sh
  2. #!/bin/bash
  3. sum_etc=$(find /etc/ | wc -l)
  4. sum_var=$(find /var/ | wc -l)
  5. sum_usr=$(find /usr/ | wc -l)
  6. sum_file=$[$sum_etc+$sum_var+$sum_usr]
  7. echo "/etc/、/var/、/usr/下一级子目录和文件有${sum_file}个"
  8. [root@server1 ~]# bash count_file.sh
  9. /etc/、/var/、/usr/下一级子目录和文件有37284

条件测试

案例

采集系统相关信息

  1. #!/bin/bash
  2. pid_sum=`ps -ef | wc -l`
  3. mem_use_per=`free | awk '/Mem/{print$3/$2*100 '%'}'`
  4. let disk_volume=`df|awk '{disk_volume+=$2} END {print disk_volume}'`
  5. let disk_free=`df|awk '{disk_free+=$4} END {print disk_free}'`
  6. disk_use_per=`awk 'BEGIN{printf"%.2f\n",((1-'$disk_free/$disk_volue')*100)}'`
  7. cpu_cores=`cat /proc/cpuinfo | awk -F: '/core/{cores+=$2} END {print cores}'`
  8. echo "系统进程总数:$pid_sum"
  9. echo "内存使用率:$mem_use_per"
  10. echo "磁盘总空间:$disk_volume"
  11. echo "磁盘剩余空间:$disk_free"
  12. echo "磁盘占用率:${disk_use_per}%"
  13. echo "cpu总核数:$cpu_cores"

详解:

  • 进程总数,通过ps -ef命令查看所有进程,在通过wc -l统计输出了多少行,即有多少进程。
  • 内存使用率,通过free命令可以查看实体内存和交换内存,再通过awk工具的正则表达式/Mem/先找到Mem这条记录,然后执行 打印第3列除第2列乘100 加上一个百分号。
  • 磁盘总空间,通过df命令查看磁盘使用情况,再通过awk工具第2列的和并打印出来,然后通过let命令赋值给disk_volume(需注意的是let命令只能整数赋值)。磁盘剩余空间类似。
  • 磁盘占用率,通过磁盘剩余空间和磁盘总空间可以计算出,(测试时计算发现输出为0,因为除法保留整数,实际值是0.9左右),通过awk保留两位小数输出。
  • cpu总核数,查看文件/prc/cpuinfo,通过awk工具,以”:”为分隔符,查找core这条记录并执行累加第2列,执行完后打印结果

检测httpd进程数,确保httpd处于稳定运行状态

  1. #!/bin/bash
  2. function check_httpd_process_number(){
  3. httpd_process_num=`ps -ef|grep httpd|wc -l`
  4. if [ $httpd_process_num -ge 50 ];then
  5. systemctl restatrt httpd &> /dev/null
  6. # 如果重启失败,尝试重启apache服务5次
  7. if [$? -ne 0];then
  8. num_httpd_restart=0
  9. while true;do
  10. let num_httpd_restart++
  11. systemctl restart httpd &> /dev/null
  12. [$? -eq 0] && break
  13. [num_httpd_restart -eq 5] && break
  14. done
  15. else
  16. echo "重启一次就成功了" && return 0
  17. fi
  18. # 判断apache重启是否成功了
  19. systemctl status httpd &> /dev/null
  20. [$? -ne 0] && echo"apache启动失败,向管理员发送告警" && return 1
  21. sleep 60
  22. # 判断apache重启后是否还是异常
  23. if [$httpd_process_num -ge 50];then
  24. echo "apache重启后进程数异常,向管理员发送告警" && return 1
  25. else
  26. return 0
  27. fi
  28. else
  29. echo "httpd进程数为${httpd_process_num},小于50"
  30. sleep 10
  31. return 0
  32. fi
  33. }
  34. # 每10秒执行一次函数,检测httpd进程数是否正常,return 1推出检测
  35. while true;do
  36. check_httpd_process_number
  37. [$? -eq 1] && exit
  38. done

统计两个目录下相同文件以及不同文件,通过md5散列值的方式进行比较

  1. #!/bin/bash
  2. # server1:192.168.80.10:/test/ server2:192.168.80.20:/root/demo/
  3. # 输出相同的文件、各自不同的文件,md5仅校验文件内容,不比对文件名、修改时间等属性
  4. #server1_ip=192.168.80.10
  5. dir1=/test/
  6. echo "==========server1 md5=========="
  7. index1=0
  8. for i in `ls $dir1`;do
  9. md5=`md5sum $dir1$i | awk '{print $1}'`
  10. arr1[$index]=$md5:$i
  11. echo ${arr1[$index]}
  12. let index1++
  13. done
  14. #server2_ip=192.168.80.20
  15. dir2=/root/demo/
  16. echo "==========server2 md5=========="
  17. index2=0
  18. for i in `ls $dir1`;do
  19. md5=`md5sum $dir2$i awk '{print $1}'`
  20. arr2[$index]=$md5:$i
  21. echo ${arr2[$index]}
  22. let index2++
  23. done
  24. echo "==========server1上的文件=========="
  25. for i in ${arr1[@]};do
  26. server1_md5=`echo $i | awk -F: '{print $1}'`
  27. server1_filename=`echo $i | awk -F: '{print $2}'`
  28. tmp_same_num=0
  29. for j in ${arr2[@]};do
  30. server2_md5=`echo $i | awk -F: '{print $1}'`
  31. server2_filename=`echo $i | awk -F: '{print $2}'`
  32. if [server1_md5 == server2_md5];then
  33. let tmp_same_num++
  34. echo "相同文件:$dir1${server1_filename}" && break
  35. fi
  36. done
  37. if [tmp_same_num -eq 0];then
  38. echo "server1上的独立文件:$dir1${server1_filename}"
  39. fi
  40. done