一、shell编程简介
1、shell编程初识
程序 语言 编程 --------------------------------------------------------------------------------------- 编译型语言:编译型语言的首先将源代码编译生成机器语言,再由机器运行机器码(二进制)。像C/C++等都是编译型语言。 解释型语言:源代码不是直接翻译成机器语言,而是先翻译成中间代码,再由解释器对中间代码进行解释运行。比如Python/JavaScript/Shell等都是解释型语言
2、shell的定义
shell 也是一种程序设计语言,它有变量,关键字,各种控制语句,语法结构,利用shell程序设计语 言可以编写功能很强、代码简短的程序。
我们可以通过shell的命令来控制和操作操作系统,比如linux中的shell命令就包括ls、cd、pwd等等,总结来说shell就是一个命令解释器,他通过接收用户输入的shell命令来启动、停止程序的运行或者对计算机进行控制。
3、shell的分类和更改
# cat /etc/shells /bin/sh /bin/bash /sbin/nologin /usr/bin/sh /usr/bin/bash /usr/sbin/nologin #默认shell: bash shell #centos中脚本使用的默认 shell 为 /usr/bin/bash 查看当前正在使用的shell # echo $SHELL 查看当前正在使用的shell /bin/bash shell种类的修改 # vim /etc/passwd 编辑登录shell
4、shell特性
适用范围
Shell 能做什么? 1. 自动化批量系统初始化程序 (update,软件安装,时区设置,安全策略...)---初始化脚本 2. 自动化批量软件部署程序 (LAMP,LNMP,Tomcat,LVS,Nginx)---自动化安装脚本 3. 日志分析处理程序(PV, UV, 200, !200,grep/awk)----akw、sed、grep 4. 自动化备份恢复程序(MySQL完全备份/增量 + Crond)-----数据备份恢复脚本 5. 自动化信息采集及监控程序(收集系统/应用状态信息,CPU,Mem,Disk,Net,TCP Status,Apache,MySQL)--监控脚本 6. 配合Zabbix信息采集(收集系统/应用状态信息,CPU,Mem,Disk,Net,Apache,MySQL) 7. Shell可以做任何运维的事情(一切取决于业务需求)
shell经常使用的元素
文件描述符与输出重定向: 在 shell程序中,最常使用的FD (file descriptor) 大概有三个, 分别是: 0: Standard Input (STDIN) 1: Standard Output (STDOUT) 2: Standard Error Output (STDERR) 在标准情况下, 这些FD分别跟如下设备关联: stdin(0): keyboard 键盘输入,并返回在前端 stdout(1): monitor 正确返回值 输出到前端 stderr(2): monitor 错误返回值 输出到前端 >a.txt 1>a.txt 2>a.txt &>a.txt 1>&2 2>&1 一般来说, "1>" 通常可以省略成 ">". 1>&2 正确返回值传递给2输出通道 &2表示2输出通道,之前如果有定义标准错误重定向到某log文件,那么标准输出也重定向到这个log文件,如果此处错写成 1>2, 就表示把1输出重定向到文件2中. 2>&1 错误返回值传递给1输出通道, 同样&1表示1输出通道. 例子:当前目录下只有a.txt,没有b.txt # touch a.txt # ls a.txt b.txt 1>file.out 2>&1 # cat file.out ls: cannot access b.txt: No such file or directory a.txt 现在, 正确的输出和错误的输出都定向到了file.out这个文件中, 而不显示在前端
5、bash初始化
用户登录时相关的bash配置文件 (登录脚本)属于全局配置文件: /etc/profile /etc/profile.d/*.sh /etc/bashrc 个人配置文件,每个用户的配置文件 ~/.bsah_profile ~/.bashrc #profile类的文件: 设定环境变量,运行命令或脚本,用户在登录的时候会自动生效 #bashrc类的文件: 定义命令别名 用户登录时加载bash配置文件的过程: 登录式shell加载配置文件过程 ~/.bash_profile --> ~/.bashrc --> /etc/bashrc ---> /etc/profile --> /etc/profile.d/*.sh
6、shell编程特点
bash shell特性
补全 tab键 #yum -y install bash-completion 历史--history 别名---alias 快捷键---ctrl+c+l 前后台作业---jobs fg bg & 重定向 > >> < 管道 ---| #命令排序执行: ; && || &&:逻辑与,前面执行成功,后面才执行。前面命令执行失败,后面命令也不执行 ||:逻辑或,前面执行失败,后面执行,前面命令执行成功,后面不执行。 ;:从左往右按顺序执行,不管前面执行成功与否,后面都执行 通配符:[] {} ? *
7、通配符置换
在 Shell 中命令中,通常会使用通配符表达式来匹配一些文件 *,?,[],{} 例: 字符 含义 实例 * 匹配 0 或多个字符 a*b a与b之间可以有任意长度的任意字符, 也可以一个也没有, 如aabcb, axyzb, a012b, ab。 ? 匹配任意一个字符 a?b a与b之间必须也只能有一个字符, 可以是任意字符, 如aab, abb, acb, a0b。 [list] 匹配 list 中的任意单一字符 a[xyz]b a与b之间必须也只能有一个字符, 但只能是 x 或 y 或 z, 如: axb, ayb, azb。 [!list] 匹配 除list 中的任意单一字符 a[!0-9]b a与b之间必须也只能有一个字符, 但不能是阿拉伯数字, 如axb, aab, a-b。 [c1-c2] 匹配 c1-c2 中的任意单一字符 如:[0-9] [a-z] a[0-9]b 0与9之间必须也只能有一个字符 如a0b, a1b... a9b。 {string1,string2,...} 匹配 sring1 或 string2 (或更多)其一字符串 a{abc,xyz,123}b a与b之间只能是abc或xyz或123这三个字符串之一。
8、shell脚本规范
[root@linux-server ~]# vim helloworld.sh ---.sh代表这个文件是个shell脚本 拓展名后缀,如果省略.sh则不易判断该文件是否为shell脚本 1. #!/usr/bin/env bash #---shebang蛇棒, 解释器, 翻译功能 2. # 3. # Author: soso666 4. # Email: soso666@163.com ---这就是注释, 你没看错 5. # Github: https:github.com/soso666 6. # Date: 2024/5/18 7. printf "hello world\n" 第一行: “#!/usr/bin/env bash”叫做shebang, shell语法规定shell脚本文件第一行为整个文件的解释器 第二行: 为“#”开头的行为注释行默认不会被程序所读取, 用来说明文件及标定所属人员使用, 也可用来解释程序 第七行: 为格式化打印语句printf, printf可以把后面的“hello world”打印到指定的终端中, \n 为换行符
功能说明:打印hello world
# sh helloworld.sh hello world
# chmod +x helloworld.sh
# ./helloworld.sh
# /root/helloworld.sh
hello world
二、Shell变量的类型
bash作为程序设计语言和其它高级语言一样也提供使用和定义变量的功能
变量分类:预定义变量、环境变量、自定义变量、位置变量
1、预定义变量
预定义变量:预定义的特殊变量有着特殊的含义,用户不可以更改,所有的预定义变量都由$符号和另外一个符号组成,常用的预定义特殊变量如下: $$ 当前进程PID $? 命令执行后的返回状态:0 为执行正确,非 0 为执行错误 $# 位置参数的数量 $* 所有位置参数的内容 $@ 显示所有的参数 $! 上一个后台进程的PID (wait命令中使用,后面讲) 拓展:$* 和 $@ 有什么区别 ------------------------------------------------------------------------------ $$案例: # echo $$ 当前进程的进程号(PID) # cat a.sh #!/bin/bash ls echo $$ # ./a.sh anaconda-ks.cfg 1971 ------------------------------------------------------------------------------ # echo $? 最后一次执行的命令的返回状态。如果这个变量的值为 0,则证明上一条命令正确执行;如果这个变量的值为非 0 ,则 证明上一条命令执行错误 # ls anaconda-ks.cfg a.txt b.txt file.out helloworld.sh #ls命令正确执行 # echo $? 0 #预定义变量"$?"的值是0,证明上一条命令正确执 ------------------------------------------------------------------------------ # echo $! 后台运行的最后一个进程的进程号(PID) $!案例: # sleep 3000 & [1] 3731 # ps -ef|grep sleep root 3731 3283 0 11:48 pts/1 00:00:00 sleep 3000 root 3736 3283 0 11:48 pts/1 00:00:00 grep --color=auto sleep # echo $! 3731 ----------------------------------------------------------------------------------- $#:表示命令行参数的个数。 $@:包含所有的命令行参数,即“$1、$2、$3......”。 与$*类似,但是每个参数都是独立的,可以用于循环,参数个数无限。 $*:包含所有的命令行参数,即“$1、$2、$3......”。 $0:脚本执行路径--脚本名字
2、自定义变量
自定义变量:就是自己设置的变量只能在当前终端和脚本中使用 变量名称=值 #定义变量 #变量名称:只能由字母,数字,下划线组成,不能以数字/特殊字符开头; #注意:应该让变量名称有意义,使用规范; = 赋值符号 前后不能有空格 ; 值: 所有的字符串和数字都可以; 引用变量: $变量名 或 ${变量名}。 示例: # a=100 # echo $a 100 # echo $aa # 这里输出为空,因为解释器认为$aa是变量 # echo ${a}a 100a 查看变量: echo $变量名 取消变量: unset 变量名,仅在当前shell中有效
3、环境变量
环境变量:shell在开始执行时已经定义好的,就是系统执行环境的一些设置 # env #env是 environment (环境) 的简写,所有的环境变量(包含自定义的环境变量) # set #列出系统中所有的变量,包括自定义的变量 export 变量名 #使自定义的变量成为环境变量,才使用这个参数。环境变量拥有可继承性:export之后就拥有继承性环境变量可以被向下继承 永久生效 写到4个登陆脚本中 ~/.bashrc ~/profile 更好放在/etc/profile.d/目录下建立独立的环境变量配置文件 # vim /etc/profile.d/test.sh IPADDT=192.168.1.1 # source /etc/profile.d/test.sh #让环境变量生效 # echo $IPADDT 192.168.1.1 常用环境变量:USER UID HOME HOSTNAME PWD PS1 PATH PATH:存储所有命令所在的路径(只要是在PATH变量中的目录,就可以直接运行目录下的命令,不需要输入命令的绝对路径) #子进程 仅会继承父 shell 的环境变量, 不会继承父 shell 的自定义变量
4、位置变量
位置变量也叫位置参数:在脚本代码中调用通过命令行传递给脚本的参数 $1 $2 $3 $... #分别对应传递给脚本内容里面的第1、第2等参数 例子: # /test.sh start #start是第1个位置参数 # /test.sh 2 3 5 hello #2是第1个位置参数,3是第2个位置参数...依次类推 例子: [root@linux-server ~]# cd /opt/test/script/ [root@linux-server script]# vim weizhi.sh #!/usr/bin/bash echo 我的第一个位置参数是:$1 echo 我的第二个位置参数是:$2 echo 我的第三个位置参数是:$3 echo 我的第四个位置参数是:$4 echo 一共有 $# 个位置参数 echo 你输入的参数分别是:$* [root@linux-server script]# chmod +x weizhi.sh [root@linux-server script]# ./weizhi.sh 1 3 4 6 我的第一个位置参数是:1 我的第二个位置参数是:3 我的第三个位置参数是:4 我的第四个位置参数是:6 一共有 4 个位置参数 你输入的参数分别是:1 3 4 6
5、变量运算
算式运算符: +、-、*、/、()、%取余 如:(5+3)*2 运算方式:$(()) $[] expr $(()):例子 # echo $(( 5+2-(3*2)/5 )) # echo $(((3*2)/5)) $[]:例子 # echo $[ 5 + 2 - (3*2)/5 ] expr:例子 # expr 5 + 3 8 #注意: 运算符号两边的空格必须写 乘法运算: # expr 5 \* 3 15 # expr 5 '*' 3 15 # expr $[5 * 4] 20 #脚本中常用的方式 echo "$a和$b的和是: `expr $a '+' $b`" echo $(($a + $b)) ================================================================= 取1到6之间的随机数: # echo $RANDOM # echo $(($RANDOM % 6 + 1)) 5 取1-10之间的随机数: # echo $[$RANDOM % 10 + 1] 10 计算我的信用卡一个月的利息,假设我欠10000块钱 #!/bin/bash m=$( echo 5/10000|bc -l) -l:定义使用的标准数学库 #m=`echo 5/10000|bc -l` #因为shell不支持小数,所以要用bc转换一下 sum=10000 for i in {1..365} do sum=$(echo $sum+$sum*$m | bc ) echo $sum done echo $sum 简单例子: #!/bin/bash sum=1 for i in {1..20} do sum=$(echo $sum+1|bc) echo $sum done
6、变量引用
变量引用 #转义:\ 1.当一个字符被引用时,其特殊含义被禁止,把有意义的变的没意义,把没意义的变的有意义 转义案例: # echo you now $1250 #$1会自动代表位置变量 you now 250 # echo you now \$1250 you now $1250 ------------------------------------------------------------------- \n \t # echo -e '5\\n6\n7' 5\n6 7 完全引用:'' 强引 #指的是被引号包围起来的变量名不会进行解析,原样变量名原样输出,这种方式比较适合定义显示纯字符串的情况,不希望解析变量、命令等的场景。 部分引用:"" 弱引 #指的是被引号包围起来的变量名会先进行解析,然后将变量的解析结果输出来。这种方式适合字符串中附带有变量和命令并且想将其解析后再输出的变量定义。 #读取用户标准输入:read read:功能就是读取键盘输入的值,并赋予变量 #read -t 5 var #read -p "提示信息" var #read -s #屏蔽回显,一般输入的内容需要保密 read后面的变量var可以只有一个,也可以有多个,这时如果输入多个数据,则第一个数据给第一个变量,第二个数据给第二 个变量,如果输入数据个数过多,则最后所有的值都给最后一个变量 [root@linux-server script]# vim readtest.sh #!/bin/bash # read test read -p "请输入你的银行卡帐号:" num read -p "请在五秒内输入密码:" -t 5 pass echo "你的银行卡号是: $num" echo "你的密码是: $pass" [root@linux-server script]# ./readtest.sh #-s 选项 能够使read命令中输入的数据不显示在监视器上
7、脚本运行方式
创建bash脚本
1.创建脚本文件 #!/bin/bash 指定命令解释器 # 注释 编写bash指令集合 2.修改权限 chmod +x test.sh
bash脚本的执行
#chmod +x script.sh #bash scripts.sh #sh scripts.sh #/shelldoc/scripts.sh #./scripts.sh #source ./scripts.sh 使用当前shell执行 比如cd /tmp会改变当前shell环境,但是其他的方式不会
bash脚本测试
•sh –x script 这将执行该脚本并显示所有变量的值 •sh –n script 不执行脚本只是检查语法模式,将返回所有错误语法 •sh –v script 执行脚本前把脚本内容显示在屏幕上
8、变量置换
8.1命令替换
a=`date +%m%d` a=$(date +%m%d) 反引号亦可用$() 代替 常用: # rpm -qa|grep httpd # yum -y remove `rpm -qa|grep httpd`
8.2变量替换
一 ${parameter:-word} 若 parameter 为空或未设置,则用 word 代替 parameter 进行替换,parameter 的值不变 # a=1 # unset b # a=${b:-3} # echo $a 3 # echo $b # 若 parameter 不为空,则不替换,parameter 的值不变 # unset b # # a=1 # b=2 # a=${b:-3} # echo $a 2 # echo $b 2 # 二 ${parameter:=word} 若 parameter 为空或未设置,则用 word 代替 parameter 进行替换,parameter 的值改变 # a=1 # unset b # a=${b:=3} # echo $a 3 # echo $b 3 # 若 parameter设置了,则 不替换,parameter 的值不变 # a=1 # b=2 # a=${b:=3} # echo $a 2 # echo $b 2 # 三 ${parameter:+word} 若 parameter 未设置了,则不替换,parameter 的值不变 # a=1 # unset b # a=${b:+3} # echo $a # # echo $b # 若 parameter 设置了,则用 word 代替 parameter 进行替换,parameter 的值不变 # a=1 # b=2 # a=${b:+3} # echo $a 3 # echo $b 2 #
8.3变量替换-匹配截取
${变量#关键词} 若变量内容从头开始的数据符合『关键词』,则将符合的最短数据切除 ${变量##关键词} 若变量内容从头开始的数据符合『关键词』,则将符合的最长数据切除 ${变量%关键词} 若变量内容从尾向前的数据符合『关键词』,则将符合的最短数据切除 ${变量%%关键词} 若变量内容从尾向前的数据符合『关键词』,则将符合的最长数据切除 ${变量/旧字符串/新字符串} 若变量内容符合『旧字符串』则『第一个旧字符串会被新字符串替代』 ${变量//旧字符串/新字符串} 若变量内容符合『旧字符串』则『全部的旧字符串会被新字符串替代』 索引及切片 # a=12345678 # echo ${a:5} //从第5位开始截取 678 # echo ${a:3:4} //从第3位开始,截取4位 4567 # echo ${a:2:-1} //从第2位开始,截取到倒数第1位前 34567 # echo ${a:2:-2} //从第2位开始,截取到倒数第2位前 3456 # url=www.sina.com.cn # echo ${#url} //获取变量的长度 15 # echo ${url} //正常显示变量 www.sina.com.cn 变量内容的删除 [root@youngfit ~]# echo ${url#*.} 从前往后,最短匹配 sina.com.cn [root@youngfit ~]# echo ${url##*.} 从前往后,最长匹配 cn [root@youngfit ~]# echo ${url%.*} 从后往前,最短匹配 www.sina.com [root@youngfit ~]# echo ${url%%.*} 从后往前,最长匹配 www [root@youngfit ~]# echo ${url#a.} 从前往后匹配,必须得以a.开头 www.sina.com.cn [root@youngfit ~]# echo ${url#*a.} 从前往后匹配,必须得以a.开 com.cn [root@localhost ~]# vim mail.sh 脚本案例: #!/usr/bin/bash read -p "请输入你的邮箱 " mail echo "你的邮箱是$mail" echo "你的邮箱服务器是${mail#*@}" mail_host=${mail#*@} case $mail_host in 163.com) echo "网易服务器" ;; 126.com) echo "126服务器" ;; qq.com) echo "qq邮箱" ;; *) echo "您输入的邮箱不正确" exit 2 esac ${变量/旧字符串/新字符串} 若变量内容符合『旧字符串』则『第一个旧字符串会被新字符串替代』 ${变量//旧字符串/新字符串} 若变量内容符合『旧字符串』则『全部的旧字符串会被新字符串替代』 变量内容的替换 $ a=123456123789 $ echo ${a/1/2} 第一次匹配的被替换 223456123789 $ echo ${a//1/} 全局的匹配被替换为空 2345623789 $ echo ${a/1/x} x23456123789 $ echo ${a//1/x} x23456x23789 例:file=/dir1/dir2/dir3/my.file.txt ${file#*/}: 拿掉第一条 / 及其左边的字符串:dir1/dir2/dir3/my.file.txt ${file##*/}: 拿掉最后一条 / 及其左边的字符串:my.file.txt ${file#*.}: 拿掉第一个 . 及其左边的字符串:file.txt ${file##*.}: 拿掉最后一个 . 及其左边的字符串:txt ${file%/*}: 拿掉最后条 / 及其右边的字符串:/dir1/dir2/dir3 ${file%%/*}: 拿掉第一条 / 及其右边的字符串:(空值) ${file%.*}: 拿掉最后一个 . 及其右边的字符串:/dir1/dir2/dir3/my.file ${file%%.*}: 拿掉第一个 . 及其右边的字符串:/dir1/dir2/dir3/my 记忆的方法为: # 是去掉左边(在键盘上 # 在 $ 之左边) % 是去掉右边(在键盘上 % 在 $ 之右边) 单一符号是最小匹配﹔两个符号是最大匹配(贪婪匹配)
basename & dirname
basename 命令 basename 是去除目录后剩下的名字,取文件名 例: [root@linux-server ~]# temp=/home/temp/1.test [root@linux-server ~]# base=`basename $temp` [root@linux-server ~]# echo $base 1.test dirname 是获取目录名 例: [root@linux-server ~]# temp=/home/temp/1.test [root@linux-server ~]# dir=`dirname $temp` [root@linux-server ~]# echo $dir /home/temp
三、shell编程--流程控制
1、shell编程之条件结构
shell条件测试
测试 test 条件 条件为真返回 0,条件为假返回 1 [ 条件 ] test能够理解3中类型的表达式 1.文件测试 2.字符串比较 3.数字比较 字符串 -n STRING the length of STRING is nonzero -z STRING the length of STRING is zero STRING1 = STRING2 the strings are equal STRING1 != STRING2 the strings are not equal 数字 eq 等于 ne 不等于 ge 大于等于 gt 大于 le 小于等于 lt 小于 文件 test -f 存在且是正规文件 -d 存在且是目录 -h 存在且是符号链接 -b 块设备 -c 字符设备 -e 文件存在
2、shell分支if语句
流控制: •在一个shell脚本中的命令执行顺序称作脚本的流。大多数脚本会根据一个或多个条件来改变它们的流。 •流控制命令:能让脚本的流根据条件而改变的命令称为条件流控制命令 •exit语句:退出程序的执行,并返回一个返回码,返回码为0正常退出,非0为非正常退出,例如: •exit 0 条件判断 If代码返回0表示真,非0为假 if语句语法如下: if list1 then list2 elif list3 then list4 else list5 fi 例: #!/bin/bash read -p "请输入号码: " num if [ $num = 1 ];then echo "1" elif [ $num = 2 ];then echo "2" else echo "输入有误!" fi 多个条件联合 逻辑与 if [ $condition1 ] && [ $condition2 ] if [ $condition -a $condition2 ] if [[ $condition1 && $condition2 ]] 逻辑或 if [ $condition1 ] || [ $condition2 ] if [ $condition -o $condition2 ] if [[ $condition1 || $condition2 ]] 练习: 编写脚本port.sh,执行脚本后显示系统的httpd、ftp、ssh、sendmail这些服务是否开启 case case 语句是 shell 中流控制的第二种方式,语法如下: case $word in pattern1) list1 ;; pattern2) list2 ;; ... ... patternN) listN ;; *) list* ;; esac 命令;;表明流应该跳转到case语句的最后,类似C语言中的break指令。 练习:建立脚本case.sh,当执行时,要求我们在键盘输入适当的值(one|two|three),当输入正确时并打印,当输入错误时会提示你,应该输入正确的值。
3、shell分支case语句
1. case 变量 in 2. 模式1) 3. 命令序列1 4. ;; 5. 模式2) 6. 命令序列2 7. ;; 8. 模式3) 9. 命令序列3 10. ;; 11.*) 12. 无匹配后命令序列 13.esac
第一行: 声明case关键字调用case语法, 紧跟的“变量”一般为用户的输入值, in代表从下方的各个模式进行匹配
第2-4行: 匹配到“模式1”后进行命令的输出或执行, 模式1: 一般为字符或数值
第11-12行: 当用户输入的字符不存在匹配模式时, 直接执行或打印*)下的命令或语句
实例1: [root@linux-server script]# vim foo.sh #!/usr/bin/env bash case $1 in foo) echo "bar" ;; bar) echo "foo" ;; *) echo "Usage:$0 '{foo|bar}'" ;; esac
4、shell编程之循环结构
4.1 shell循环for语句
for i in {取值范围} do 循环体 done #!/usr/bin/env bash # # Author: # Date: 2024/**/** for i in {1..100} do echo $i done #!/bin/bash for(( i=1;i <= 5;i++)) do echo "$i" done 参数解释: 默认值 i=1 条件 i<=多少?取决于定义,为用户输入的变量,先条件成立在执行命令 增幅 i++ 执行一次加一 区别: i++===先赋值在运算 ++i===先运算在赋值 例子 [root@localhost script]# i=1 [root@localhost script]# h=1 [root@localhost script]# let x=i++ [root@localhost script]# echo $x 1 [root@localhost script]# echo $i 2 [root@localhost script]# let y=++h [root@localhost script]# echo $y 2 [root@localhost script]# echo $h 2
测试生产环境的主机存活性,将up的ip保存在一个文件中,将down的ip保存在一个文件中
#!/usr/bin/env bash # # Author: >ip_alive.txt >ip_down.txt segment="192.168.161" for i in {2..254} do { ping -c1 $segment.$i &>/dev/null if [ $? -eq 0 ];then printf "alive: $segment.$i\n" >>ip_alive.txt else printf "down: $segment.$i\n" >>ip_down.txt fi }& done wait echo "finish..."
4.2 shell循环while语句
while 条件 do 循环体 done 案例: # vim c.sh #!/usr/bin/bash i=1 while [ $i -lt 50 ] do echo $i #let i++ done 注意观察,#请问如何能够自动终止 #在shell中,let命令用于指定算术运算,即 let expr
案例1:
创建一个文件里面的用户 [root@shell ~]# cat user.txt jack #提前创建 tom #不创建 [root@shell ~]# cat user.sh #!/bin/bash # done < user.txt 会一行一行处理文件的内容,赋值给变量 while read user do id $user &>/dev/null if [ $? -eq 0 ];then echo "$user is already exists" else useradd $user echo "create $user successfully" fi done < user.txt
4.3 shell循环until语句
until 条件 do 循环体 done -eq 等于 -ne 不等于 -gt 大于 -ge 大于等于 -lt 小于 -le 小于等于
[root@host ~]# cat until.sh #!/bin/bash x=1 until [ $x -ge 10 ] do echo $x x=`expr $x + 1` #let x++ done x=1 while [ ! $x -ge 10 ] do echo $x x=`expr $x + 1` done
4.4shell循环控制
shift、continue、break、exit
4.4.1shift
shift命令 位置参数可以用shift命令左移。比如shift 3表示原来的$4现在变成$1,原来的$5现在变成$2等等,原来的$1、$2、$3丢弃,$0不移动。不带参数的shift命令相当于shift 1。 #测试 shift 命令(x_shift3.sh) [root@youngfit shell]# cat x_shift3.sh #!/bin/bash shift echo "第一个位置参数: $1" [root@youngfit shell]# bash x_shift3.sh 2 3 第一个位置参数: 3 对于位置变量或命令行参数,其个数必须是确定的,或者当 Shell 程序不知道其个数时,可以把所有参数一起赋值给变量$*。 若用户要求 Shell 在不知道位置变量个数的情况下,还能逐个的把参数一一处理,也就是在 $1 后为 $2,在 $2 后面为 $3 等,则需要用shift把所有参数变成$1 #测试 shift 命令(x_shift.sh) #!/bin/bash until [ $# -eq 0 ] do echo "第一个参数为: $1 参数个数为: $#" shift done 执行以上程序x_shift.sh: $./x_shift.sh 1 2 3 4 结果显示如下: 第一个参数为: 1 参数个数为: 4 第一个参数为: 2 参数个数为: 3 第一个参数为: 3 参数个数为: 2 第一个参数为: 4 参数个数为: 1 从上可知 shift 命令每执行一次,变量的个数($#)减一,而变量值提前一位 用 until 和 shift 命令计算所有命令行参数的和。 #shift 上档命令的应用(x_shift2.sh) sum=0 until [ $# -eq 0 ] do sum=`expr $sum + $1` shift done echo "sum is: $sum" 执行上述程序: $x_shift2.sh 10 20 15 其显示结果为: 45
4.4.2continue、break、exit
Linux脚本中的break continue exit return
break 结束并退出本次循环
continue 在循环中不执行continue下面的代码,转而进入下一轮循环
exit 退出脚本, 常带一个整数给系统,如 exit 0
可理解为:break是立马跳出循环;continue是跳出当前条件循环,继续下一轮条件循环;exit是直接退出整个脚本
例如: 在循环过程中,有时候需要在未达到循环结束条件时强制跳出循环,Shell使用两个命令来实现该功能:break和continue。 break命令 break命令允许跳出所有循环(终止执行后面的所有循环)。 下面的例子中,脚本进入死循环直至用户输入数字大于5。要跳出这个循环,返回到shell提示符下,需要使用break命令。 代码如下: [root@qf-cloud ~]# cat ./break1.sh #!/usr/bin/env bash while true do echo -n "请输入数字1-5: " read aNum case $aNum in 1|2|3|4|5) echo "你的数字是$aNum" ;; *) echo "你的数字不是1-5,结束" break ;; #跳出while循环 esac done continue continue命令与break命令类似,只有一点差别,它不会跳出所有循环,仅仅跳出当前循环。 对上面的例子进行修改: 代码如下: [root@localhost ~]# cat continue1.sh #!/usr/bin/env bash while true do echo -n "请输入数字1-5: " read aNum case $aNum in 1|2|3|4|5) echo "你的数字是$aNum" ;; *) echo "你的数字不是1-5,结束" continue ;; esac done 运行代码发现,当输入大于5的数字时,该例中的循环不会结束. break和exit的区别 [root@qf-cloud ~]# cat case1.sh #!/bin/bash while true do read -p "请输入1|2|3: " num1 case $num1 in 1) echo $num1 ;; 2) while true do read -p "请再次输入1|2|3: " num2 case $num2 in 1) echo $num2 ;; 2) break ;; #跳出本次循环 3) exit ;; #直接退出脚本 esac done ;; 3) break ;; esac done
实战--shell版本jumpserver开发
堡垒机
pc-1
pc-2
pc-3
pc-4
首先要有思路, 完善不足的地方
#!/usr/bin/env bash # # Author: #可以先添加上账密验证环节 while : do trap ':' INT EXIT TSTP TERM HUP clear cat <<-EOF +-------------------------------------+ | JumpServer @Version1.0 | +-------------------------------------+ | a. WebServer Apache. | | b. MySQL Databases Server. | | c. PHP Development Computer. | | d. Quit | +-------------------------------------+ EOF read -p "Please input your jump to server's number: " computer case $computer in a) ssh jumper@192.168.161.129 #可以嵌套子case循环 ;; b) ssh jumper@192.168.161.130 ;; c) ssh jumper@192.168.161.131 ;; d) exit ;; *) printf "ERROR: Please redo your select!" ;; esac done
四、shell编程之函数
function (功能) 功能函数 完成特定功能的代码片段 函数必须先定义才能使用 优点:避免重复的代码 定义函数 调用函数 取消函数 函数传参 命名空间 local 返回值 return value value不能超过0-255
1、shell函数function
函数声明
function_name () { list of commands }
函数名 function_name,这就是你将使用它从其他地方在你的脚本调用。
取消函数
unset myfunc //取消函数
myfunc() //函数定义 { echo “This is my first shell function” } myfunc //函数调用
产生以下执行结果
./test.sh This is my first shell function 函数必须提前定义测试 [root@youngfit fun]# cat fun05.sh #!/bin/bash fun () { echo "hello" } fun unset fun fun [root@youngfit fun]# bash fun05.sh hello fun05.sh: line 8: fun: command not found 函数的返回值,返回的是函数体内最后一条命令是否成功的返回值 [root@youngfit fun]# systemctl stop httpd [root@youngfit fun]# cat fun03.sh #!/bin/bash fun() { systemctl status httpd &>/dev/null systemctl status vsftpd &>/dev/null } fun echo $? [root@youngfit fun]# systemctl stop vsftpd [root@youngfit fun]# bash fun03.sh 4
函数传参
在Shell中,调用函数时可以向其传递参数。在函数体内部,通过 n 的形式来获取参数的值,例如,1表示第一个参数,$2表示第二个参数
示例
[root@youngfit fun]# cat fun06.sh #!/bin/bash if [ ! $# -eq 3 ];then echo "Must Input Three number: " p1 p2 p3 exit fi fun() { echo $[$1*$2*$3] } fun 1 2 3 这个时候只是传参到了脚本,并没有传到函数里面 [root@youngfit fun]# bash fun06.sh 1 3 4 6 修改版: [root@youngfit fun]# cat fun06.sh #!/bin/bash if [ ! $# -eq 3 ];then echo "Must Input Three number: " p1 p2 p3 exit fi fun() { echo $[$1*$2*$3] } fun $1 $2 $3
五、shell编程之数组
普通数组:只能用整数作为数组的索引
关联数组:可以使用字符串作为数组的索引
数组定义
普通数组定义: [root@youngfit shell]# books=( linux shell awk sed ) #元数 引用: [root@youngfit shell]# echo ${books[0]} linux [root@youngfit shell]# echo ${books[1]} shell [root@youngfit shell]# echo ${books[2]} awk 关联数组需要提前声明 declare -A myarry1 [root@youngfit shell]# declare -A myarry1 [root@youngfit shell]# myarry1=([name]=youngfit [sex]=man [age]=26) [root@youngfit shell]# echo ${marry1[name]} [root@youngfit shell]# echo ${myarry1[name]} youngfit [root@youngfit shell]# echo ${myarry1[age]} 26
定义方法1: # declare -a myarry=(5 6 7 8) # echo ${myarry[2]} 显示结果为 7 定义方法2: # array=( one two three four five six ) # array2=(tom jack alice) # array3=(`cat /etc/passwd`) 将该文件中的每一个行作为一个元数赋值给数组array3 # array4=(tom jack alice "bash shell") # array5=(1 2 3 4 5 6 7 "linux shell" [20]=saltstack) 定义方法3: #!/bin/bash area[6]=23 area[13]=37 area[51]="UFOs"
访问数组
当您设置任何数组变量,并可访问它,如下所示:
[root@youngfit shell]# aa=(haha heihei baibai) [root@youngfit shell]# echo ${aa[0]} //访问数组中的第一个元数 [root@youngfit shell]# echo ${aa[@]} //访问数组中所有的元数 等同与echo ${aa[*]} [root@youngfit shell]# echo ${#aa[@]} //统计元数的个数 [root@youngfit shell]# echo ${!aa[@]} //统计索引
${array_name[index]} //引用
示例
#!/bin/bash NAME[0]="BJ" NAME[1]="SH" NAME[2]="SZ" NAME[3]="GZ" NAME[4]="HZ" NAME[5]="ZZ" echo "First Index: ${NAME[0]}" echo "Second Index: ${NAME[1]}" echo "sixth Index: ${NAME[5]}"
输出结果为
$./test.sh First Index: BJ Second Index: SH sixth Index: ZZ
您可以访问数组中的所有项目通过以下方式之一:
${array_name[*]} ${array_name[@]}
示例
#!/bin/sh NAME[0]="BJ" NAME[1]="SH" NAME[2]="SZ" NAME[3]="GZ" NAME[4]="HZ" echo "First Index: ${NAME[*]}" echo "Second Index: ${NAME[@]}"
输出结果
$./test.sh First Index: BJ SH SZ GZ HZ Second Index: BJ SH SZ GZ HZ
疑难点
shell数组中"*" 和 "@" 区别
关于在shell脚本中数组变量中 “*”跟 “@” 区别 “*”当变量加上“” 会当成一串字符串处理. “@”变量加上“” 依然当做数组处理. 在没有加上“” 的情况下 效果是等效的.