shell编程
shell编程能做什么
1)安装操作系统(手动安全ISO) 自动化安装(kickstart cobbler)shell脚本
2)初始化操作系统 优化(SSH优化、关闭SElinux、优化防火墙、NTP时间同步、更改默认YUM、源字符集、安装常用的软件 lrzsz net-tools tree.. wget、隐藏版本信息、加大文件描述符、内核优化...)初始化写入脚本 自动化执行
3) 安装服务(Nginx PHP MySQL MariaDB NFS sersync REDIS keepalived docker....zabbix)使用shell脚本自动选择安装
4) 启动----停止(centos6.x: /etc/init.d/server start;centos7.x:systemctl start server) 系统默认的启动方式 shell脚本
5) 监控 zabbix、cacti、nagios、ELK、公司研发的监控平台;监控的值(shell脚本统计)
6) 日志统计 三剑客 日志切割(脚本+定时任务)
初识Shell
什么是shell
pwd ls 都是通过bash解释的、Shell是一个命令解释器,作用是解释执行用户输入的命令及程序
交互式模式就是shell等待你的输入,并且执行你提交的命令。这种模式被称作交互式是因为shell与用户进行交互。这种模式也是大多数用户非常熟悉的:登录、执行一些命令、签退。当你签退后,shell也终止了。shell也可以运行在另外一种模式:非交互式模式。在这种模式下,shell不与你进行交互,而是读取存放在文件中的命令,并且执行它们。当它读到文件的结尾,shell也就终止了。
什么是shell脚本
命令的大礼包 把可执行命令放到文件中 称为shell脚本(判断语句 循环语句 数组....)
编译语言: 把代码编程成二进制语言 机器可识别 一次编译 终身运行 速度快
脚本语言: 每次执行脚本 一行行的解释
shell的书写规范
1) shell脚本存放统一的目录
2) 脚本名字的结尾使用.sh
3) 脚本的开头 必须有解释器 #!/bin/bash
4) 脚本内有作者信息 脚本信息
5) 每段代码块有注释(尽量使用英文)
6) 标点符号 (语法尽量一次性书写完毕)
7) 成对的符号 一次性书写完毕
第一个shell脚本
[root@test scripts]# vim hello.sh #!/bin/bash echo "Hello Word!" [root@test scripts]# #执行脚本的三种方法 #方法1 直接使用解释器执行 [root@test scripts]# bash hello.sh Hello Word! [root@test scripts]# #方法2 全路径方式执行 需要执行权限 [root@test scripts]# chmod +x hello.sh [root@test scripts]# ./hello.sh Hello Word! [root@test scripts]# #方法3 source .执行脚本 子shell中的内容调用到父shell中执行 [root@test scripts]# source hello.sh Hello Word! [root@test scripts]# . hello.sh Hello Word! [root@test scripts]#
环境变量
什么是环境变量?
LANG='en_US.UTF-8'
name=test
右边一堆内容 使用一个名字来代替称为环境变量
如何查看变量环境变量 加$
echo $name
env 查看系统定义的环境变量
变量的分类
全局变量(环境变量) 针对系统所有用户生效
局部变量(普通变量) 针对当前登录用户生效
变量生存周期分类 两类
临时性 export 声明变量即可 或者 name=oldboy
永久性 修改配置文件 /etc/profile
#环境变量文件执行顺序 1. /etc/profile 2. .bash_profile 3. .bashrc 4. /etc/bashrc #如果按照文件内容的生效顺序 1. /etc/profile 2. .bashrc 3. /etc/bashrc 4. .bash_profile
如何定义环境变量
要求以字母 数字 和下划线组合 尽量以字母和_开头 等号两端不允许有空格 名字 见名知其意 变量名字的定义的语法: OLDBOY_AGE=18 系统用的变量都是大写 oldboy_age=18 全小写 oldboy_Age=18 小驼峰语法 Oldboy_Age=18 大驼峰语法 变量值得定义: 1.数字定义 连续的数字 test=188888 2.字符串定义 连续的字符串 name="test test test" 不知道加什么就加双引号 例子: CODE_DIR=/etc/sysconfig/network-scripts/ [root@test scripts]# name=opesn [root@test scripts]# echo "$name" opesn [root@test scripts]# echo '$name' $name [root@test scripts]# 3.命令的定义 方法1 [root@test scripts]# tim=`date +%F` 方法2 [root@test scripts]# tim=$(date +%F)
特殊的位置变量
$0 代表了脚本的名称,如果使用全路径执行则脚本名称带全路径
$n 代表脚本的第n个参数 $0被脚本名称占用 参数从$1开始 $9 以后需要加{}
$# 代表脚本传参的总个数
$* 获取脚本的所有的参数 不加双引号和$@相同 加上双引号则把参数视为一个整体 $1$2$3(在循环体中)
$@ 获取脚本的所有的参数 不加双引号和$* 相同 加上双引号则把参数视为独立的(在循环体中)
$? 获取上一条命令的结果 0为成功 非0失败 0-255之间
$$ 获取脚本的PID
$! 上一个在后台运行的脚本的PID 调试使用
$_ 获取命令行或脚本的最后一个参数 相当于ESC .
变量传参的三种方法
方法1 直接传参 方法2 赋值传参 方法3 read传参 #方法1 [root@test scripts]# vim test.sh #!/bin/bash echo $1 $2 [root@test scripts]# bash test.sh 1 2 1 2 [root@test scripts]# #方法2 [root@test scripts]# vim test.sh #!/bin/bash name1=$1 name2=$2 echo ${name1} ${name2} [root@test scripts]# bash test.sh 1 2 1 2 [root@test scripts]# #方法3 [root@test scripts]# bash test.sh please input name : 1 please input name : 2 1 2 [root@test scripts]# #使用read方式更改主机名和IP地址 [root@test scripts]# vim hostname_and_ip.sh #!/bin/bash eth=/etc/sysconfig/network-scripts/ifcfg-eth0 read -p "please input hostname: " name hostnamectl set-hostname ${name} read -p "please input IP: " ipadd sed -i "/IPADDR/c IPADDR=${ipadd}" ${eth} [root@test scripts]# [root@test scripts]# bash hostname_and_ip.sh please input hostname: opesn please input IP: 10.0.1.250 [root@test scripts]#
变量的子串
#计算变量的长度 [root@test scripts]# echo $test|wc -L 10 [root@test scripts]# echo ${#test} 10 [root@test scripts]# expr length "$test" 10 [root@test scripts]# echo $test |awk '{print length}' 10 [root@test scripts]# #截取变量中的字符 [root@test scripts]# echo ${test:2:2} am [root@test scripts]# echo ${test:2} am opesn [root@test scripts]# echo ${test:2:4} am o [root@test scripts]#
变量子串的删除和替换
#从前面往后面删除(##代表贪婪匹配) [root@test scripts]# url=www.baidu.com [root@test scripts]# echo ${url} www.baidu.com [root@test scripts]# echo ${url#*.} baidu.com [root@test scripts]# echo ${url#*.*.} com [root@test scripts]# echo ${url##*.} com [root@test scripts]# #从后面往前面删除(%代表贪婪匹配) [root@test scripts]# url=www.baidu.com [root@test scripts]# echo ${url} www.baidu.com [root@test scripts]# echo ${url%.*} www.baidu [root@test scripts]# echo ${url%.*.*} www [root@test scripts]# echo ${url%%.*} www [root@test scripts]# #替换(//代表贪婪匹配) [root@test scripts]# url=www.baidu.com [root@test scripts]# echo ${url} www.baidu.com [root@test scripts]# echo ${url/baidu/qq} www.qq.com [root@test scripts]# echo ${url/w/a} aww.baidu.com [root@test scripts]# echo ${url//w/a} aaa.baidu.com [root@test scripts]#
数值运算
#expr只能做整数运算 [root@test scripts]# expr 1 + 1 2 [root@test scripts]# expr 5 - 5 0 [root@test scripts]# expr 1 \* 5 5 [root@test scripts]# expr 10 / 5 2 [root@test scripts]# #$[]只能做整数运算 [root@test scripts]# echo $[10+10] 20 [root@test scripts]# echo $[10-10] 0 [root@test scripts]# echo $[10*10] 100 [root@test scripts]# echo $[10/10] 1 [root@test scripts]# #$(())只能做整数运算,效率最高的运算 [root@test scripts]# echo $((5+5)) 10 [root@test scripts]# echo $((5-5)) 0 [root@test scripts]# echo $((5*5)) 25 [root@test scripts]# echo $((5/5)) 1 [root@test scripts]# #let只能做整数运算 [root@test scripts]# let sum=1+1 [root@test scripts]# echo $sum 2 [root@test scripts]# #bc整数、小数运算 [root@test scripts]# echo 10+10|bc 20 [root@test scripts]# echo 10-10|bc 0 [root@test scripts]# echo 10*10|bc 100 [root@test scripts]# echo 10/10|bc 1 [root@test scripts]# #awk小数、整数运算 [root@test scripts]# awk 'BEGIN{print 10-5.5}' 4.5 [root@test scripts]# awk 'BEGIN{print 10+5.5}' 15.5 [root@test scripts]# awk 'BEGIN{print 10*5}' 50 [root@test scripts]# awk 'BEGIN{print 10/5}' 2 [root@test scripts]# #python小数、整数运算 [root@test scripts]# python >>> 10+5 15 >>> 10-2 8 >>> 5*5 25 >>> 5/5 1 >>> #expr 判断传参是否为整数 [root@test ~]# vim expr.sh #!/bin/bash num1=$1 num2=$2 expr $1 + $2 &>/dev/null [ $? -ne 0 ] && echo "请输入两个整数" && exit echo "${num1}+${num2}=$[${num1}+${num2}]" [root@test ~]# bash expr.sh 1 1.5 请输入两个整数 [root@test ~]# bash expr.sh 1 1 1+1=2 [root@test ~]#
条件表达式
[ -f file ] -e 文件存在则为真 -f 是否存在并且为普通文件 -d 是否为目录 -r 是否可读 -w 是否可写 -x 是否可执行 [root@test ~]# [ -e /etc ] && echo "存在"||echo "不存在" 存在 [root@test ~]# [ -e /etcc ] && echo "存在"||echo "不存在" 不存在 [root@test ~]# [ -f /etc ] && echo "存在"||echo "不存在" 不存在 [root@test ~]# [ -f /etc/hosts ] && echo "存在"||echo "不存在" 存在 [root@test ~]#
数值表达式
[ 整数1 比较符 整数2 ] -eq 等于 -ne 不等于 -gt 大于 -ge 大于等于 -lt 小于 -le 小于等于 [root@test ~]# [ 10 -eq 10 ] && echo "ok" ||echo "no" ok [root@test ~]# [ 10 -gt 10 ] && echo "ok" ||echo "no" no [root@test ~]# [ 10 -ge 10 ] && echo "ok" ||echo "no" ok [root@test ~]# [ 10 -lt 10 ] && echo "ok" ||echo "no" no [root@test ~]# [ 10 -le 10 ] && echo "ok" ||echo "no" ok [root@test ~]# [ 10 -ne 10 ] && echo "ok" ||echo "no" no [root@test ~]#
多整数比较
[ 整数1 比较符 整数2 -o 整数3 比较符 整数4 ] [ 整数1 比较符 整数2 -a 整数3 比较符 整数4 ] -o 或 -a 与 [root@test ~]# [ 10 -eq 10 -o 10 -ne 10 ] [root@test ~]# echo $? 0 [root@test ~]# [ 10 -eq 10 -a 10 -ne 10 ] [root@test ~]# echo $? 1 [root@test ~]#
字符串比较
#字符串比较必须加双引号 [root@test ~]# [ "$USER" = root ] [root@test ~]# echo $? 0 [root@test ~]# [ "$USER" = "root" ] [root@test ~]# echo $? 0 [root@test ~]# [ "$USER" = "opesn" ] [root@test ~]# echo $? 1 [root@test ~]# [ ! "$USER" = "opesn" ] [root@test ~]# echo $? 0 [root@test ~]# -z 值为0,则为真 -n 值不为0,则为真 [root@test ~]# [ -n "" ] && echo 0 || echo 1 1 [root@test ~]# [ -n "s" ] && echo 0 || echo 1 0 [root@test ~]# [root@test ~]# [ -z "s" ] && echo 0 || echo 1 1 [root@test ~]# [ -z "" ] && echo 0 || echo 1 0
正则对比
正则比对 [[]] 取反 ! 写在表达式的前面 [root@test ~]# [[ $USER =~ ^r ]] [root@test ~]# echo $? 0 [root@test ~]# [[ $USER =~ ^n ]] [root@test ~]# echo $? 1 [root@test ~]# [root@test ~]# [[ ! $USER =~ ^n ]] [root@test ~]# echo $? 0 [root@test ~]# [[ ! $USER =~ ^r ]] [root@test ~]# echo $? 1 [root@test ~]#
if判断
#单分支(单分支:一个条件一个结果) [root@test test]# vim if.sh #!/bin/bash if [ -f /etc/passwd ];then echo "yes" fi #双分支(双分支:一个条件两个结果) [root@test test]# vim if.sh #!/bin/bash if [ -f /etc/passwdd ];then echo "passwdd file is yes" else echo "passwdd file is no" fi #多分支(多分支:多个条件多个结果) [root@test test]# vim if.sh #!/bin/bash if [ -f /etc/passwdd ];then echo "passwdd file is yes" elif [ -f /etc/passwddd ];then echo "passwddd file is yes" elif [ -f /etc/passwd ];then echo "passwd file is yes" fi
case语句
case 变量 in 变量内容1) 命令组 ;; 变量内容2) 命令组 ;; 变量内容3) 命令组 ;; *) echo "请输入正确的变量" esac
case语句书写
[root@test ~]# vim jumpserver.sh #!/bin/bash clear cat <<EOF * * * * * * * * * * * * * * * * 1.m01=10.0.1.61 * * q.退出 * * * * * * * * * * * * * * * * EOF trap "" INT HUP TSTP while true do read -p "请输入你要连接的服务器:" sum case $sum in 1) clear ssh 10.0.1.61 clear cat <<EOF * * * * * * * * * * * * * * * * 1.m01=10.0.1.61 * * q.退出 * * * * * * * * * * * * * * * * EOF ;; q) exit ;; *) cat <<EOF * * * * * * * * * * * * * * * * 1.m01=10.0.1.61 * * q.退出 * * * * * * * * * * * * * * * * EOF esac done
for循环
for 变量名称 in 取值列表 (数字 字符串 `cat 文件` 命令) do 命令 echo hehe done
for循环书写
[root@test ~]# vim ping.sh #!/bin/bash . /etc/init.d/functions for i in {60..65} do { ping -c1 -w1 10.0.1.$i &>/dev/null [ $? -eq 0 ] && action "ping 10.0.1.$i is ok" /bin/true } & done wait echo "在线取IP is ok"
while循环
while [ 条件 ] 为真则执行 为假退出
[root@test ~]# vim while_sum+.sh #!/bin/bash sum=0 i=1 while true do sum=$[${sum}+${i}] [ $i -eq 100 ] && echo $sum && exit ((i++)) done
语句控制
exit #直接退出整个脚本 break #跳出循环体 执行循环体外的命令 continue #跳出当前的循环 继续下一次循环
exit
[root@test test]# cat exit.sh #!/bin/sh while true do echo hehe exit done echo done....... [root@test test]# bash exit.sh hehe [root@test test]#
break
[root@test test]# cat break.sh #!/bin/sh while true do echo hehe break done echo done....... [root@test test]# bash break.sh hehe done....... [root@test test]#
continue
[root@test test]# cat continue.sh #!/bin/sh for i in {1..5} do [ $i -eq 4 ] && continue echo $i done echo done....... [root@test test]# bash continue.sh 1 2 3 5 done....... [root@test test]#
函数
1) 完成特定功能的代码块 2) 代码模块化 便于复用和可读 3) 和变量类似 必须先定义在调用,如果不调用则函数不会执行
函数的定义方法
[root@test test]# cat function.sh #!/bin/sh fun1(){ echo "函数第一种定义方式" } function fun2 { echo "函数第二种定义方式" } function fun3(){ echo "函数第三种定义方式" } fun1 fun2 fun3 [root@test test]# bash function.sh 函数第一种定义方式 函数第二种定义方式 函数第三种定义方式 [root@test test]#
函数的传参
[root@test test]# cat fun.sh #!/bin/bash fun() { echo 1 echo "$1" return 100 } fun $1 [root@test test]# bash fun.sh 2 1 2 [root@test test]#
函数的本地变量local
[root@test test]# cat fun.sh #!/bin/sh fun1(){ local num=20 for i in `seq 30` do total=$[num+i] done echo "结果为:$total" } fun1 echo $num [root@test test]# bash fun.sh 结果为:50 [root@test test]#
return 返回值
[root@test test]# cat fun.sh #!/bin/sh fun(){ if [ -f "$1" ];then return 50 else return 100 fi } fun $1 re=$? [ $re -eq 50 ] && echo "$1 文件存在" [ $re -eq 100 ] && echo "$1 文件不存在" [root@test test]# bash fun.sh /etc/hosts /etc/hosts 文件存在 [root@test test]# bash fun.sh /etc/hostsssssssss /etc/hostsssssssss 文件不存在 [root@test test]#
数组
普通数组
只能使用数字作为索引 1) 数组的定义格式 第一种定义方式 数组名[索引名] 默认的索引从0开始 数组名[下标]=值 第二种定义方式 数组名=([0]=值 [1]=值 [20]=值) 第三种定义方式 array=(shell mysql kvm docker) array=(shell mysql kvm docker [10]=test [20]=hehe) 2) 查看数组的值 echo ${array[0]} 3) 查看数组的索引 [root@backup ~]# echo ${!array[*]}
[root@test test]# cat array.sh #!/bin/bash IPS=( 114.114.114.114 223.5.5.5 ) for i in ${IPS[*]} do ping -c 1 -W 1 $i &>/dev/null [ $? -eq 0 ] && echo ok|| echo error done [root@test test]# bash array.sh ok ok [root@test test]#
关联数组
declare -A array 声明关联数组 [root@backup ~]# declare -A array [root@backup ~]# array[index1]=shell [root@backup ~]# array[index2]=MySQL [root@backup ~]# array[index3]=KVM [root@backup ~]# echo ${array[*]} shell MySQL KVM [root@backup ~]# echo ${!array[*]} index1 index2 index3
[root@test test]# cat sex.txt m m m f f f m f m x [root@test test]# cat array.sh #!/bin/sh declare -A array while read line do let array[$line]++ done<sex.txt for i in ${!array[*]} do echo "$i 出现了 ${array[$i]} 次" done [root@test test]# bash array.sh f 出现了 4 次 m 出现了 5 次 x 出现了 1 次 [root@test test]#