Shell脚本
程序
程序:算法+数据结构,数据是程序的核心
-
算法:处理数据的方式
-
数据结构:数据在计算机中的类型和组织方式
分类
按程序编程风格:
-
过程式:以指令为中心,数据服务于指令
-
对象式:以数据为中心,指令服务于数据
按程序运行方式:
-
编译运行:高级语言-->编译器-->机器代码-->执行
源代码需要编译器转换为程序文件,运行程序文件时不需要编译器的参与,因此程序执行效率高
比如:C,C++
-
解释运行:高级语言-->执行-->解释器-->机器代码
源代码不需要事先编译,运行时启动解释器而后由解释器边解释边运行,因此效率比较低
比如:shell,python,php,JavaScript,perl
按编程实现是调用库还是调用外部的程序文件:
-
非完整编程语言:利用系统上的命令及编程组件进行编程,shell脚本
-
完整的编程语言:利用库和编程组件进行编程,非shell脚本
按编程模型:
-
面向过程编程语言
以指令为中心来组织代码,以过程或函数为基础,数据服务于代码,围绕指令来组织数据;这种语言对底层硬件,内存等操作比较方便,但是写代码和调试维护等会很麻烦。 他们按照顺序执行,选择执行,循环执行 比如:C bash C++ python
-
面向对象的编程语言
以数据为中心来组织代码,以对象作为基本程序结构单位的程序设计语言,指令服务于数据,围绕数据来组织指令;指用于描述的设计是以对象为核心,而对象是程序运行时刻的基本成分。语言中提供了类、继承等成分。 对象:特定的数据类型 类class:实例化成为对象 比如:Java C++ python
综上所述可知:
shell脚本编程属于解释运行的过程式编程语言且依赖于外部程序文件来运行
编程基本结构
-
各种系统命令的组合
-
数据存储:变量、数组
-
表达式: a+b
-
语句: if
bash脚本
一种为shell编写的脚本程序;
是Linux命令的堆砌;
但由于Linux中很多命令不具有幂等性,需要设定程序逻辑来判断运行条件是否满足,以避免其运行中发生错误
幂等性
即一个操作,不论执行多少次,产生的效果和返回的结果都是一样的!
ShellScript作用
减少重复性的工作
-
自动化常用命令
-
执行系统管理和故障排除
-
创建简单的应用程序
-
处理文本或文件
-
自动化安装操作系统
-
kickstart 底层shell脚本
-
cobbler 底层shell脚本
-
-
初始化操作系统 SSH优化 关闭SElinux 防火墙放行需要的端口(80 443 22修改 10050) YUM源 时间同步 系统最大描述符 内核参数优化 字符集优化 禁止开机自动启动 修改主机名称 (修改公司网卡名称)... 手动操作要注意 命令行安全 bash的命令历史 写入shell脚本(常用)
-
安装服务 Nginx PHP MySQL Rsync等等... 针对不同的版本写入shell脚本自动安装
-
配置服务
-
启动服务 所有的服务底层的启动方式都是使用的shell脚本 公司自己研发的程序 nohup python3.5 test.py --redis --port --mysql --port -a xxxx & 复制一下 写入脚本 sh start_test_py.sh 如何停止py程序 ps axu|grep test.py |grep -v grep|awk '{print $2}'|xargs kill -9 复制一下 写入脚本 sh stop_test_py.sh 把py的进程的端口和PID取出来 来判断是否运行
-
日志统计 查看程序运行的情况 统计我们需要的数据 日志切割 定时任务+脚本 统计数据 定时任务+脚本 ---> 通过邮件发送给管理员 ELK 日志统计界面 py开发日志界面 py界面----> 数据库 <----数据 日志展示
-
监控 监控服务 服务端口是否存在 服务是否存在 服务器的硬件资源使用情况 状态 日志 网络 Zabbix 通过脚本统计---> 测试---> 添加到zabbix服务 (cacti监控流量 Nagios宽带运营商 IT公司)
ShellScript规范
脚本文件创建约定
-
脚本存放在固定的目录
/server/scripts
统一管理 -
脚本使用
.sh
结尾,让我们能识别是shell脚本 -
脚本命名,见名知其意
-
脚本内的注释最好不用中文(可以用)
-
脚本内的成对的符号一次性写完再写内容
脚本代码开头约定
-
默认解析器:
#!/usr/bin/env bash
会自己判断使用的shell是什么,并加载相应的环境变量 -
程序名,避免更改文件名为无法找到正确的文件
-
版本号
-
修改时间
-
作者相关信息
-
该程序的作用,及注意事项
-
最后是各版本的更新简要说明
#!/usr/bin/env bash # ------------------------------------------ # Filename: hello.sh # Revision: 1.0 # Date: 2020/10/22 # Author: liupenghui # Email: 15094034633@163.com # Description: This is the first script # Copyright: 2020 liu # License: GPL # ------------------------------------------ echo “hello world”
注释
# 主函数 []<-() <-------函数注释这样写 function main(){ local var="Hello World!!!" echo ${var} } # info级别的日志 []<-(msg:String) <-------带入参的函数注释 log_info(){ echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')][$$]: [info] $*" >&2 } # error级别的日志 []<-(msg:String) <-------带入参的函数注释 log_error(){ # todo [error]用红色显示 <------函数内注释 local msg=$1 # 将要输出的日志内容 <------变量的注释紧跟在变量的后面 if [[ x"${msg}" != x"" ]];then # 注释 <-------函数内注释 `#` 与缩进格式对整齐 echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')][$$]:[error] $*" >&2 fi }
缩进
-
使用两个空格进行缩进,不使用tab缩进
-
不在一行的时候使用
\
进行换行,使用\
换行的原则是整齐美观
#!/usr/bin/env bash # 脚本使用帮助文档 []<-() manual(){ cat "$0"|grep -v "less \"\$0\"" \ |grep -B1 "function " \ |grep -v "\\--" \ |sed "s/function //g" \ |sed "s/(){//g" \ |sed "s/#//g" \ |sed 'N;s/\n/ /' \ |column -t \ |awk '{print $1,$3,$2}' \ |column -t } function search_user_info(){ local result=$(httpclient_get --cookie "${cookie}" \ "${url}/userName=${user_name}") }
执行方式
-
使用解释器(sh或者bash)运行脚本,开启一个子shell运行脚本内容
-
执行脚本绝对路径或相对路径,需要脚本有执行权限
-
使用. 或者source运行脚本,在当前父shell中运行里面的内容
-
传递给
|bash
执行,不常用
可以给脚本加上执行权限
chmod +x /server/scripts/one.sh,
并将脚本的绝对路径添加到path变量中echo PATH=/server/scripts:$PATH > /etc/profile.d/shell.sh,
使变量立即生效. /etc/profile.d/shell.sh,就可以像运行普通命令一样直接执行脚本了!
父进程和子进程
[root@oldboyedu-lnb ~]# name=f;(echo $name;name=z;echo $name);echo $name # 小括号会开启子进程,赋予的变量,只在小括号内有效,执行完命令后,就会退出子进程。 f z f [root@oldboyedu-lnb ~]# name=f;{ echo $name;name=z;echo $name; };echo $name # 大括号不会开启子进程,在当前进程有效,执行完命令后,留在当前进程。 f z z
调试
# 语法检测 bash -n /path/to/script # 调试执行 bash -x /path/to/script
echo打印颜色字
echo -e "\033[31malong\033[0m" 显示红色along echo -e "\033[1;31malong\033[0m" 高亮显示红色along echo -e "\033[41malong\033[0m" 显示背景色为红色的along echo -e "\033[31;5malong\033[0m" 显示闪烁的红色along color=$[$[RANDOM%7]+31] echo -ne "\033[1;${color};5m*\033[0m" 显示闪烁的随机色along
ShellScript变量
命名法则
-
不能使程序中的保留字:例如if,for
-
只能使用数字、字母及下划线,且不能以数字开头
-
见名知义
-
统一命名规则:驼峰命名法
-
建议:
-
全局变量大写
-
局部变量小写
-
函数名小写
格式
-
变量赋值使用
=
等号,左右不能留有空格 -
使用变量时推荐使用
"${}"
双引号和大括号包裹
var1="Hello World" # 正确,推荐使用双引号 var2=6.70 # 小数 var3="${var1}" # 推荐 双引号和大括号 包裹
单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的,单引号字串中不能出现单引号(对单引号使用转义符后也不行)。 双引号中的普通字符都会原样输出,可以使用$引用变量,双引号中可以出现单引号。
-
常量一定要定义成readonly
-
函数中的变量要用local修饰,定义成局部变量,这样在外部遇到重名的变量也不会影响
web="www.chen-shang.github.io" function main(){ local name="chenshang" # 这里使用local定义一个局部变量 local web="${web}" # 这里${}内的web是全局变量,之后在函数中在使用web变量都是使用的局部变量 local web2="${web}" # 对于全局变量,虽然在使用的时候直接使用即可,但还是推荐使用一个局部变量进行接收,
然后使用局部变量,以防止在多线程操作的时候出现异常(相当于java中的静态变量在多线程中的时候需要注意线程安全一样,但常量除外) }
-
变量一经定义,不允许删除(也就是禁用unset命令)
类型
强类型:
变量不经过强制转换,它永远是这个数据类型,不允许隐式的类型转换。一般定义变量时必须指定类型、参与运算必须符合类型要求;调用未声明变量会产生错误 如:java , c# ,python
弱类型:
语言的运行时会隐式做数据类型转换。无须指定类型,默认均为字符型;参与运算会自动进行隐式类型转换;变量无须事先定义可直接调用 如:bash 不支持浮点数,php,javascript
shell中变量的基本类型就是String、数值(可以自己看做Int、Double之类的)、Boolean。
Boolean 其实是Int类型的变种, 在shell中0代表真、非0代表假,所以往往在shell脚本中用 readonly TURN=0 && readonly FALSE=1
。
根据变量的生效范围等标准划分下面变量类型:
局部变量:生效范围为当前shell进程;对当前shell之外的其它shell进程,包括当前shell的子shell进程均无效 环境变量:生效范围为当前shell进程及其子进程 本地变量:生效范围为当前shell进程中某代码片断,通常指函数 位置变量:$1, $2, ...来表示,用于让脚本在脚本代码中调用通过命令行传递给它的参数 特殊变量:$?, $0, $*, $@, $#,$$
局部变量
-
变量赋值:
name=‘value’
-
变量引用:
${name}
或者$name
(1) 可以是直接字串:name=“root" (2) 变量引用:name="$USER" (3) 命令引用:name=`COMMAND` name=$(COMMAND)
-
" "
弱引用,其中的变量引用会被替换为变量值 -
' '
强引用,其中的变量引用不会被替换为变量值,而保持原字符串 -
显示已定义的所有变量:
-
删除变量:
环境变量
变量声明、赋值: export name=VALUE declare -x name=VALUE 变量引用: $name, ${name} 显示所有环境变量: env print env export declare -x 删除变量:unset name bash内建的环境变量 PATH SHELL USER UID HOME PWD SHLVL LANG MAIL HOSTNAME HISTSIZE _ 下划线
只读变量
只能声明,但不能修改和删除
声明只读变量: readonly name declare -r name 查看只读变量: readonly -p
位置变量
在脚本代码中调用通过命令行传递给脚本的参数
$1, $2, ... 对应第1、第2等参数,shift [n]换位置,从$9以后需要加{}表示整体 set -- 清空所有位置变量
特殊变量
$0 脚本文件名称,如果全路径执行则带全路径,可以使用basename只获取名字 $# 传递给脚本的参数的个数 $* 传递给脚本的所有参数 $@ 传递给脚本的所有参数 "$*" 全部参数合为一个字符串,可在循环中验证 "$@" 每个参数为独立字符串,可在循环中验证 $$ 运行脚本的PID $! 上一个运行脚本的PID $_ 当前命令行的最后一个参数, 类似于ESC .
$$
和$BASHPID
区别:两者都是当前进程的编号,但是$BASHPID
更精确
basename
只输出路径的基名
[root@oldboyedu-lnb ~]# basename /etc/passwd passwd
进程使用退出状态来报告成功或失败
0 代表成功 1-255 代表失败 $? 保存上一条命令的退出状态
例如:
ping -c1 -W1 hostdown &> /dev/null echo $? exit [n] 自定义退出状态码
注意:
脚本中一旦遇到exit命令,脚本会立即终止;终止退出状态取决于exit命令后面的数字
如果未给脚本指定退出状态码,整个脚本的退出状态码取决于脚本中执行的最后一条命令的状态码
子串
[root@shell ~]# test='I am oldboy' [root@shell ~]# url='www.baidu.com' ${var:n:x}切片 [root@shell ~]# echo ${test:2:2} # (2,2+2]从第二个字符开始向后两位为止 am [root@shell ~]# echo $test|awk '{print $2}' am [root@shell ~]# echo $test|cut -c3-4
am ${#var}字符长度 [root@shell ~]# echo ${#test} 11 [root@shell ~]# echo $test|wc -L 11 [root@shell ~]# expr length "$test" 11 [root@shell ~]# echo $test|awk '{print length}' 11
统计出字符串小于3的单词
I am lzhenya teacher I am 18 [root@shell ~]# cat for.sh for i in I am lzhenya teacher I am 18 do [ ${#i} -lt 3 ] && echo $i done [root@shell ~]# sh for.sh I am I am 18 [root@shell ~]# echo I am lzhenya teacher I am 18|xargs -n1|awk '{if(length<3)print}' I am I am 18 [root@shell ~]# echo I am lzhenya teacher I am 18|awk '{for(i=1;i<=NF;i++)if(length($i)<3)print $i}' I am I am 18
删除匹配内容
支持通配符*
${var#}从前往后匹配,${var##}贪婪匹配 ${var%}从后往前匹配,${var%%}贪婪匹配
如果要匹配#
或%
,需使用\
转义
[root@shell ~]# echo ${url#www.} baidu.com [root@shell ~]# echo ${url#*.} baidu.com [root@shell ~]# echo ${url#*.*.} com [root@shell ~]# echo ${url##*.} com [root@shell ~]# echo ${url%.com} www.baidu [root@shell ~]# echo ${url%.*} www.baidu [root@shell ~]# echo ${url%.*.*} www [root@shell ~]# echo ${url%%.*} www
${var/a/b}替换匹配内容 ${var//a/b}贪婪匹配 [root@shell ~]# echo ${url/w/W} Www.baidu.com [root@shell ~]# echo ${url//w/W} WWW.baidu.com [root@shell ~]# echo ${url/baidu/sina} www.sina.com [root@shell ~]# echo $url|sed 's#www#WWW#g' WWW.baidu.com
ShellScript算术运算
+, -, *, /, %取模(取余), **(乘方)
,乘法符号有些场景中需要转义
-
整数计算使用
expr
或者$[]
或者$(())
(运算最快)或者let
-
小数计算使用
bc
计算器
-
实现算术运算:
(1) var=$(expr arg1 arg2 arg3 ...) (2) var=$[算术表达式] (3) var=$((算术表达式)) (4) let var=算术表达式 (5) declare –i var = 数值 (6) echo ‘算术表达式’ | bc
-
随机数
bash有内建的随机数生成器变量:$RANDOM(0-32767) 生成随机数 echo $RANDOM 生成指定范围随机数 示例:
# 生成随机7个数(0-6) echo $[RANDOM%7] # 生成随机7个数(31-37) echo $[$[RANDOM%7]+31]
生成随机字符:cat /dev/urandom # 生成8个随机大小写字母或数字
cat /dev/urandom |tr -dc [:alnum:] |head -c 8 tr -dc ‘a-zA-Z0-9’</dev/urandom|head -c8
-
增强型赋值:
+=, -=, *=, /=, %= let var OPER value 例如:let count+=3 自加3后自赋值 自增,自减: let var+=1 let var++ let var-=1 let var-- let var=i++ 是赋值后加 let var=++i 是先加后赋值
# 取1-63的余,其中 RANDOM%63 的值是0-62,加1就是1-63 echo $[RANDOM%63+1] # 生成随机颜色
echo -e "\033[1;$[RANDOM%7+31]m 字符串\033[0m"
-
逻辑运算
true, false
1, 0
与 &
1 与 1 = 1
1 与 0 = 0
0 与 1 = 0
0 与 0 = 0
或 |
1 或 1 = 1
1 或 0 = 1
0 或 1 = 1
0 或 0 = 0
非 !
! 1 = 0 ! true
! 0 = 1 ! false
短路与 &&
第一个为0,结果必定为0
第一个为1,第二个必须要参与运算
短路或 ||
第一个为1,结果必定为1
第一个为0,第二个必须要参与运算
异或:^ 异或的两个值,相同为假,不同为真
短路与和短路或 [ $RANDOM%6 –eq 0 ] && rm –rf /* || echo “click” # 数字互换 A=10;B=20;A=$[A^B];B=$[A^B];A=$[A^B];echo A=$A B=$B A=01010=10 B=10100=20 A=$[A^B]=11110=30 A=11110=30 B=10100=20=10 B=$[A^B]=01010 A=11110=30 B=01010=10 A=$[A^B]=10100=20 [root@oldboyedu-lnb ~]# A=10;B=20;A=$[A^B];B=$[A^B];A=$[A^B];echo A=$A B=$B A=20 B=10
ShellScript条件测试
非特别说明,则所有文件类操作都会追踪到软链接的源文件
test EXPRESSION [ EXPRESSION ] (( EXPRESSION )) 算术表达式 [[ EXPRESSION ]] 不会发生文件名扩展或者单词分割,会发生参数扩展和命令替换 注意:EXPRESSION 前后必须有空白字符
test -d "$HOME" ;echo $? [ "abc" != "def" ];echo $? test EXPRESSION && echo "exist" || echo "not exist" 更人性化地显示结果 test EXPRESSION && echo true || echo false 更人性化地显示结果 test -e file && echo "exist" || echo "not exist" test 3 -gt 4 && echo true || echo false
-
bash的数值测试
-v VAR 变量VAR是否设置 数值测试:
-gt 是否大于 -ge 是否大于等于 -eq 是否等于 -ne 是否不等于 -lt 是否小于 -le 是否小于等于
-
bash的字符串测试
= 是否等于 > ascii码是否大于ascii码 < 是否小于 != 是否不等于 =~ 左侧字符串是否能够被右侧的 正则表达式 所匹配
注意: 此表达式一般用于[[ ]]中,[[ ]]中匹配正则表达式或通配符,不需要引号 [[ hello == hell? ]] && echo true || echo false [[ 2\<3 ]] && echo true || echo false [[ 0 < 1 ]] && echo true || echo false [[ 2 -lt 3 ]] && echo true || echo false [ 2 \< 3 ] && echo true || echo false [ 1 = 1 ] && echo true || echo false -z "STRING“ 字符串是否为空,空为真,不空为假 -n "STRING“ 字符串是否不空,不空为真,空为假 注意:用于字符串比较时的用到的操作数都应该使用引号
-
bash的文件测试
存在性测试
-a FILE:同 -e -e FILE: 文件存在性测试,存在为真,否则为假 存在性及类别测试 -b FILE:是否存在且为块设备文件 -c FILE:是否存在且为字符设备文件 -d FILE:是否存在且为目录文件 -f FILE:是否存在且为普通文件 -h FILE 或 -L FILE:存在且为符号链接文件 -p FILE:是否存在且为命名管道文件 -S FILE:是否存在且为套接字文件
-
bash的文件权限测试
文件权限测试:
-r FILE:是否存在且可读 -w FILE: 是否存在且可写 -x FILE: 是否存在且可执行
文件特殊权限测试:
-u FILE:是否存在且拥有suid权限 -g FILE:是否存在且拥有sgid权限 -k FILE:是否存在且拥有sticky权限
-
bash的文件属性测试
文件大小测试: -s FILE: 是否存在且非空 文件是否打开:
-t fd: fd 文件描述符是否在某终端已经打开 -N FILE:文件自从上一次被读取之后是否被修改过 -O FILE:当前有效用户是否为文件属主 -G FILE:当前有效用户是否为文件属组
双目测试: FILE1 -ef FILE2: FILE1是否是FILE2的硬链接 FILE1 -nt FILE2: FILE1是否新于FILE2(mtime) FILE1 -ot FILE2: FILE1是否旧于FILE2
-
bash的组合测试条件
第一种方式:
EXPRESSION1 -a EXPRESSION2 并且,只能在test或[]中使用 EXPRESSION1 -o EXPRESSION2 或者,只能在test或[]中使用 ! EXPRESSION 非
第二种方式: COMMAND1 && COMMAND2 并且,短路与,代表条件性的AND THEN,只能在[[]]中使用 COMMAND1 || COMMAND2 或者,短路或,代表条件性的OR ELSE,只能在[[]]中使用 ! COMMAND 非 如:[ -f “$FILE” ] && [[ “$FILE”=~ .*\.sh$ ]]
-
条件性的执行操作符
示例:
grep -q no_such_user /etc/passwd || echo 'No such user' No such user ping -c1 -W2 station1 &> /dev/null \ > && echo "station1 is up" \ > || (echo 'station1 is unreachable'; exit 1) station1 is up test "$A" = "$B" && echo "Strings are equal" test “$A”-eq “$B” && echo "Integers are equal“ [ "$A" = "$B" ] && echo "Strings are equal" [ "$A" -eq "$B" ] && echo "Integers are equal“ [ -f /bin/cat -a -x /bin/cat ] && cat /etc/fstab # 判断字符串为空或者localhost.localdomain,则临时修改主机名为 www.magedu.com [ -z “$HOSTNAME” -o $HOSTNAME "=="localhost.localdomain" ] && hostname www.magedu.com
ShellScript相关命令
read
read 把输入值分配给一个或多个shell变量 -a 后跟一个变量,该变量会被认为是个数组,然后给其赋值,默认是以空格为分割符 -e 在输入的时候可以使用命令补全功能 -r 屏蔽\的转义功能 -u 后面跟fd,从文件描述符中读入,该文件描述符可以是exec新开启的 -p 指定输入前打印提示信息 -s 静默输入,输入的字符不在屏幕上显示,一般用于密码 -n N 指定输入的字符长度最大为N -d ‘字符’ 以输入的指定‘字符’作为结束符 -t N TIMEOUT为N秒,超时退出
read 从标准输入中读取值,给每个单词分配一个变量,所有剩余单词都被分配给最后一个变量
read -p “Enter a filename: “
FILE 示例: 修改主机名称为shell,并且修改eth0网卡IP地址为88
[root@shell ~]# cat hostname.sh #!/bin/bash eth0_cfg='/etc/sysconfig/network-scripts/ifcfg-eth0' old_ip=`ifconfig eth0|awk 'NR==2{print $2}'|awk -F. '{print $NF}'` read -p "please input hostname: " name read -p "please input New IP: " IP hostnamectl set-hostname $name sed -i "s#$old_ip#$IP#g" $eth0_cfg grep $IP $eth0_cfg [root@shell ~]# cat ping.sh read -p "Please Input URL: " url ping -c2 -W1 $url &>/dev/null [ $? -ne 0 ] && echo "ping不通" || echo "通了"
cat
cat <<EOF、cat <<-EOF的区别 用于执行脚本的时候,需要往一个文件里自动输入N行内容。 cat用于显示文本文件内容,全部输出。 EOF是END Of File的缩写,表示自定义终止符,Ctrl-D就代表EOF。 man说明: If the redirection operator is <<-, then all leading tab characters are stripped from input lines and the line containing delimiter. 翻译: 如果重定向的操作符是<<-,那么分界符(EOF)所在行的开头部分的制表符(Tab)都将被去除。 也就是说: cat <<EOF中EOF必须顶行写,前面不能用制表符或者空格。如果结束分解符EOF前有制表符或者空格,则EOF不会被当做结束分界符,只会继续被当做stdin来输入。 cat <<-EOF中就算最后的EOF前面有多个制表符和空格,但仍然会被当做结束分界符,表示stdin的结束。
trap
trap命令用于指定在接收到信号后将要采取的动作,常见的用途是在脚本程序被中断时完成清理工作。当shell接收到sigspec指定的信号时,arg参数(命令)将会被读取,并被执行,而不会执行原操作。
trap [-lp] [[arg] sigspec ...] -l 让shell打印一个命令名称和其相对应的编号的列表 -p 如果有-p选项而没有提供arg参数,则会打印所有与sigspec指定信号相关联的的trap命令; 如果没有提供任何参数或者仅有-p选项,trap命令将会打印与每一个信号有关联的命令的列表; [arg]参数缺省或者为“-”,每个接收到的sigspec信号都将会被重置为它们进入shell时的值 [arg]参数是空字符串每一个由sigspec指定的信号都会被shell和它所调用的命令忽略;
trap commands signals # commands 可以是任何有效的Linux命令,或一个用户定义的函数, # signals 可以是任意数量的信号,或你想来捕获的列表。 # 信号有3种表达方法:信号的数字2、全名SIGINT、缩写INT
参考实例:
trap "rm -f $WORKDIR/work1$ $WORKDIR/dataout$; exit" 1 2 收到指定信号后清理临时文件 trap '' 1 2 20 收到指定信号后忽略信号 trap '-' 1 2 20 trap 1 2 20 恢复信号的默认操作,重设陷阱
每个sigspec信号都是是以名字或者编号的形式定义在signal.h头文件中,信号的名字是不区分大小写的,其前缀SIG是可选的,有以下情况:
如果sigspec是EXIT(0),那么arg指定的命令将会在shell上执行退出命令时执行 如果sigspec是DEBUG,那么arg指定的命令将会在以下每个命令执行之前执行: 简单命令,for语句,case语句,select命令,算法命令,在函数内的第一条命令。 如果sigspec是ERR,那么arg指定的命令将会在任何简单命名执行完后返回值为非零值时执行,但是也有以下例外情况,arg命令不会执行,这些规则同样适用于errexit选项: 如果执行失败的命令是紧跟在while或者until关键字之后的一组命令中的一部分时 如果执行失败的命令是if测试语句的一部分时,是 && 和 ||连接的列表中的一部分时 如果执行失败的命令的返回值是被取反过的(通过!操作符) 如果sigspec是RETURN,那么arg指定的命令在每次shell函数或者脚本用"."或者内置的命令执行完成后执行
注意:
在shell入口处被忽略的命令是没法被trap和reset的。
被trap的信号,在创建的子进程中使用时会在子进程被创建时被重置为原始的值。
如果trap使用的sigspec信号是无效的信号,则trap命令返回false(失败),否则返回true(成功)。
① 打印0-9,ctrl+c不能终止
执行脚本后,打印0-9,每秒一个数字,ctrl+c转换为echo press ctrl+c
#!/bin/bash trap 'echo press ctrl+c' 2 for ((i=0;i<10;i++));do sleep 1 echo $i done
② 打印0-3,ctrl+c不能终止,3之后恢复,能终止
执行脚本后,打印0-3,每秒一个数字,ctrl+c不能终止,打印3之后解除捕获2信号,能终止
#!/bin/bash trap '' 2 trap -p for ((i=0;i<3;i++));do sleep 1 echo $i done trap '-' SIGINT for ((i=3;i<10;i++));do sleep 1 echo $i done
信号
信号是一种进程间通信机制,它给应用程序提供一种异步的软件中断,使应用程序有机会接受其他程序活终端发送的命令(即信号)。
应用程序收到信号后,有三种处理方式:忽略,默认,或捕捉。
进程收到一个信号后,会检查对该信号的处理机制:
-
如果是SIG_IGN,就忽略该信号;
-
如果是SIG_DFT,则会采用系统默认的处理动作,通常是终止进程或忽略该信号;
-
如果给该信号指定了一个处理函数(捕捉),则会中断当前进程正在执行的任务,转而去执行该信号的处理函数,返回后再继续执行被中断的任务。
在有些情况下,我们不希望自己的shell脚本在运行时刻被中断,比如说我们写得shell脚本设为某一用户的默认shell,使这一用户进入系统后只能作某一项工作,如数据库备份,我们不希望用户使用Ctrl+c之类能够进入到shell状态,做我们不希望做的事情。这便用到了信号处理。
常见信号:
1) SIGHUP: 无须关闭进程而让其重读配置文件
2) SIGINT: 中止正在运行的进程;相当于Ctrl+c
3) SIGQUIT: 相当于ctrl+\
9) SIGKILL: 强制杀死正在运行的进程;本信号不能被阻塞,处理和忽略。
15) SIGTERM :终止正在运行的进程(默认为15)
18) SIGCONT :继续运行
19) SIGSTOP :后台休眠
信号名称 | 信号数 | 描述 |
---|---|---|
SIGHUP | 1 | 本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联。登录Linux时,系统会分配给登录用户一个终端(Session)。在这个终端运行的所有程序,包括前台进程组和后台进程组,一般都属于这个Session。当用户退出Linux登录时,前台进程组和后台有对终端输出的进程将会收到SIGHUP信号。这个信号的默认操作为终止进程,因此前台进程组和后台有终端输出的进程就会中止。对于与终端脱离关系的守护进程,这个信号用于通知它重新读取配置文件。 |
SIGINT | 2 | 程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl+C)时发出。 |
SIGQUIT | 3 | 和SIGINT类似, 但由QUIT字符(通常是Ctrl+/)来控制。进程在因收到SIGQUIT退出时会产生core文件,在这个意义上类似于一个程序错误信号。 |
SIGFPE | 8 | 在发生致命的算术运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为0等其它所有的算术的错误。 |
SIGKILL | 9 | 用来立即结束程序的运行。本信号不能被阻塞,处理和忽略。 |
SIGALRM | 14 | 时钟定时信号,计算的是实际的时间或时钟时间。 alarm函数使用该信号。 |
SIGTERM | 15 | 程序结束(terminate)信号,与SIGKILL不同的是该信号可以被阻塞和处理,通常用来要求程序自己正常退出。shell命令kill缺省产生这个信号。 |
SIGHUP 1 /* Hangup (POSIX). */ 终止进程 终端线路挂断 SIGINT 2 /* Interrupt (ANSI). */ 终止进程 中断进程 Ctrl+C SIGQUIT 3 /* Quit (POSIX). */ 建立CORE文件终止进程,并且生成core文件 Ctrl+ SIGILL 4 /* Illegal instruction (ANSI). */ 建立CORE文件,非法指令 SIGTRAP 5 /* Trace trap (POSIX). */ 建立CORE文件,跟踪自陷 SIGABRT 6 /* Abort (ANSI). */ SIGIOT 6 /* IOT trap (4.2 BSD). */ 建立CORE文件,执行I/O自陷 SIGBUS 7 /* BUS error (4.2 BSD). */ 建立CORE文件,总线错误 SIGFPE 8 /* Floating-point exception (ANSI). */ 建立CORE文件,浮点异常 SIGKILL 9 /* Kill, unblockable (POSIX). */ 终止进程 杀死进程 SIGUSR1 10 /* User-defined signal 1 (POSIX). */ 终止进程 用户定义信号1 SIGSEGV 11 /* Segmentation violation (ANSI). */ 建立CORE文件,段非法错误 SIGUSR2 12 /* User-defined signal 2 (POSIX). */ 终止进程 用户定义信号2 SIGPIPE 13 /* Broken pipe (POSIX). */ 终止进程 向一个没有读进程的管道写数据 SIGALARM 14 /* Alarm clock (POSIX). */ 终止进程 计时器到时 SIGTERM 15 /* Termination (ANSI). */ 终止进程 软件终止信号 SIGSTKFLT 16 /* Stack fault. */ SIGCHLD 17 /* Child status has changed (POSIX). */ 忽略信号 当子进程停止或退出时通知父进程 SIGCONT 18 /* Continue (POSIX). */ 忽略信号 继续执行一个停止的进程 SIGSTOP 19 /* Stop, unblockable (POSIX). */ 停止进程 非终端来的停止信号 SIGTSTP 20 /* Keyboard stop (POSIX). */ 停止进程 终端来的停止信号 Ctrl+Z SIGTTIN 21 /* Background read from tty (POSIX). */ 停止进程 后台进程读终端 SIGTTOU 22 /* Background write to tty (POSIX). */ 停止进程 后台进程写终端 SIGURG 23 /* Urgent condition on socket (4.2 BSD).*/ 忽略信号 I/O紧急信号 SIGXCPU 24 /* CPU limit exceeded (4.2 BSD). */ 终止进程 CPU时限超时 SIGXFSZ 25 /* File size limit exceeded (4.2 BSD). */ 终止进程 文件长度过长 SIGVTALRM 26 /* Virtual alarm clock (4.2 BSD). */ 终止进程 虚拟计时器到时 SIGPROF 27 /* Profiling alarm clock (4.2 BSD). */ 终止进程 统计分布图用计时器到时 SIGWINCH 28 /* Window size change (4.3 BSD, Sun). */ 忽略信号 窗口大小发生变化 SIGIO 29 /* I/O now possible (4.2 BSD). */ 忽略信号 描述符上可以进行I/O SIGPWR 30 /* Power failure restart (System V). */ SIGSYS 31 /* Bad system call. */
expect
安装
yum install expect -y expect [选项] [ -c cmds ] [ [ -[f|b] ] cmdfile ] [ args ] -c:从命令行执行expect脚本,默认expect是交互地执行的 -d:可以输出输出调试信息 示例: expect -c 'expect "\n" {send "pressed enter\n"} expect -d ssh.exp
expect中相关命令 :
spawn:启动新的进程 send:用于向进程发送字符串 expect:从进程接收字符串 interact:允许用户交互,并停留在远程连接的主机上 exp_continue:匹配多个字符串在执行动作后加此命令
expect最常用的语法(tcl语言:模式-动作)
单一分支模式语法:
匹配到hi后,会输出“you said hi”,并换行
expect “hi” {send “You said hi\n"}
多分支模式语法:
匹配hi,hello,bye任意字符串时,执行相应输出。
expect "hi" { send "You said hi\n" } \ "hehe" { send "Hehe yourself\n" } \ "bye" { send "Good bye\n" }
等同如下:
expect { "hi" { send "You said hi\n"} "hehe" { send "Hehe yourself\n"} "bye" { send " Good bye\n"} }
示例
① 用户名密码自动登录系统
#!/usr/bin/expect set ip 192.168.7.100 set user root set password centos set timeout 10 # 登录 调用user和ip两个变量的值 spawn ssh $user@$ip expect { # 有发现yes/no 输入 yes\n "yes/no" { send "yes\n";exp_continue } # 有发现password输入$password的值 "password" { send "$password\n" } } # 允许用户交互 interact
② shell调用expect脚本
#!/bin/bash ip=$1 user=$2 password=$3 expect <<EOF # 开启expect命令多行重定向 set timeout 20 spawn ssh $user@$ip expect { "yes/no" { send "yes\n";exp_continue } "password" { send "$password\n" } } expect "]#" { send "useradd hehe\n" } expect "]#" { send "echo centos |passwd --stdin hehe\n" } expect "]#" { send "exit\n" } expect eof # 结束语 EOF
③ 多主机批量操纵:根据相同用户名和密码,批量创建用户
1、创建IP地址清单
[root@oldboyedu-lnb ~]# cat >> iplist.txt << EOF 192.168.7.101 192.168.7.102 192.168.7.103 EOF
2、通过while实现批量读取文件内容
#!/bin/bash while read ip;do user=root password=centos expect <<EOF set timeout 20 spawn ssh $user@$ip expect { "yes/no" { send "yes\n";exp_continue } "password" { send "$password\n" } } expect "]#" { send "useradd hehe\n" } # 远程ssh登录后创建用户名 expect "]#" { send "echo centos |passwd --stdin hehe\n" } # 设置密码 expect "]#" { send "exit\n" } expect eof EOF done < iplist.txt
④ 多主机批量操纵:根据不同用户名和密码传递公钥,实现免密钥登录
1、创建IP地址,密码清单
[root@oldboyedu-lnb ~]# cat >> iplist.txt << EOF 192.168.7.101 wangwang 192.168.7.102 centos 192.168.7.103 hahahaha EOF
2、通过while实现批量读取文件内容
#!/bin/bash ssh-keygen -t rsa -P "" -f /root/.ssh/id_rsa while read ip password;do user=root set timeout 10 expect << EOF spawn ssh-copy-id -i /root/.ssh/id_rsa.pub $user@$ip expect { "yes/no" { send "yes\n";exp_continue } "password" { send "$password\n" } } expect eof EOF done <iplist.txt
ShellScript逻辑语句
-
顺序执行
-
选择执行
-
循环执行
条件语句
if
选择执行:可嵌套
单分支 if 判断条件;then 条件为真的分支代码 fi 双分支 if 判断条件; then 条件为真的分支代码 else 条件为假的分支代码 fi 多分支 if 判断条件1; then 条件1为真的分支代码 elif 判断条件2; then 条件2为真的分支代码 elif 判断条件3; then 条件3为真的分支代码 else 以上条件都为假的分支代码 fi
逐条件进行判断,第一次遇为“真”条件时,执行其分支,而后结束整个if语句
Example:
根据命令的退出状态来执行命令
if ping -c1 -W2 station1 &> /dev/null; then echo 'Station1 is UP' elif grep "station1" ~/maintenance.txt &> /dev/null; then echo 'Station1 is undergoing maintenance' else echo 'Station1 is unexpectedly DOWN!' exit 1 fi
① 判断年纪
请输入年纪,先判断输入的是否含有除数字以外的字符,有,输出"please input a int";没有,继续判断是否小于150,是否大于18。
#!/bin/bash read -p "Please input your age: " age if [[ $age =~ [^0-9] ]] ;then echo "please input a int" exit 10 elif [ $age -ge 150 ];then echo "your age is wrong" exit 20 elif [ $age -gt 18 ];then echo "good good work,day day up" else echo "good good study,day day up" fi
② 判断分数
请输入成绩,先判断输入的是否含有除数字以外的字符,有,输出"please input a int";没有,继续判断是否大于100,是否大于85,是否大于60。
#!/bin/bash read -p "Please input your score: " score if [[ $score =~ [^0-9] ]] ;then echo "please input a int" exit 10 elif [ $score -gt 100 ];then echo "Your score is wrong" exit 20 elif [ $score -ge 85 ];then echo "Your score is very good" elif [ $score -ge 60 ];then echo "Your score is soso" else echo "You are loser" fi
case
case 变量引用 in PAT1) 分支1 ;; PAT2) 分支2 ;; *) 默认分支 ;; esac
case支持glob风格的通配符: *: 任意长度任意字符 ?: 任意单个字符 []: 指定范围内的任意单个字符 a|b: a或b
① 判断yes or no
请输入yes or no,回答Y/y、yes各种大小写组合为yes;回答N/n、No各种大小写组合为no。
#!/bin/bash read -p "Please input yes or no: " anw case $anw in [yY][eE][sS]|[yY]) echo yes ;; [nN][oO]|[nN]) echo no ;; *) echo false ;; esac
循环语句
for
for (( i = 0; i < 10; i++ )); do 循环体 done for item in 列表; do 循环体 done
执行机制:依次将列表中的元素赋值给“变量名”; 每次赋值后即执行一次循环体; 直到列表中的元素耗尽,循环结束
列表:
-
支持glob通配符,如:
{1..10}
、*.sh
; -
也可以引用变量
${array}
,如:seq 1 $1
;
① 求(1+2+...+n)的总和
sum初始值为0,请输入一个数,判断输入的值是否以1-9开头,后面跟任意个0-9的数字,不是,就报错;是,进入for循环,i的范围为1~输入的数,每次的循环为sum=sum+i,循环结束,最后输出sum的值。
#!/bin/bash sum=0 read -p "Please input a positive integer: " num if [[ ! $num =~ ^[1-9][0-9]* ]] ;then echo "input error" else for i in `seq 1 $num` ;do sum=$[$sum+$i] done echo $sum fi
while
while [[ 循环控制条件 ]]; do 循环体 done while read -r item ;do 循环体 done < 'file_name' cat 'file_name' | while read line; do 循环体 done
循环控制条件;进入循环之前,先做一次判断;每一次循环之后会再次做判断;条件为“true” ,则执行一次循环;直到条件测试状态为“false” 终止循环
遍历文件的每一行:依次读取file_name文件中的每一行,且将行赋值给变量line
① 100以内所有正奇数之和
sum初始值为0,i的初始值为1;当i<=100时,进入循环,判断 i÷2取余,不为0时为奇数,sum=sum+i,i+1;为0时,i+1;循环结束,最后输出sum的值。 #!/bin/bash sum=0 i=1 while [ $i -le 100 ] ;do if [ $[$i%2] -ne 0 ];then let sum+=i let i++ else let i++ fi done echo "sum is $sum"
② 菜单
#!/bin/bash cat << EOF 1)gongbaojiding 2)kaoya 3)fotiaoqiang 4)haishen 5)baoyu 6)quit EOF while read -p "please choose the number: " num;do case $num in 1) echo "gongbaojiding price is 30" ;; 2) echo "kaoya price price is 80" ;; 3) echo "fotiaoqiang price is 200" ;; 4) echo "haishen price is \$20" ;; 5) echo "baoyu price is \$10" ;; 6) break ;; *) echo "please input again" esac done
③ 统计日志访问IP情况
#!/bin/bash # 其中access_log为访问日志,统计访问IP和次数,导出到文件iplist.txt中 sed -rn 's/^([^[:space:]]+).*/\1/p' access_log |sort |uniq -c > iplist.txt # while read 逐行处理 while read count ip;do if [ $count -gt 100 ];then # 将统计后的日志导出到文件crack.log中 echo from $ip access $count >> crack.log fi # while read 支持重定向,可以将要统计的文件导入到循环中 done < iplist.txt
④ 统计磁盘使用率大于指定值的信息
#!/bin/bash # 定义报警的磁盘使用率 WARNING=10 df | awk '{if($5>$WARNING)print $0}' #!/bin/bash # 定义报警的磁盘使用率 WARNING=10 df |sed -rn '/^\/dev\/sd/s#^([^[:space:]]+).* ([[:digit:]]+)%.*$#\1 \2#p' | while read part use; do if [ $use -gt $WARNING ]; then echo $part will be full,use:$use fi done
#!/bin/bash # 定义报警的磁盘使用率 WARNING=10 df |awk -F"[[:space:]]+|%" '/dev\/sd/{print $1,$(NF-2)}' > disk.txt while read part use; do if [ $use -gt $WARNING ]; then echo $part will be full,use:$use fi done < disk.txt
until
until [[ 循环控制条件 ]]; do 循环体 done 进入条件:循环条件为false ; 退出条件:循环条件为true; 刚好和while相反,所以不常用,用while就行。
① 监控test用户,登录就杀死
#!/bin/bash # 发现test用户登录,条件为true,退出循环 until pgrep -u test &> /dev/null ;do # 每隔0.5秒扫描 sleep 0.5 done # 杀死test用户相关进程 pkill -9 -u test
select
select variable in list do 循环体 done
① select 循环主要用于创建菜单,按数字顺序排列的示菜单项将显示在标准错误上,并显示PS3提示符,等待用户输入
② 用户输入菜单列表中的某个数字,执行相应的命令
③ 用户输入被保存在内置变量 REPLY 中
④ select 是个无限循环,因此要记住用 break 命令退出循环,或用 exit 命令终止脚本。也可以按 ctrl+c退出循环
⑤ select 经常和 case 联合使用
⑥ 与for循环类似,可以省略 in list, 此时使用位置参量
示例: 生成菜单,并显示选中的价钱
#!/bin/bash PS3="Please choose the menu: " select menu in mifan huimian jiaozi babaozhou quit do case $REPLY in 1|4) echo "the price is 15" ;; 2|3) echo "the price is 20" ;; 5) break ;; *) echo "no the option" esac done
注意:PS3是select的提示符,自动生成菜单,选择5退出循环。
循环控制语句
continue [N]:提前结束第N层的本轮循环,而直接进入下一轮判断;最内层为第1层
break [N]:提前结束第N层循环,最内侧为第1层
例:
while CONDTITON1; do CMD1 if CONDITION2; then continue / break fi CMD2 done
(2)案例:
① 求(1+3+...+49+53+...+100)的和
#!/bin/bash sum=0 for i in {1..100} ;do [ $i -eq 51 ] && continue [ $[$i%2] -eq 1 ] && { let sum+=i;let i++; } done echo sum=$sum
分析:做1+3+...+99的循环,当i=51时,跳过这次循环,但是继续整个循环,结果为:sum=2449
② 求(1+3+...+49)的和
#!/bin/bash sum=0 for i in {1..100} ;do [ $i -eq 51 ] && break [ $[$i%2] -eq 1 ] && { let sum+=i;let i++; } done echo sum=$sum
分析:做1+3+...+99的循环,当i=51时,跳出整个循环,结果为:sum=625
shift
shift n 用于将参数列表list左移指定次数,最左端的那个参数就从列表中删除,其后边的参数继续进入循环,n是整数 1.依次读取输入的参数并打印参数个数:
$ cat run.sh #!/bin/bash while [ $# != 0 ];do echo "第一个参数为:$1,参数个数为:$#" shift done $ sh run.sh a b c d e f 第一个参数为:a,参数个数为:6 第一个参数为:b,参数个数为:5 第一个参数为:c,参数个数为:4 第一个参数为:d,参数个数为:3 第一个参数为:e,参数个数为:2 第一个参数为:f,参数个数为:1
2.把参数进行左移3个:
$ cat t.sh #!/bin/bash echo -e "./t.sh arg1 arg2 arg3 arg4 arg5 arg6" str1="${1},${2},${3}" echo "str1=$str1" shift 3 str2=$@ echo "str2=$str2" $ sh t.sh 1 2 3 4 5 6 7 str1=1,2,3 3.将参数从左到右逐个移动: $ cat shift.sh #!/bin/bash while [ $# -ne 0 ] do echo "第一个参数为: $1 参数个数为: $#" shift done $ sh shift.sh Lily Lucy Jake Mike 第一个参数为: Lily 参数个数为: 4 第一个参数为: Lucy 参数个数为: 3 第一个参数为: Jake 参数个数为: 2 第一个参数为: Mike 参数个数为: 1
① 创建指定的多个用户
#!/bin/bash if [ $# -eq 0 ] ;then echo "Please input a arg(eg:`basename $0` arg1)" exit 1 else while [ -n "$1" ];do useradd $1 &> /dev/null echo "User:$1 is created" shift done fi
分析:如果没有输入参数(参数的总数为0),提示错误并退出;反之,进入循环;若第一个参数不为空字符,则创建以第一个参数为名的用户,并移除第一个参数,将紧跟的参数左移作为第一个参数,直到没有第一个参数,退出。
② 打印直角三角形的字符
#!/bin/bash while (( $# > 0 )) do echo "$*" shift done [root@oldboyedu-lnb ~]# sh trian.sh 1 2 3 1 2 3 2 3 3
Boolean
true
永远成功
false
永远错误
无限循环
while true ;do 循环体 done # 或者 until false ;do 循环体 done
并行循环
每次循环将循环体放入后台执行. 继续下一次循环. 最后等待所有线程执行完毕再退出脚本
for name in 列表 ;do { 循环体 }& done wait
① 搜寻指定ip(子网掩码为24的)的网段中,UP的ip地址
read -p "Please input network (eg:192.168.0.0): " net echo $net |egrep -o "\<(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\>" [ $? -eq 0 ] || ( echo "input error";exit 10 ) IP=`echo $net |egrep -o "^([0-9]{1,3}\.){3}"` for i in {1..254};do { ping -c 1 -w 1 $IP$i &> /dev/null && \ echo "$IP$i is up" }& done wait
分析:请输入一个IP地址,例如192.168.37.234,
如果格式不是0.0.0.0 则报错退出;
正确则进入循环,IP变量的值为192.168.37. i的范围为1-254,并行ping 192.168.37.1-154,ping通就输出此IP为UP。
函数递归示例
函数递归: 函数直接或间接调用自身 注意递归层数 递归实例: 阶乘是基斯顿·卡曼于 1808 年发明的运算符号,是数学术语,一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且有0的阶乘为1,自然数n的阶乘写作n!
n!=1×2×3×...×n 阶乘亦可以递归方式定义:0!=1,n!=(n-1)!×n n!=n(n-1)(n-2)...1 n(n-1)! = n(n-1)(n-2)!
函数递归示例 示例:
fact.sh #!/bin/bash fact() { if [ $1 -eq 0 -o $1 -eq 1 ]; then echo 1 else echo $[$1*$(fact $[$1-1])] fi } fact $1
展开命令行
-
把命令行分成单个命令词
-
展开别名
-
展开大括号的声明({})
-
展开波浪符声明(~)
-
命令替换$()和``
-
再次把命令行分成命令词
-
展开文件通配(*、?、[abc]等等)
-
准备I/0重导向(<、>)
-
运行命令
防止扩展
-
反斜线
\
会使随后的一个字符按原意解释 -
单引号
'
防止所有扩展 -
双引号
"
除了以下情况,防止所有扩展:
$ (美元符号) 变量扩展(注意:"$" 输出 $,仍有特殊含义) ` (反引号) 命令替换 \ (反斜线) 禁止单个字符扩展 ! (叹号) 历史命令替换
分支
HEAD_KEYWORD parameters; BODY_BEGIN BODY_COMMANDS BODY_END
-
将HEAD_KEYWORD和初始化命令或者参数放在第一行;
-
将BODY_BEGIN同样放在第一行;
-
复合命令中的BODY_COMMANDS部分以2个空格缩进;
-
BODY_END部分独立一行放在最后;
if if [[ condition ]]; then # statements fi if [[ condition ]]; then # statements else # statements fi if [[ condition ]]; then # statements elif [[ condition ]]; then # statements else # statements fi
if 后面的判断 使用 双中括号[[]] if [[ condition ]]; then 写在一行
-
while
while [[ condition ]]; do # statements done while read -r item ;do # statements done < 'file_name'
-
until
until [[ condition ]]; do # statements done
-
for
for (( i = 0; i < 10; i++ )); do # statements done for item in ${array}; do # statements done
-
case
case $var in pattern ) #statements ;; *) #statements ;; esac
ShellScript函数
function用法
-
函数function是由若干条shell命令组成的语句块,实现代码重用和模块化编程。
-
它与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运 行,而是shell程序的一部分,定义函数只对当前的会话窗口有效,如果再打开一个窗口再定义另外一个函数,就对另一个窗口有效,两者互不影响。
-
函数和shell程序比较相似,区别在于以下两种:
(1)Shell程序在子Shell中运行。
(2)而Shell函数在当前Shell中运行。因此在当前Shell中,函数可以对shell中变量进行修改。
定义函数
function main(){ #函数执行的操作 #函数的返回结果 } 或 main(){ #函数执行的操作 #函数的返回结果 } 或 function main { #函数执行的操作 #函数的返回结果 }
-
使用关键字
function
显示定义的函数为 public 的函数,可以供外部脚本以sh 脚本 函数 函数入参
的形式调用 -
未使用关键字
function
显示定义的函数为 privat 的函数, 仅供本脚本内部调用,注意这种privat是人为规定的,并不是shell的语法,不推荐以sh 脚本 函数 函数入参
的形式调用,注意是不推荐而不是不能。
本shell规约这样做的目的就在于使脚本具有一定的封装性,看到
function
修饰的就知道这个函数能被外部调用, 没有被修饰的函数就仅供内部调用。你就知道如果你修改了改函数的影响范围. 如果是被function修饰的函数, 修改后可能影响到外部调用他的脚本, 而修改未被function修饰的函数的时候,仅仅影响本文件中其他函数。
如 core.sh 脚本内容如下是
# 重新设置DNS地址 []<-() function set_name_server(){ > /etc/resolv.conf echo nameserver 114.114.114.114 >> /etc/resolv.conf echo nameserver 8.8.8.8 >> /etc/resolv.conf cat /etc/resolv.conf } # info级别的日志 []<-(msg:String) log_info(){ echo -e "[$(date +'%Y-%m-%dT%H:%M:%S%z')][$$]: \033[32m [info] \033[0m $*" >&2 } # error级别的日志 []<-(msg:String) log_error(){ # todo [error]用红色显示 echo -e "[$(date +'%Y-%m-%dT%H:%M:%S%z')][$$]: \033[31m [error] \033[0m $*" >&2 }
则我可以使用
sh core.sh
set_name_server
的形式调用 set_name_server
函数,但就不推荐使用 sh core.sh log_info "Hello World"
的形式使用 log_info
和 log_error
函数,注意是不推荐不是不能。
(1)可在交互式环境下定义函数
(2)可将函数放在脚本文件中作为它的一部分
#!/bin/bash # 定义function func_os_version,在大括号里边定义命令,取出操作系统的版本号,类似于定义别名一样 func_os_version () { sed -nr 's/.* ([0-9]+)\..*/\1/p' /etc/redhat-release } # 直接写上函数名,或者用echo加反引号输出结果 echo OS version is `func_os_version`
如果命令过多,不太方便
(3)可放在只包含函数的单独文件中
-
然后将函数文件载入shell
-
文件名可任意选取,但最好与相关任务有某种联系。例如:functions.main
-
一旦函数文件载入shell,就可以在命令行或脚本中调用函数。可以使用
set
查看所有定义的函数,其输出列表包括已经载入shell的所有函数 -
若要改动函数,首先用
unset function_name
从shell中删除函数。改动完毕后,再重新载入此文件
# 将定义的函数放到functions文件中
[root@centos-7 ~]# cat functions func_os_version () { sed -nr 's/.* ([0-9]+)\..*/\1/p' /etc/redhat-release } [root@centos-7 ~]# cat osversion.sh #!/bin/bash # 在当前脚本加载 functions source functions # 调用函数 func_os_version
# 对脚本osversion.sh加上执行权限 [root@centos-7 ~]# chmod +x osversion.sh # 运行脚本 [root@centos-7 ~]# ./osversion.sh 7
可以使用declare -F
查看所有定义的函数
定义环境函数
使子进程也可使用
(1)声明:export -f function_name (2)查看:export -f 或 declare -xf
调用函数
调用:给定函数名
函数名出现的地方,会被自动替换为函数代码
函数的生命周期:被调用时创建,返回时终止
使用脚本单独调用函数中的某个函数
#!/usr/bin/env bash # shellcheck disable=SC1091,SC2155 readonly local TRUE=0 && readonly local FALSE=1 # 脚本使用帮助文档 manual(){ cat "$0"|grep -v "less \"\$0\"" \ |grep -B1 "function " \ |grep -v "\\--" \ |sed "s/function //g" \ |sed "s/(){//g" \ |sed "s/#//g" \ |sed 'N;s/\n/ /' \ |column -t \ |awk '{print $1,$3,$2}' \ |column -t } ###################################################################### # 主函数 main(){ (manual) } ###################################################################### # 执行函数 [Any]<-(function_name:String,function_parameters:List<Any>) execute(){ function_name=$1 shift # 参数列表以空格为分割左移一位,相当于丢弃掉第一个参数 function_parameters=$* (${function_name} "${function_parameters}") } case $1 in "-h" | "--help" | "?") (manual);; "") (main) ;; *) (execute "$@") ;; esac
使用如上的框架,只需要在 两个 ######################################################################
之间写函数,就可以使用 sh 脚本名称
脚本中的某个函数 脚本中的某个函数的入参
的形式调用函数了。 使用 sh 脚本名称 ?
或者 sh 脚本名称 -h/--help
就可以查看这个脚本中的函数说明了。
-
在函数内部首先使用有意义的变量名接受参数,然后在使用这些变量进行操作,禁止直接操作
$1
,$2
等,除非这些变量只用一次 -
函数的注释 函数类型的概念是从函数编程语言中的概念偷过来的,shell函数的函数类型指的是函数的输入到函数的输入的映射关系
# 主函数 []<-() <-------函数注释这样写 function main(){ local var="Hello World!!!" echo ${var} } # info级别的日志 []<-(msg:String) <-------带入参的函数注释 log_info(){ echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')][$$]: [info] $*" >&2 }
main函数的函数类型是 []<-() , <- 左侧表的是函数的返回值类型用[]包裹, 右侧是函数的参数类型用()包裹,多个参数用 ',' 分隔, 参数的描述是从 Scala 语言中偷过来, 先是参数名称, 然后是参数类型, 中间用:分隔 对于main函数的注释来说, # 顶格写,后面紧跟一个空格,其实这样写是遵循的markdown的语法, 后面再跟一个空格,然后是 []<-(),代表这个函数没有入参也没有返回值,这个函数的目的就是执行这个这个函数中的命令,但我不关心这个函数的返回值。也就是利用函数的副作用来完成我们想要的操作。 对于log_info也是一样不过最后的函数类型是 []<-(msg:String) 代表入参是一个string类型的信息,然后也没有返回值。 关于函数的返回值,我理解的函数的返回值有两种形式,一种是显示的return一种是隐式的echo
以下是几种常见的写法
[]<-() [String]<-(var1:String,var2:String) [Boolean]<-(var1:String,var2:Int) []<-(var1:String)
返回值
1、函数的执行结果返回值:
(1) 使用echo等命令进行输出
(2) 函数体中调用命令的输出结果
2、函数的退出状态码:
(1) 默认取决于函数中执行的最后一条命令的退出状态码
(2) 自定义退出状态码,其格式为:
return 从函数中返回,用最后状态命令决定返回值:
(1)return 0 无错误返回。
(2)return 1-255 有错误返回
执行一条命令的时候, 比如 pwd 正常情况下它输出的结果是 当前所处的目录
$ pwd /Users/chenshang
shell中必然有一种状态来标识一条命令是否执行成功,也就是命令执行结果的状态。
0代表真、成功的含义。
非零代表假、失败的含义。
所以 pwd 这条命令如果执行成功的话,命令的执行结果状态一定是0,然后返回值才是当前目录。如果这条命令执行失败的话,命令的执行结果状态一定不是0,有可能是1 代表命令不存在,然后输出 not found,也有可能执行结果状态是2代表超时,然后什么也不输出。那怎么获取这个命令的执行结果和执行结果的状态呢?
function main(){ pwd }
执行main函数就会在控制台输出当前目录 如果想要将pwd的内容获取到变量中以供后续使用呢
function main(){ local dir=$(pwd) echo "dir is ${dir}" }
如果想要获取pwd的执行结果的状态呢
function main(){ local dir=$(pwd) local status=$? echo "pwd run status is ${status}" #这个stauts一定有值,且是int类型,取值范围在0-255之间 echo "dir is ${dir}" }
显示return
return 用来显示的返回函数的返回结果,例如
# 检查当前系统版本 [Integer]<-() function check_version(){ (log_info "check_version ...") # log_info是我写的工具类中的一个函数 local version # 这里是先定义变量,在对变量进行赋值,我们往往是直接初始化,而不是像这样先定义在赋值,这里只是告诉大家可以这么用 version=$(sed -r 's/.* ([0-9]+)\..*/\1/' /etc/redhat-release) (log_info "centos version is ${version}") return "${version}" }
这样这个函数的返回值是一个数值类型,我在脚本的任何地方调用check_version这个函数后,使用 $? 获取返回值
check_version local version=$? echo "${version}"
注意这里不用 local version=$(check_version) 这种形式获取结果,这样也是获取不到结果的,因为显示的return结果,
返回值只能是[0-255]的数值,这对于我们一般的函数来说就足够了,
因为我们使用显示return的时候往往是知道返回结果一定是数字且在[0-255]之间的,常常用在状态判断的时候。
本shell规约规定:
-
明确返回结果是在[0-255]之间的数值类型的时候使用显示 reuturn 返回结果
-
返回结果类型是Boolean类型,也就是说函数的功能是起判断作用,返回结果是真或者假的时候使用显示 return 返回结果
# 检查网络
[Boolean]<-()
function check_network(){ (log_info "check_network ...") for((i=1;i<=3;i++));do http_code=$(curl -I -m 10 -o /dev/null -s -w %\{http_code\} www.baidu.com) if [[ ${http_code} -eq 200 ]];then (log_info "network is ok") return ${TRUE} fi done (log_error "network is not ok") return ${FALSE} } # 获取数组中指定元素的下标 [int]<-(TABLE:Array,target:String) function get_index_of(){ readonly local array=($1) local target=$2 local index=-1 # -1其实是255 local size=${#array[@]} for ((i=0;i<${size};i++));do if [[ ${array[i]} == ${target} ]];then return ${i} fi done return ${index} }
隐式echo
return 用来显示的返回函数的返回结果,例如
# 将json字符串格式化树形结构
[String]<-(json_string:String) function json_format(){ local json_string=$1 echo "${json_string}"|jq . #jq是shell中处理json的一个工具 }
函数中所有的echo照理都应该输出到控制台上 例如
json_format "{\"1\":\"one\"}"
你会在控制台上看到如下输出
{ "1": "one" }
但是一旦你用变量接收函数的返回值,这些本该输出到控制台的结果就都会存储到你定义的变量中 例如
json=$(json_format "{\"1\":\"one\"}") echo "${json}" # 如果没有这句,上面的语句执行完成后,不会在控制台有任何的输出
我们把 json_format 改造一下
# 将json字符串格式化树形结构 [String]<-(json_string:String)
function json_format(){ local json_string=$1 echo "为格式化之前:${json_string}" # 其实新添加的只一句只是用来记录一行日志的 echo "${json_string}"|jq . # jq是shell中处理json的一个工具 }
echo "为格式化之前:${json_string}" 其实新添加的只一句只是用来记录一行日志的,
但是json=$(json_format "{"1":"one"}") json 也会将这句话作为返回结果进行接收,但这是我不想要看到的。
子shell可以捕获父shell的变量,但不能改变父shell的变量,使用()将代码块包裹,包裹的代码块将在子shell中运行,子shell相当于独立的一个环境,不会影响到父shell的结果
所以如果我不想让 echo "为格式化之前:${json_string}" 这句话也作为结果的话,我就只需要用()将代码块包裹即可
# 将json字符串格式化树形结构 [String]<-(json_string:String)
function json_format(){ local json_string=$1 (echo "为格式化之前:${json_string}") # 其实新添加的只一句只是用来记录一行日志的 echo "${json_string}"|jq . # jq是shell中处理json的一个工具 }
示例
① 对不同的成绩分段进行判断
[root@oldboyedu-lnb ~]# cat functions func_is_digit(){ # 判断参数$1不是空,就为真,取反,空为真 if [ ! "$1" ];then # 请输入数字 echo "Usage:func_is_digit number" return 10 # 如果输入是数字,返回0 elif [[ $1 =~ ^[[:digit:]]+$ ]];then return 0 else # 否则提醒不是数字 echo "Not a digit" return 1 fi } [root@oldboyedu-lnb ~]# cat score.sh #!/bin/bash source /data/functions read -p "Input your score:" SCORE func_is_digit $SCORE #判断上面的命令执行结果不等于0就退出 if [ $? -ne 0 ];then exit else # 如果成功了,对成绩的三种判断如下。 if [ $SCORE -lt 60 ];then echo "You are loser" elif [ $SCORE -lt 80 ];then echo "soso" else echo "very good" fi fi
② function配合case:代码发布与回滚与检验
#!/bin/bash # Author: liupengju # date: 2020-06-22 # TEL: xxxxxxxxxx # 代码发布与回滚 set -e set -u # adx代码部署 定义变量 ADX_DIR=/gnome/adx adx_new_version="gnome-adx-0.0.1-SNAPSHOT-jar-with-dependencies.jar" ADX_NEW_MD5=`md5sum $adx_new_version | awk '{ print $1 }'` # adx代码部署 cf平台的md5码 ADX_CHK_MD5="43bcfe7594f083a8653126e0896b93ac" # directAd代码部署 定义变量 direct_DIR=/gnome/directAd/ direct_version="direct-ad-0.0.1-SNAPSHOT-jar-with-dependencies.jar" direct_MD5=`md5sum $direct_version | awk '{ print $1 }'` #direct_old_version=$(ls -l |tail -n1 | awk '{print $9}') # directAd代码部署 cf平台的md5码 direct_CHK_MD5="03c3c2fc62b2edfc92e548351010ee9f" ##########部署directAd代码############################# fun_copy_direct_code(){ mv $direct_DIR/$direct_version $direct_DIR/bak/${direct_version}_$(date +"%F-%T") echo "-----上一个版本已经移动到备份目录" cp /data/$direct_version $direct_DIR && echo "-----代码复制成功!!!" } fun_chk_direct_code(){ if [[ "$direct_MD5" == "$direct_CHK_MD5" ]];then echo "-----代码校验成功" && echo "代码部署成功后MD5值为:$direct_MD5" else echo "-----代码校验失败" && exit fi } fun_deploy_direct_restart(){ #$direct_DIR/restart.sh systemctl restart httpd systemctl restart nginx echo "后端服务重启成功!!!" } # 验证端口存活状态 fun_chk_direct_port1(){ PORT1=`ss -nlt|grep 8080 |awk -F"[[:space:]]+|:" '{ print $7}'` PORT2=`ss -nlt|grep 8182 |awk -F"[[:space:]]+|:" '{ print $7}'` for port in $PORT1 $PORT2;do echo "The port is:$port------监听端口正常" done } #############回滚directAd代码################################### fun_rollback_direct_code(){ cd $direct_DIR/bak # 提取上一个版本的jar包 direct_old_version=$(ls -l |tail -n1 | awk '{print $9}') mv $direct_DIR/${direct_version} $direct_DIR/bak/${direct_version}_$(date +"%F-%T") mv $direct_DIR/bak/${direct_old_version} $direct_DIR/${direct_version} echo "------旧版本代码移动成功" direct_old_MD5=$(md5sum $direct_DIR/${direct_version} | awk '{print $1}') echo "代码回滚后MD5值为:$direct_old_MD5" } fun_rollback_direct_restart(){ #$direct_DIR/restart.sh systemctl restart httpd systemctl restart nginx echo "--------后端服务重启成功" } # 验证端口存活状态 fun_chk_direct_port2(){ PORT1=`ss -nlt|grep 8080 |awk -F"[[:space:]]+|:" '{ print $7}'` PORT2=`ss -nlt|grep 8182 |awk -F"[[:space:]]+|:" '{ print $7}'` for port in $PORT1 $PORT2;do echo "The port is:$port------端口监听正常" done } #####################adx代码部署######################################## fun_copy_adx__code(){ mv $ADX_DIR/$adx_new_version $ADX_DIR/bak/${adx_new_version}_$(date +"%F-%T") echo "-----上一个版本已经移动到备份目录" cp /data/$adx_new_version $ADX_DIR && echo "-----代码复制成功!!!" } fun_chk_adx_code(){ if [[ "$ADX_NEW_MD5" == "$ADX_CHK_MD5" ]];then echo "-----代码校验成功" && echo "代码部署成功后MD5值为:$ADX_NEW_MD5" else echo "-----代码校验失败" && exit fi } fun_deploy_adx_restart(){ #$ADX_DIR/restart.sh systemctl restart httpd systemctl restart nginx echo "后端服务已经启动!!!" } # 验证端口存活状态 fun_chk_adx_port1(){ PORT1=`ss -nlt|grep 8080 |awk -F"[[:space:]]+|:" '{ print $7}'` PORT2=`ss -nlt|grep 8182 |awk -F"[[:space:]]+|:" '{ print $7}'` for port in $PORT1 $PORT2;do echo "The port is:$port------监听的端口正常启动" done } ###################################adx代码回滚########################### fun_rollback_adx_code(){ cd $ADX_DIR/bak adx_old_version=$(ls -l |tail -n1 | awk '{print $9}') mv $ADX_DIR/${adx_new_version} $ADX_DIR/bak/${adx_new_version}_$(date +"%F-%T") mv $ADX_DIR/bak/${adx_old_version} $ADX_DIR/${adx_new_version} echo "------旧版本代码移动成功" adx_old_MD5=$(md5sum $ADX_DIR/${adx_new_version} | awk '{print $1}') echo "代码回滚后MD5值为:$adx_old_MD5" } fun_rollback_adx_restart(){ #$ADX_DIR/restart.sh systemctl restart httpd systemctl restart nginx echo "--------后端服务已经启动" } # 验证端口存活状态 fun_chk_adx_port2(){ PORT1=`ss -nlt|grep 8080 |awk -F"[[:space:]]+|:" '{ print $7}'` PORT2=`ss -nlt|grep 8182 |awk -F"[[:space:]]+|:" '{ print $7}'` for port in $PORT1 $PORT2;do echo "The port is:$port-------端口监听正常" done } case $1 in direct_deploy) fun_copy_direct_code fun_chk_direct_code fun_deploy_direct_restart fun_chk_direct_port1 ;; direct_rollback) fun_rollback_direct_code fun_rollback_direct_restart fun_chk_direct_port2 ;; adx_deploy) fun_copy_adx__code fun_chk_adx_code fun_deploy_adx_restart fun_chk_adx_port1 ;; adx_rollback) fun_rollback_adx_code fun_rollback_adx_restart fun_chk_adx_port2 ;; esac
#!/bin/bash # Auth: liupenghui # date: 2020-10-22 # TEL: xxxxx # 部署完成校验
####验证adserver版本号#############
fun_chk_adx_version(){ ansible adx -m shell -a 'md5sum /gnome/adx/gnome-adx-0.0.1-SNAPSHOT-jar-with-dependencies.jar' |awk '{print $1}'|sort |head -n62 |tee version_adx |cat -n adx_version=$(ansible adx -m shell -a 'md5sum /gnome/adx/gnome-adx-0.0.1-SNAPSHOT-jar-with-dependencies.jar' |awk '{print $1}'|sort |tail -n62 |uniq -c|awk '{print $2}') echo -e "\e[1;32m新发布的版本号为:$adx_version\e[0m" version1=$(diff metadata version_adx) if [ -z $version1 ];then echo -e "\e[1;32m代码部署成功 \e[0m" else echo -e "\e[1;31m请检查错误 \e[0m" fi }
####验证directAd版本号############
fun_chk_direct_version(){ ansible adx -m shell -a ' md5sum /gnome/directAd/direct-ad-0.0.1-SNAPSHOT-jar-with-dependencies.jar'|awk '{print $1}'|sort |head -n62 |tee version_direct | cat -n direct_version=$(ansible adx -m shell -a 'md5sum /gnome/directAd/direct-ad-0.0.1-SNAPSHOT-jar-with-dependencies.jar'|awk '{print $1}'|sort |tail -n62 |uniq -c|awk '{print $2}') echo -e "\e[1;32m 新发布的版本号为:$direct_version\e[0m" version2=$(diff metadata version_direct) if [ -z $version2 ];then echo -e "\e[1;32m 代码部署成功 \e[0m" else echo -e "\e[1;31m 请检查错误 \e[0m" fi } ###验证8080端口状态############### fun_chk_8080_port(){ chk_ip_8080=$(ansible adx -m shell -a ' netstat -ntulp |grep 8080' |awk '{print $1}' |egrep "[0-9]+\.*" |sort | tee data_8080.bak |cat -n) DIR_8080=$(diff metadata data_8080.bak) if [ -z $DIR_8080 ];then echo -e "\e[1;32m端口检查成功,端口号:8080 \e[0m" else echo -e "\e[1;31m 请检查错误 \e[0m" fi } ####验证8182端口状态############# fun_chk_8182_port(){ chk_ip_8182=$(ansible adx -m shell -a ' netstat -ntulp |grep 8182' |awk '{print $1}' |egrep "[0-9]+\.*" |sort |tee data_8182.bak |cat -n) DIR_8182=$(diff metadata data_8182.bak) if [ -z $DIR_8182 ];then echo -e "\e[1;32m 端口检查成功,端口号:8182 \e[0m" else echo -e "\e[1;31m 请检查错误 \e[0m" fi } case $1 in adx) fun_chk_adx_version fun_chk_8080_port fun_chk_8182_port ;; direct) fun_chk_direct_version fun_chk_8080_port fun_chk_8182_port ;; esac
bash配置文件
-
按生效范围划分,存在两类:
全局配置:
/etc/profile /etc/profile.d/*.sh /etc/bashrc
个人配置:
~/.bash_profile ~/.bashrc
交互式登录: (1)直接通过终端输入账号密码登录 (2)使用“su - UserName” 切换的用户 执行顺序:
/etc/profile --> /etc/profile.d/*.sh --> ~/.bash_profile --> ~/.bashrc --> /etc/bashrc
非交互式登录: (1)su UserName (2)图形界面下打开的终端 (3)执行脚本 (4)任何其它的bash实例 执行顺序:
/etc/profile.d/*.sh --> /etc/bashrc --> ~/.bashrc
按功能划分,存在两类:
profile类: 为交互式登录的shell提供配置
全局:/etc/profile, /etc/profile.d/*.sh 个人:~/.bash_profile
功用: (1) 用于定义环境变量 (2) 运行命令或脚本
bashrc类: 为非交互式和交互式登录的shell提供配置
全局:/etc/bashrc 个人:~/.bashrc
功用: (1) 定义命令别名和函数
(2) 定义本地变量
注意:
(1)命令中定义的特性,譬如变量和别名,仅对当前shell进程有效
(2)配置文件中定义的特性,只对随后新启动的shell进程有效,想让通过配置文件定义的特性立即生效,可以在命令行重复定义一次,或者让shell进程重读配置文件
source /PATH/FROM/CONF_FILE
. /PATH/FROM/CONF_FILE
-
用户退出任务
~/.bash_logout
在退出登录shell时运行。
用于
-
创建自动备份
-
清除临时文件
-
示例:当退出登录时,删除文件/data/test
[root@centos7 ~]# vim ~/.bash_logout # ~/.bash_logout rm -rf /data/test
环境变量配置文件
系统开机启动,环境变量配置文件的执行顺序为:
/etc/profile -> (~/.bash_profile | ~/.bash_login | ~/.profile) -> ~/.bashrc -> /etc/bashrc -> ~/.bash_logout
(1)/etc/profile:
此文件为系统的每个用户设置环境信息,当用户第一次登录时,该文件被执行.
并从/etc/profile.d目录的配置文件中搜集shell的设置(*.sh文件)。
(2)~/.bash_profile:
每个用户都可使用该文件输入专用于自己使用的shell信息,当用户登录时,该文件仅仅执行一次!
默认情况下,他设置一些环境变量,执行用户的.bashrc文件。
(3)~/.bashrc:
该文件包含专用于你的bash shell的bash信息,当登录时以及每次打开新的shell时,
该文件被读取,调用/etc/bashrc。
(4)/etc/bashrc:
为每一个运行bash shell的用户执行此文件.当bash shell被打开时,该文件被读取。
(5)~/.bash_logout:
当每次退出系统(退出bash shell)时,执行该文件。可用于 创建自动备份 和 清除临时文件
注意:/etc/profile 中设定的变量(全局)的可以作用于任何用户,
而~/.bashrc 等中设定的变量(局部)只能继承/etc/profile中的变量,他们是"父子"关系.
~/.bash_profile 是交互式 login 方式进入 bash 时运行的;
~/.bashrc 是交互式 non-login 方式进入 bash 时运行的;
通常二者设置大致相同,通常前者会调用后者,以便统一配置用户环境。
其他配置文件
~/.bash_history 是bash shell的历史记录文件,里面记录了你在bash shell中输入的所有命令。
环境变量: HISSIZE 设置在历史记录文件里保存记录的条数。
/etc/enviroment 是系统的环境变量,与登录用户无关,例如登录提示语言,没必要修改 /etc/profile 是所有用户的环境变量,与登录用户有关,变量冲突以用户的环境变量为准
系统应用程序的执行与用户环境可以是无关的,但与系统环境是相关的. 系统开机时,读取的顺序是:? /etc/enviroment -> /etc/profile 登陆系统时,用户shell建立环境,读取的顺序是: /etc/profile -> /etc/enviroment
永久修改环境变量(编辑环境变量配置文件)
示例: 在文件末尾添加环境变量:
export PATH=$PATH:/opt/lamp/mysql/bin export CLASSPATH=./JAVA_HOME/lib;$JAVA_HOME/jre/lib
使环境变量生效
source /etc/profile 通知bash进程重新读取配置文件 . /etc/profile 执行配置文件 注销系统
(同名的环境变量,后写入的起作用)
查看环境变量
set 显示当前shell的变量,包括当前用户的变量(内部命令) export 显示当前导出成用户变量的shell变量(内部命令) env 显示当前用户的环境变量(外部命令) declare -x printenv echo $PATH 显示当前导出成用户变量shell的变量,包括当前用户的变量中的PATH
临时设置或显示环境变量
注意:使用export或declare设置的变量都是临时变量,也就是说退出当前的shell,为该变量定义的值便不会生效
export [-fnp] [NAME]=[变量设置值] 当前导出成用户变量的shell变量 -f 代表[NAME]中为函数名称。 -n 删除指定的变量。变量实际上并未删除,只是不会输出到后续指令的执行环境中。 -p 列出所有的shell赋予程序的环境变量。 Examples: export MYENV=7 # 定义环境变量并赋值 export LANG=zh_CN.UTF-8 # 临时修改语言环境字符集中文 declare [-fFirx] [-p] [name[=value]] 显示所有shell变量(与 typeset 相同) +/- "-"可用来指定变量的属性,"+"则是取消变量所设的属性; -p 将显示每个[name]的属性和值 -f 仅显示函数; -r 将[name]设置为只读; -x 指定的变量会成为用户环境变量,可供shell以外的程序来使用;env |grep [name] -i [value]可以是数值,字符串或运算式。 -F 包含-f,禁止显示函数定义;只有函数名和属性会被显示 Examples: declare -x MYENV=7 # 定义环境变量并赋值 declare -i number=$RANDOM*100/32767 # 设置0~100之间的随机数变量number
显示和设置shell中的行为选项:shopt(向下兼容set)
shopt 列出当前shell中只能由shopt设置的选项 cdspell 自动改正cd命令参数中的小错误 hostcomplete 以@开头时,按tab键可进行主机名的自动完成 dotgblob 以点开始的文件名被包含在路径名扩展中 mailwarn 显示邮件警告信息 shopt -o 列出可由set命令设置的选项 emacs 进入emacs编辑模式 vi 进入vi编辑模式 ignoreeof 不允许单独使用Ctrl+D退出的用法,要使用exit。与IGNOREEOF=10等价 noclobber 不允许重定向覆盖已存在文件 noglob 不允许扩展文件名通配符 nounset 使用未定义的变量时给出错误 -p 显示可设置选项及当前取值 -s 设置每一选项为on -u 设置每一选项为off -q 不输出信息
临时设置shell变量:set
set 显示所有shell变量 -a 标示已修改的变量,导出至环境变量。echo $[name] -b 使被中止的后台程序立刻回报执行状态。 -C 重定向所产生的文件无法覆盖已存在的文件。 -d Shell预设会用杂凑表记忆使用过的指令,以加速指令的执行。使用-d参数可取消。 -e 若指令传回值不等于0,则立即退出shell。等同 set –o errexit -f 取消使用通配符。 -h 自动记录函数命令的所在位置。hash下来 -H,--history 可利用"!"加<指令编号>的方式来执行history中记录的指令。 -k 指令所给的参数都会被视为此指令的环境变量。 -l 记录for循环的变量名称。 -m 使用监视模式。可以通过Job control来控制进程的停止、继续,后台或者前台执行等。 -n 只读取指令,而不实际执行。 -p 启动优先顺序模式。 -P 启动-P参数后,执行指令时,会以实际的文件或目录来取代符号连接。 -t 执行完随后的指令,即退出shell。 -u 当执行时使用到未定义过的变量,则显示错误信息。等同 set –o nounset -v 显示shell所读取的输入值。 -x 执行指令后,会先显示该指令及所下的参数 -i,--interactive-comments 交互式 shell ,shell 脚本中默认关闭 -B,braceexpand 大括号扩展命令,默认开启
删除shell变量:unset
unset [-f] [-v] [name ...] 删除shell变量 name -f 仅删除函数 -v 仅删除变量
常用的环境变量
PATH
决定了shell将到哪些目录中寻找命令或程序,预设可执行文件或命令的搜索路径。 $PATH=路径1:路径2:...:路径n
RANDOM
0~32767之间的随机整数
HISTSIZE
设置在历史记录文件里保存记录的条数。
HISTFILE
指定历史文件,默认为~/.bash_history
HISTFILESIZE
命令历史文件记录的条数
HISTTIMEFORMAT
HISTTIMEFORMAT=“%F %T “ 显示时间
HISTIGNORE
HISTIGNORE=“str1:str2*:… “ 忽略str1,str2开头的历史
HISTCONTROL
HISTCONTROL= 记录方式 ignoredups 默认,忽略重复的命令,连续且相同为“重复” ignorespace 忽略所有以空白开头的命令 ignoreboth 相当于 ignoredups,ignorespace 的组合 erasedups 删除重复命令
CDPATH
cd 相对路径进入目录时,寻找匹配目录的目录
PWD
当前目录路径,自动更新
OLDPWD
上一次目录路径,自动更新
LANG
系统语言
LANG="en_US.UTF-8" LANG="zh_CN.UTF-8" Cent OS 6.x 配置文件 /etc/sysconfig/i18n Cent OS 7.x 配置文件: /etc/locale.conf localectl set-locale LANG='en_US.UTF-8'
PS1
提示符变量,用于设置提示符格式,设置一级shell提示符环境变量,个性化终端命令行提示符的信息或格式。
PS1 基本提示符 例如:export PS1="[\u@\h \w]\$ " \d :代表日期,格式为weekday month date,例如:Wed Dec 12 \H :完整的主机名称。例如:hostname是debian.linux \h :仅取主机的第一个名字,如上例,则为debian,.linux则被省略 \t :显示时间为24小时格式,如:HH:MM:SS \T :显示时间为12小时格式 \A :显示时间为24小时格式:HH:MM \u :当前用户的账号名称 如:root \v :BASH的版本信息 如:3.2 \w :完整的工作目录名称。家目录会以~代替 如显示/etc/default/ \W :利用basename取得工作目录名称,只会列出最后一个目录。如上例则只显示default \# :下达的第几个命令 \$ :提示字符,如果是root时,提示符为:# ,普通用户则为:$
PS2
用于设置二级shell提示符环境变量。
终端命令行的相关设置:stty
stty 显示终端命令行的相关设置 -a 打印终端所有当前设置 iuclc 禁止输出大写,前加-开启 olcuc 禁止输出小写,前加-开启 size 打印出终端的行数和列数 stty eof "string" 修改Ctrl+D快捷键 echo 打开回显,前加-关闭 igncr 忽略回车符,前加-关闭
LOGNAME:
当前用户的登录名 LANGUGE: 语言相关的环境变量,多语言可以修改此环境变量 MAIL: 当前用户的邮件存放目录 BASH:记录当前bash shell的路径。 BASH_SUBSHELL:记录当前子shell的层次。 BASH_SUBSHELL是从0开始计数的整数。 BASH_VERSINFO:是一个数组包含六个元素,这六个元素显示bash的版本信息。 BASH_VERSION:显示shell版本的信息。 DIRSTACK:记录了栈顶的目录值,初值为空。 GLOBLGNORE:是由冒号分割的模式列表,表示通配时忽略的文件名集合。 GROUPS:记录当前用户所属的组。 HOME:记录当前用户的家目录,由/etc/passwd的倒数第二个域决定。 HOSTNAME:记录主机名。 HOSTTYPE和MACHTYPE:都是记录系统的硬件架构。 IFS:用于设置指定shell域分隔符,默认情况下为空格。 OSTYPE:记录操作系统类型。 PPID:是创建当前进程的进程号,即当前进程的父进程号 RELY:REPLY变量与read和select有关。 SECONDS:记录脚本从开始到结束耗费的时间。 SHELL:显示当前所用的shell SHELLOPTS:记录了处于“开”状态的shell选项列表,它只是一个只读变量。 SHLVL:记录了bash嵌套的层次,一般来说,我们启动第一个Shell时。$SHLVL=1。如果在这个Shell中执行脚本,脚本中的$SHLVL=2。 TMOUT:用来设置脚本过期的时间,比如TMOUT=3,表示该脚本3秒后过期。 UID: 已登用户的ID USER:显示当前用户名字
用于查询与修改系统的本地化(locale)与键盘布局的设置:localectl
localectl list-locales 列出所有可用的 locale
设置系统的本地化环境变量(可以一次设置多个)
例如 "LANG=zh_CN.utf8", "LC_MESSAGES=en_US.utf8" 等等。
localectl set-locale LANG=zh_CN.UTF-8 --- centos7修改字符集信息
自定义环境变量
export PS1='\[\e[36;1m\][\u@\h \W]\$ \[\e[1;37m\]'
脚本示例
bc.sh
四则运算计算器
#!/bin/bash # 1 if [[ $# == 2 ]];then echo $1+$2=$(($1+$2)) echo $1-$2=$(($1-$2)) echo $1*$2=$(($1*$2)) echo $1/$2=$(($1/$2)) exit fi # 2 if [[ $# == 0 ]];then read -p "请输入参数1: " var1 read -p "请输入参数2: " var2 if [[ $var1 == "" || $var2 == "" ]];then # 3 var1=10 var2=10 fi echo $var1+$var2=$(($var1+$var2)) echo $var1-$var2=$(($var1-$var2)) echo $var1*$var2=$(($var1*$var2)) echo $var1/$var2=$(($var1/$var2)) exit fi echo 请输入2个参数! rabbit_chook.sh
鸡兔同笼脚本:
输入头数:35 输入脚个数:94
输出x只兔、y只鸡
算法:
x+y=35(头数) 2x+4y=94(脚数)
94/2=47-35=12(兔子个数)35-12=23(鸡个数)
let #!/bin/bash read -p "please input head num: " head read -p "please input floot num: " floot let x=$floot/2-$head let y=$head-$x echo "rabbit=$x;chook=$y" expr #!/bin/bash read -p "please input head num: " head read -p "please input floot num: " floot x=`expr $floot / 2 - $head` y=`expr $head - $x` echo "rabbit=$x;chook=$y" $[ ] #!/bin/bash read -p "please input head num: " head read -p "please input floot num: " floot rabbit=$[(floot-2*head)/2] chook=$[head-rabbit] echo "rabbit=$rabbit;chook=$chook" $(()) #!/bin/bash read -p "please input head num: " head read -p "please input floot num: " floot rabbit=$((floot/2-head)) chook=$((head-rabbit)) echo "rabbit=$rabbit;chook=$chook"
9x9.sh
#!/bin/bash for a in {1..9};do for b in `seq 1 $a`;do let c=$a*$b ;echo -e "${a}x${b}=$c\t\c" done echo done [root@oldboyedu-lnb ~]# sh 9x9.sh 1x1=1 2x1=2 2x2=4 3x1=3 3x2=6 3x3=9 4x1=4 4x2=8 4x3=12 4x4=16 5x1=5 5x2=10 5x3=15 5x4=20 5x5=25 6x1=6 6x2=12 6x3=18 6x4=24 6x5=30 6x6=36 7x1=7 7x2=14 7x3=21 7x4=28 7x5=35 7x6=42 7x7=49 8x1=8 8x2=16 8x3=24 8x4=32 8x5=40 8x6=48 8x7=56 8x8=64 9x1=9 9x2=18 9x3=27 9x4=36 9x5=45 9x6=54 9x7=63 9x8=72 9x9=81
chess_board.sh
#!/bin/bash red="\033[1;41m \033[0m" yellow="\033[1;43m \033[0m" for i in {1..8};do if [ $[i%2] -eq 0 ];then for i in {1..4};do echo -e -n "$red$yellow"; done echo else for i in {1..4};do echo -e -n "$yellow$red"; done echo fi done
color_isosceles_triangle.sh
#!/bin/bash read -p "Please input a num: " num if [[ $num =~ [^0-9] ]];then echo "input error" else for i in `seq 1 $num` ;do xing=$[2*$i-1] for j in `seq 1 $[$num-$i]`;do echo -ne " " done for k in `seq 1 $xing`;do color=$[$[RANDOM%7]+31] echo -ne "\033[1;${color};5m*\033[0m" done echo done fi
[root@oldboyedu-lnb ~]# sh color_isosceles_triangle.sh Please input a num: 5 * *** ***** ******* ********* systeminfo.sh
显示当前主机系统信息,包括主机名,IPv4地址,操作系统版本,内核版本,CPU型号,内存大小,硬盘大小
#! /usr/bin/sh hn=`uname -nr | awk '{print $1}'` ipv4=`ip route | grep -o 'src.*$' | awk '{print $2}'` os=`cat /etc/redhat-release` ke=`uname -nr | awk '{print $2}'` cpu=`lscpu | grep 'Ven.*' | awk '{print $3}'` me=`free -h | tail -1 | awk '{print $2}'` sda=`fdisk -l | head -2 | awk '{print $3}' | tail -1` echo "hostname:$hn" echo "IPv4:$ipv4" echo "OS version:$os" echo "Kernel version:$ke" echo "CPU model:$cpu" echo "SWAP size:$me" echo "Disk size:$sda G"
sysinfo.sh
带颜色显示当前主机系统信息,包括系统版本,内核版本,硬盘使用率,主机名
#!/usr/bin/env bash # ------------------------------------------ # Filename: hello.sh # Revision: 1.0 # Date: 2020/10/21 # Author: wu # Email: wu@gmail.com # Description: This is the first script # Copyright (C): 2020 All rights reserved # License: GPL # ------------------------------------------ # RED is content color # REDD is content color RED="\033[1;31m" REDD="\033[0m" echo -e OS Version is $RED`cat /etc/centos-release`$REDD echo -e Disk used is $RED`df | grep /dev/sd | tr -s " " |cut -d" " -f5 | sort -nr | head -n1`$REDD echo -e Kernel is $RED`uname -r`$REDD echo -e "Host name is $RED`hostname`"$REDD
create_sh.sh
编写生成脚本基本格式的脚本,包括作者,联系方式,版本,时间,描述等
#!/usr/bin/env bash # ------------------------------------------ # Filename: hello.sh # Revision: 1.0 # Date: 2020/10/21 # Author: wu # Email: wu@gmail.com # Description: create script and start # Copyright (C): 2020 All rights reserved # License: GPL # ------------------------------------------ if [ $# -lt 1 ]; then echo '至少应该给一个参数!' exit 1 fi cat > $1 <<EOF #!/usr/bin/env bash # ------------------------------------------ # Filename: $1 # Revision: 1.0 # Date: `date +%F` # Author: wu # Email: wu@gmail.com # Description: This is the $1 script # Copyright (C): 2020 All rights reserved # License: GPL # ------------------------------------------ EOF chmod +x $1 vim + $1
backup.sh
每日将/etc/目录备份到/backup/etcYYYYmm-dd中
#! /usr/bin/sh if [[ ! -d '/backup' ]]; then mkdir /backup fi cp -af /etc/ /backup/etc$(date -d 'today' +'%Y%m-%d') echo "/etc/已备份" chmod u+x backup.sh echo '00 0 9 * * * root /root/backup.sh #每天早上九点执行backup.sh该文件' >> /etc/crontab
user_no_nologin.sh
统计出/etc/passwd文件中,默认shell为非/sbin/nologin的用户个数,并将用户都显示出来
#! /usr/bin/sh getent passwd | grep -v /sbin/nologin
user_max_uid.sh
查出用户UID最大值的用户名、UID及shell类型
#! /usr/bin/sh getent passwd | sort -t: -k3 -nr | head -n1
memory
显示占用系统内存最多的进程
#! /usr/bin/sh ps aux | sort -k4 -nr | head -n1
disk.sh
显示当前硬盘分区中空间利用率最大的值
#! /usr/bin/sh echo `df|egrep -o '[0-9]{1,3}%'|sort -nr|head -1`
links.sh
显示正连接本主机的每个远程主机的IPv4地址和连接数,并按连接数从大到小排序
#! /usr/bin/sh echo `netstat -nt | awk '{print $4}' | egrep '[0-9.]' | cut -d: -f1 | sort | uniq -c | sort -nr`
sumid.sh
计算/etc/passwd文件中的第10个用户和第20用户的UID之和
#! /usr/bin/sh echo `cut -d: -f3 /etc/passwd | sed -n '10p;20p' | awk '{sum += $1};END {print sum}'`
sumspace.sh
传递两个文件路径作为参数给脚本,计算这两个文件中所有空白行之和
#! /usr/bin/sh echo `cat $1 $2 | sed -n '/^$/p' | wc -l`
sumfile.sh
统计/etc, /var, /usr 目录中共有多少个一级子目录和文件
#! /usr/bin/sh # c is Number of parameters c=3 echo "`ll /etc /var /usr | wc -l` -($c*2+($c-1))" | bc
argsnum.sh
接受一个文件路径作为参数;如果参数个数小于1,则提示用户“至少应该给一个参数”,并立即退出;如果参数个数不小于1,则显示第一个参数所指向的文件中的空白行数
#! /usr/bin/sh if [ $# -lt 1 ]; then echo '至少应该给一个参数!' exit 1 fi cat $1 | sed -n '/^$/p' | wc -l
hostping.sh
接受一个主机的IPv4地址做为参数,测试是否可连通。如果能ping通,则提示用户“该IP地址可访问”;如果不可ping通,则提示用户“该IP地址不可访问”
#! /usr/bin/sh if [ $# -lt 1 ]; then echo '至少应该给一个参数!' exit 1 fi if ping -c1 -W2 $1; then echo '该IP地址可访问' else echo '该IP地址不可访问' fi #! /usr/bin/sh ping -c1 -W2 $1 [ $? -eq 0 ] && echo '该IP地址可访问' || echo '该IP地址不可访问'
hostsping.sh
使用for和while分别实现:
测试192.168.0.0/24网段内,地址是否能够ping通,若ping通则输出"success!",若ping不通则输出"fail!"
for循环实现
#!/bin/bash NUM=`seq 1 254` for IP in ${NUM};do HOST_IP="192.168.0.${IP}" ping -c 2 -w 3 ${HOST_IP} &> /dev/null if [ $? -eq 0 ];then echo "success!" && echo ${HOST_IP} >> /tmp/ip_success.txt else echo "fail!" && echo ${HOST_IP} >> /tmp/ip_fail.txt fi done
while循环实现
#!/bin/bash IP=192.168.0 NUM=1 while [ $NUM -lt 255 ];do ping -c2 -W2 $IP.$NUM &> /dev/null if [ $? -eq 0 ];then echo "$IP.$NUM success!" else echo "$IP.$NUM fail!" fi let NUM++ done
checkdisk.sh
工作日,每10分钟执行一次,检查磁盘分区空间和inode使用率,如果超过80%,就发广播警告空间将满
#! /usr/bin/sh sd=`df | egrep -o '[0-9]{1,3}%' | sort -nr | head -1 | egrep -o '[0-9]{1,3}'` inode=`df -ih | egrep -o '[0-9]{1,3}%' | sort -nr | head -1 | egrep -o '[0-9]{1,3}'` if [ $sd -gt 80 -o $inode -gt 80 ]; then wall 'Disk space will be full!' else echo '磁盘分区空间和inode使用率正常' fi echo "*/10 * * * 1-5 /usr/bin/bash /server/script/checkdisk.sh" >> /var/spool/cron/root
每10分钟执行一次,检查磁盘分区空间、CPU和内存使用率,如果超过80%,就发广播警告空间将满
#! /usr/bin/sh sd=`df | egrep -o '[0-9]{1,3}%' | sort -nr | head -1` cpu=`cat /proc/stat|grep '^cpu[0-9]'|awk '{used+=$2+$3+$4;tolused+=$2+$3+$4+$5+$6+$7+$8} END{printf ("%.0f",used/tolused*100)}'` mem=`free -t | tail -1 | awk '{printf ("%.0f",$3/$2*100)}'` if [ ${sd%\%} -gt 80 ]; then wall 'Disk space will be full!' elif [ $inode -gt 80 ]; then wall 'CPU space will be full!' elif [ $mem -gt 80 ]; then wall 'Mem space will be full!' fi echo "*/10 * * * * /usr/bin/bash /server/script/checkdisk.sh" >> /var/spool/cron/root
checkip.sh
编写脚本/server/script/checkip.sh,每5分钟检查一次,如果发现通过ssh登录失败次数超过10次,自动将此远程IP放入Tcp Wrapper的黑名单中予以禁止防问
写法一:
#!/bin/bash while true;do awk '/Failed/{ip[$(NF-3)]++} END { for(i in ip) { if(ip[i]>=2) { system("echo sshd:"i" >> /etc/hosts.deny") } } }' /var/log/ssh.log sleep 5m done
写法二:
#!/bin/bash while true;do # 收集IP出现失败的次数 ipnum=`awk '/Failed/{print $(NF-3)}' /var/log/ssh.log |uniq -c |awk '{print $1}'` # 统计IP地址 ip=`awk '/Failed/{print $(NF-3)}' /var/log/ssh.log |uniq -c |awk '{print $2}'` # 当IP地址出现次数大于等于3,将IP地址拉入黑名单。 if [ $ipnum -ge 3 ] ;then echo "sshd:$ip" >> /etc/hosts.deny fi sleep 5m done 执行方式:sleep 5分钟或者加入计划任务,每5分钟执行一次 $ crontab -l */5 * * * * /server/script/checkip.sh
checkDOS.sh
解决DOS攻击生产案例: 根据web日志或者或者网络连接数,监控当某个IP并发连接数或者短时内PV达到100,调用防火墙命令封掉对应的IP,监控频率每隔5分钟。防火墙命令为: iptables -A INPUT -s IP -j REJECT
#!/bin/bash # environment variable source /etc/profile iplist=$(ss -tan | awk -F "[[:space:]]+|:" '/ESTAB/{ip[$(NF-2)]++}END{ for(i in ip) {print i,ip[i]} }' | awk '{if($2>100)print $1}') for ip in $iplist do iptables -I INPUT -s $ip -j DROP echo "$ip is drop!" done 加入计划任务,每5分钟执行一次 $ crontab -l */5 * * * * /server/script/checkDOS.sh
per.sh
判断当前用户对指定参数文件,是否不可读并且不可写
#! /usr/bin/sh if [ -r $1 -a -w $1 ]; then echo "$1并非不可读并且不可写" else echo "$1不可读并且不可写" fi
excute.sh
判断参数文件是否为sh后缀的普通文件,如果是,添加所有人可执行权限,否则提示用户非脚本文件
#! /usr/bin/sh if [[ -f $1 && `echo "$1" | cut -d. -f2` = sh ]]; then chmod a+x $1 else echo "$1非脚本文件" fi
nologin.sh
禁止普通用户登录系统
#! /usr/bin/sh echo "System maintenance, no login." > /etc/nologin
login.sh
允许普通用户登录系统
#! /usr/bin/sh \mv /etc/nologin /root/nologin
createuser.sh
使用一个用户名做为参数,如果指定参数的用户存在,就显示其存在,否则添加之;显示添加的用户的id号等信息
#! /usr/bin/sh if id $1 &> /dev/null; then id $1 else useradd $1 id $1 fi
create_user_home.sh
接受二个位置参数,magedu和/www,判断系统是否有magedu,如果没有则自动创建magedu用户,并自动设置家目录为/www
#!/bin/bash id $1 &> /dev/null if [ $? -eq 0 ]; then id $1 else useradd -d $2 $1 id $1 fi
yesorno.sh
提示用户输入yes或no,并判断用户输入的是yes还是no,或是其它信息
#! /usr/bin/sh read -p "Please input:yes/no: " name if [ $name = yes ]; then echo 'You input: yes' elif [ $name = no ]; then echo 'You input: no' else echo 'You input: other info' fi
filetype.sh
判断用户输入文件路径,显示其文件类型(普通,目录,链接,其它文件类型)
#! /usr/bin/sh if [ $# -lt 1 ]; then echo '至少应该给一个参数!' exit 1 elif [ -L $1 ]; then echo '$1 is a Symbolic link' elif [ -d $1 ]; then echo '$1 is a directory' elif [ -f $1 ]; then echo '$1 is a normal file' else echo '$1 is a other file type' fi
checkint.sh
判断用户输入的参数是否为正整数
#! /usr/bin/sh if [ $# -lt 1 ]; then echo '至少应该给一个参数!' exit 1 fi if expr $1 + 1 &> /dev/null; then if [ $1 -ge 0 ]; then echo "$1是正整数" fi else echo "$1不是正整数" fi
#! /bin/bash if [ $# -gt 1 ];then echo "只能判断一个参数!" exit fi if [[ -z $1 ]];then echo "输入为空" exit fi if [[ $1 =~ ^[0-9-]?[0-9]+$ ]];then echo "输入为整数" exit fi echo "输入不是整数"
pathadd.sh
让所有用户的PATH环境变量的值多出一个路径,例如:/usr/local/apache/bin
#! /usr/bin/sh echo "export PATH=$PATH:/usr/local/apache/bin" >> /etc/profile source /etc/profile
rootlogin.sh
用户 root 登录时,将命令指示符变成红色,并自动启用如下别名:
rm='rm –i' cdnet='cd /etc/sysconfig/network-scripts/' editnet='vim /etc/sysconfig/network-scripts/ifcfg-eth0' editnet='vim /etc/sysconfig/network-scripts/ifcfg-eno16777736 或 ifcfg-ens33' (如果系统是CentOS7) #! /usr/bin/sh cat <<EOF>> /etc/profile export PS1="\e[31m[\u@\h \W]\$ \e[0m" alias rm='rm –i' alias cdnet='cd /etc/sysconfig/network-scripts/' alias editnet='vim /etc/sysconfig/network-scripts/ifcfg-eth0' EOF
loginissue.sh
任意用户登录系统时,显示红色字体的警示提醒信息“Hi,dangerous!”
#! /usr/bin/sh echo 'echo -e "\e[1;31mHi,dangerous!\e[0m"' >> /etc/profile
reset.sh
用户的环境初始化:包括别名,登录提示符,vim的设置,环境变量等
#! /usr/bin/sh
selinux.sh
开启或禁用SELinux
#!/bin/bash if [[ $1 =~ ^[Oo][Nn]$ ]]; then sed -i 's/^SELINUX=.*/SELINUX=enforcing/' /etc/selinux/config &>/dev/null setenforce 1 &> /dev/null elif [[ $1 =~ ^[Oo][Ff][Ff]$ ]]; then sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config &>/dev/null setenforce 0 &>/dev/null else echo "you are error,please input on or off !!!" fi
checkganglia.sh
当客户机数量不为10的时候,进行报错;当客户机ganglia服务没有启动时,进行报错,并且筛选出所有没有启动ganglia的客户机
#!/bin/bash DATE=`date +%Y%m%d` filename="ganglia-${DATE}.log" prefix="ganglia-${DATE}" hosts=`grep test@ /tmp/log/ganglia/${filename} | wc -l` pids=`grep gmond.pid /tmp/log/ganglia/${filename} | wc -l` if [ ${hosts} != 10 ] then echo "Some hosts are offline!" >> /tmp/log/ganglia/error-${DATE}.log fi if [ ${hosts} != ${pids} ] then echo "Some ganglia services have stopped!" >> /tmp/log/ganglia/error-${DATE}.log cd /tmp/log/ganglia/ csplit /tmp/log/ganglia/${filename} /test@/ -n2 -s {*} -f ${prefix} -b ".log.%02d" rm ${prefix}.log.00 for file in /tmp/log/ganglia/${prefix}.log.* do if [ -f "${file}" ] then #echo "${file} is file" if [ `grep gmond.pid ${file} | wc -l` == 0 ] then echo `grep test@ ${file}` >> /tmp/log/ganglia/error-${DATE}.log fi fi done fi [root@linuxcool ~]# crontab -l
# 每天查看一下ganglia的状态,并保存到/tmp/log/ganglia目录
0 0 * * * /usr/bin/parallel-ssh -h /home/test/hosts.txt -t 30 -i 'ps aux | grep gmond' > /tmp/log/ganglia/ganglia-`date +\%Y\%m\%d`.log
# 自动筛选报错
10 0 * * * /bin/bash /home/test/checkganglia.sh
tr命令
tr [OPTION]... SET1 [SET2] 转换和删除字符 -c –C --complement 取字符集的补集 -d --delete 删除SET1的字符 -s --squeeze-repeats 把连续重复的字符以单独一个字符表示 -t --truncate-set1 将SET1转化为SET2 tr -d a < filename 删除文件filename标准输出中的a tr -s '\n' < filename 删除文件filename标准输出中的空行 cat filename |tr a-z A-Z 将文件filename中标准输出的小写字母全部转换成大写字母 or cat filename |tr [:lower:] [:upper:] tr -cd '0-9a-zA-Z' < /dev/urandom | head -c8 # 生成8位随机数
收集文本统计数据wc
计数单词总数、行总数、字节总数和字符总数 可以对文件或STDIN中的数据运行
wc story.txt 39 237 1901 story.txt 行数 字数 字节数 常用选项 -l 只计数行数 -w 只计数单词总数 -c 只计数字节总数 -m 只计数字符总数 -L 显示文件中最长行的长度
uniq
uniq命令:从输入中删除前后相接的重复的行 uniq [OPTION]... [FILE]... -c: 显示每行重复出现的次数 -d: 仅显示重复过的行 -u: 仅显示不曾重复的行 注:连续且完全相同方为重复 常和sort 命令一起配合使用: sort userlist.txt | uniq -c
文本排序sort
把整理过的文本显示在STDOUT,不改变原始文件 sort [options] file(s) 常用选项
-r 执行反方向(由上至下)整理 -R 随机排序 -n 执行按数字大小整理 -f 选项忽略(fold)字符串中的字符大小写 -u 选项(独特,unique)删除输出中的重复行 -t c 选项使用c做为字段界定符 -k X 选项按照使用c字符分隔的X列来整理能够使用多次
bash如何展开命令行
-
把命令行分成单个命令词
-
展开别名
-
展开大括号的声明({})
-
展开波浪符声明(~)
-
命令替换$()和``)
-
再次把命令行分成命令词
-
展开文件通配(*、?、[abc]等等)
-
准备I/0重导向(<、>)
-
运行命令
防止扩展
反斜线\
会使随后的字符按原意解释
$echoYourcost:\$5.00 Yourcost:$5.00
加引号来防止扩展 •单引号'
防止所有扩展 •双引号"
也防止所有扩展,但是以下情况例外:
$(美元符号)-变量扩展 (反引号)-命令替换 \(反斜线)-禁止单个字符扩展,转义单个字符 !(叹号)-历史命令替换
1.简单的介绍shell 1) 为什么要使用shell shell的作用 a. 安装操作系统 手工方式安装
自动化安装操作系统 kickstart 底层shell脚本 cobbler 底层shell脚本
b. 初始化操作系统
SSH优化 关闭SElinux 防火墙放行需要的端口(80 443 22修改 10050) YUM源 时间同步 系统最大描述符 内核参数优化 字符集优化 禁止开机自动启动 修改主机名称 (修改公司网卡名称)... 手动 命令行安全 写入shell脚本(常用)
c. 安装服务 Nginx PHP MySQL Rsync等等... 针对不同的版本写入shell脚本自动安装 d. 配置服务 e. 启动服务 所有的服务底层的启动方式都是使用的shell脚本
公司自己研发的程序
nohup python3.5 test.py --redis --port --mysql --port -a xxxx &
复制一下 写入脚本 sh start_test_py.sh 如何停止py程序
ps axu|grep test.py |grep -v grep|awk '{print $2}'|xargs kill -9
复制一下 写入脚本 sh stop_test_py.sh 把py的进程的端口和PID取出来 来判断是否运行
f. 日志统计 查看程序运行的情况 统计我们需要的数据 日志切割 定时任务+脚本 统计数据 定时任务+脚本 ---> 通过邮件发送给管理员 ELK 日志统计界面 py开发日志界面 py界面----> 数据库 <----数据 日志展示
g. 监控 监控服务 服务端口是否存在 服务是否存在 服务器的硬件资源使用情况 状态 日志 网络 Zabbix 通过脚本统计---> 测试---> 添加到zabbix服务 (cacti监控流量 Nagios宽带运营商 IT公司) 减少重复性的工作
2) 学习shell编程所用到的知识 必须熟练掌握
a. vim编辑器 课下快捷键 b. xshell crt c. shell基础命令60个左右 回顾基础命令 d. 三剑客命令 grep sed awk
3) 如何学习shell编程
a. 能读懂shell脚本---> 模仿---> 自己修改 ---> 自己写 ---> 熟练运用(基础命令) 重复性 b. 有编程能力(写的越多 编程能力越高) c. 找一本适合自己的教材 或者 完善的文档 (脚本功能框架) e. 多练多操作 f. 切记拿来直接使用,语句理解后在使用
-
shell初步入门 1)什么是shell
shell是命令解释器 负责翻译我们输入的命令 输入的ls pwd命令都是shell解释器给我们运行的 交互式 我们输入命令,shell负责解释执行我们输入的命令,并且把结果输出到屏幕的过程称为交互式 用户签退exit 关闭xshell shell终止 非交互式 不和我们交互,读取存在到文本中的shell命令,读取到文件结尾,shell结束
2)什么是shell脚本
把可执行的命令统一放入到一个文本中,称为shell脚本 包含了 判断语句 循环语句 数组等
3)脚本语言的种类
编译型 c c++ 解释型 python ruby 脚本型 python shell javascript 其他语言: PHP html go 问: 编译型语法是最快的吗 看优化 面试题: Linux中默认的shell解释器是 bash shell和py的区别 shell处理底层的能力较强 所有的服务都是shell编写 一键优化 一键安装 一键统计(awk) py主要作用可以写界面 自动化管理平台CMDB 功能需求
4)书写脚本的规范
a. 脚本存放固定的目录 统一管理 /server/scripts b. 脚本使用.sh结尾 让我们能识别是shell脚本 c. 脚本命名 见名知其意 start_nginx.sh stop_nginx.sh d. 脚本内的开头使用解释器 #!/bin/bash e. 脚本内的注释最好不用中文(可以用) f. 脚本内的成对的符号一次性书写完毕 语法书写完在写内容
5)写第一个脚本 使用vim编辑test.sh 在屏幕上输出 Hellow World!
[root@shell ~]# cat test.sh #!/bin/sh echo "Hello World!"
执行脚本常用的三种方式: 父shell和子shell的区别 子shell可以继承父shell的变量 /etc/profile 变量 所有的子shell都可使用 使用bash或sh执行shell脚本 都是在子shell中运行的
-
使用sh或者bash方式运行 开启了一个子shell运行里面的内容
[root@shell ~]# sh test.sh Hello World!
-
使用全路径方式执行脚本
[root@shell ~]# /root/test.sh -bash: /root/test.sh: Permission denied [root@shell ~]# ./test.sh -bash: ./test.sh: Permission denied 增加执行权限chmod [root@shell ~]# chmod +x test.sh [root@shell ~]# ll total 4 -rwxr-xr-x 1 root root 123 Oct 15 10:49 test.sh [root@shell ~]# /root/test.sh Hello World! /bin/bash [root@shell ~]# ./test.sh Hello World! /bin/bash
-
使用. 或者source 在父进程中执行shell脚本
[root@shell ~]# . test.sh Hello World! /bin/bash [root@lb02 ~]# name=oldboy [root@shell ~]# echo $name oldboy
不常用的执行方式
[root@shell ~]# echo pwd|bash /root [root@shell ~]# sh < test.sh Hello World! /bin/bash
-
-
shell常用的基础变量 1) 什么是变量
系统中的变量: $PATH $LANG $BASH $PS1 x=1 y=x+1 y=2 x和y都是变量的名称 等号后面的是变量的值 name=oldboy 使用一个固定的值代表不固定的值(数字 字符串 命令)
2) 环境变量分类
环境变量 (全局变量) 国法 所有的shell都生效 普通变量 (局部变量) 家规 只针对当前的shell生效 自定义的变量 查看系统环境变量 env
3) 按照生命周期划分
临时生效 只是在当前shell中生效 关闭则失效 使用export直接定义即可 永久生效 对当前系统所有shell生效 写入/etc/profile 不加export 只对当前的shell生效 加export 对当前的窗口的父shell和子shell生效
4) 环境变量配置文件执行的顺序
/etc/profile 读取家目录下所有的环境变量文件
.bash_profile .bashrc /etc/bashrc
5) 定义环境变量 环境变量的名称定义: 字母数字下划线开头的组合 见名知其意 等号两端不能有空格 不允许数字开头
oldboy_age=18 # 全小写 OLDBOY_AGE=25 # 全大写 系统环境变量都大写 oldboy_Age=30 # 小驼峰语法 Oldboy_Age=35 # 大驼峰语法 变量值的定义: 数字 字符串 和命令
三种值: 字符串定义 变量值中间不允许有空格 如果有需要加引号
[root@shell ~]# test='I am lizhenya' [root@shell ~]# oldboy_age=45 [root@shell ~]# test=`pwd` # 定义命令 [root@shell ~]# echo $test /root [root@shell ~]# test=$(ls) # 定义命令 [root@shell ~]# echo $test [root@shell ~]# ls test.sh
PS: 注意赋值反引号还是双引号
[root@shell ~]# date Thu Oct 15 11:46:55 CST 2020 [root@shell ~]# time=`date` [root@shell ~]# echo $time Thu Oct 15 11:47:00 CST 2020 [root@shell ~]# echo $time Thu Oct 15 11:47:00 CST 2020 [root@shell ~]# time="date +%F+%M+%S"
6) shell脚本中特殊的位置变量
$0# 表示脚本的名称 如果全路径执行则带全路径 可以使用basename只获取名字
$0 [root@shell ~]# cat test.sh #!/bin/sh #print hello world #Author oldboy #date 20202020 #version v1.0 echo "Hello World!" echo $0 [root@shell ~]# sh test.sh Hello World!
test.sh -------------------
$n # 代表了脚本的第n的参数 n为数字 如果是0呢 则为脚本名称 从1开始 从$9以后需要加{} 表示整体 -gt 是否大于 -ge 是否大于等于 -eq 是否等于 -ne 是否不等于 -lt 是否小于 -le 是否小于等于
$# #表示脚本传参的个数 使用方法 判断传参的个数
[root@shell ~]# cat test.sh [ $# -ne 2 ] && echo "请输入两个参数" && exit expr $1 + $2 [ $# -ne 2 ] && echo "请输入两个参数" && exit echo $1 echo $2
$? # 判断上一条命令的结果是否正确 0为正确 非0失败
[root@shell ~]# cat ping.sh ping -c2 -W1 www.baiduaaaaa.com &>/dev/null [ $? -ne 0 ] && echo "ping不通" || echo "通了"
$$ # 获取当前脚本的PID
[root@shell ~]# cat test.sh #!/bin/sh #print hello world #Author oldboy #date 20202020 #version v1.0 echo $$ > /tmp/nging.pid sleep 300
$$ # 获取当前脚本的PID $# # 表示脚本传参的个数 $n # 代表了脚本的第n的参数 n为数字 如果是0呢 则为脚本名称 从1开始 从$9以后需要加{} 表示整体 $0 #脚本名称 $? # 判断上一条命令的结果是否正确 0为正确 非0失败 $! # 获取上一个在后台运行脚本的PID号 排错使用 $_ # 获取当前命令行的最后一个参数 类似于esc . $* # 获取脚本传参的所有参数 在循环体中 不加双引号和$@相同 加双引号 把所有的参数当做一个参数 $@ # 获取脚本传参的素有参数 在循环体中 不加双引号和$*相同 加双引号 把所有的参数当做独立的
-
脚本传参的三种方式 1) 直接传参
[root@shell ~]# cat test.sh echo name=$1 echo age=$2 [root@shell ~]# sh test.sh oldboy 18 name=oldboy age=18
2) 赋值传参
[root@shell ~]# cat test.sh #!/bin/sh name=$1 age=$2 echo -e "$name\n$age" [root@shell ~]# sh test.sh oldboy 20 oldboy 20
3) read 传参 read 变量名称 进行赋值 read -p "用户提示" 变量名称
[root@shell ~]# cat read.sh #!/bin/bash read -p "请输入你的姓名: " name echo "你输入的姓名是 $name" [root@shell ~]# cat read.sh #!/bin/bash read -p "请输入你的姓名和年龄: " name age echo "你输入的姓名是: $name 年龄是: $age" [root@shell ~]# sh read.sh 请输入你的姓名和年龄: oldboy 20 你输入的姓名是: oldboy 年龄是: 20 [root@shell ~]# cat read.sh #!/bin/bash read -p "请输入你的姓名: " name read -p "请输入你的年龄: " age echo "你输入的姓名是: $name 年龄是: $age" [root@shell ~]# sh read.sh 请输入你的姓名: oldboy 请输入你的年龄: 20 你输入的姓名是: oldboy 年龄是: 20 案例: 使用read传参的方式 修改主机名称为shell 并且修改eth0网卡IP地址为88 [root@shell ~]# cat hostname.sh #!/bin/bash eth0_cfg='/etc/sysconfig/network-scripts/ifcfg-eth0' old_ip=`ifconfig eth0|awk 'NR==2{print $2}'|awk -F. '{print $NF}'` read -p "please input hostname: " name read -p "please input New IP: " IP hostnamectl set-hostname $name sed -i "s#$old_ip#$IP#g" $eth0_cfg grep $IP $eth0_cfg [root@shell ~]# cat ping.sh read -p "Please Input URL: " url ping -c2 -W1 $url &>/dev/null [ $? -ne 0 ] && echo "ping不通" || echo "通了" 重复赋值 [root@shell ~]# cat dir.sh #!/bin/sh dir1=/etc dir2=/tmp read -p "请输入需要备份的目录: " dir2 echo $dir1 echo $dir2 [root@shell ~]# sh dir.sh 请输入需要备份的目录: /opt /etc /opt
-
变量的子串及删除替换
[root@shell ~]# test='I am oldboy' [root@shell ~]# echo $test I am oldboy [root@shell ~]# echo $test|awk '{print $2}' am [root@shell ~]# echo $test|cut -c3-4 am
变量切片
nginx [root@shell ~]# echo ${test:2:2} am [root@shell ~]# echo ${#test} 11
统计字符的长度
[root@shell ~]# echo $test|wc -L 11 [root@shell ~]# expr length "$test" 11 [root@shell ~]# echo $test|awk '{print length}' 11
统计出字符串小于3的单词 笔试题
I am lzhenya teacher I am 18 [root@shell ~]# cat for.sh for i in I am lzhenya teacher I am 18 do [ ${#i} -lt 3 ] && echo $i done [root@shell ~]# sh for.sh I am I am 18 [root@shell ~]# echo I am lzhenya teacher I am 18|xargs -n1|awk '{if(length<3)print}' I am I am 18
[root@shell ~]# echo I am lzhenya teacher I am 18|awk '{for(i=1;i<=NF;i++)if(length($i)<3)print $i}' I am I am 18
子串的删除替换
[root@shell ~]# echo ${url} www.baidu.com [root@shell ~]# echo ${url}|awk -F "w." '{print $3 }' baidu.com [root@shell ~]# echo ${url} www.baidu.com [root@shell ~]# echo ${url#www.} baidu.com [root@shell ~]# echo ${url#*.} baidu.com [root@shell ~]# echo ${url#*.*.} com
贪婪匹配
[root@shell ~]# echo ${url##*.} com [root@shell ~]# echo ${url%.com} www.baidu [root@shell ~]# echo ${url%.*} www.baidu [root@shell ~]# echo ${url%.*.*} www [root@shell ~]# echo ${url%%.*} www [root@shell ~]# test=%66 [root@shell ~]# echo $test %66 [root@shell ~]# echo ${test#%} 66 [root@shell ~]# echo ${test##} #66 [root@shell ~]# echo ${test#\#} 66
变量替换
[root@shell ~]# echo $url www.baidu.com [root@shell ~]# echo $url|sed 's#www#WWW#g' WWW.baidu.com [root@shell ~]# echo $url www.baidu.com [root@shell ~]# echo ${url/w/W} Www.baidu.com [root@shell ~]# echo ${url//w/W} WWW.baidu.com [root@shell ~]# echo ${url/baidu/sina} www.sina.com [root@shell ~]# expr 1 +1 expr: syntax error [root@shell ~]# expr 10 - 5 5 [root@shell ~]# expr 10 * 5 expr: syntax error [root@shell ~]# expr 10 \* 5 50 [root@shell ~]# expr 10 / 5 2 [root@shell ~]# expr length oldboy # 统计字符串长度 6
2) $[] 只支持整数运算 不支持小数运算
[root@shell ~]# echo $[1+1] 2 [root@shell ~]# echo $[1+1.5] -bash: 1+1.5: syntax error: invalid arithmetic operator (error token is ".5") [root@shell ~]# echo $[1+10] 11 [root@shell ~]# echo $[1-10] -9 [root@shell ~]# echo $[100*10] 1000 [root@shell ~]# echo $[100/10] 10 [root@shell ~]# #100*10=1000 [root@shell ~]# echo 100*10=1000 100*10=1000 [root@shell ~]# echo 100*10=$[100*10] 100*10=1000 [root@shell ~]# num1=100 [root@shell ~]# num2=10 [root@shell ~]# echo $num1*$num2 100*10 [root@shell ~]# echo $num1*$num2=$[$num1*$num2] 100*10=1000
3) $(()) 只支持整数运算 不支持小数运算 PS: $()执行命令 $(()) 数值运算 执行效率最高
[root@shell ~]# echo $((1+1)) 2 [root@shell ~]# echo $((1+1.5)) -bash: 1+1.5: syntax error: invalid arithmetic operator (error token is ".5") [root@shell ~]# echo $((100-100)) 0 [root@shell ~]# echo $((100*100)) 10000 [root@shell ~]# echo $((100/100)) 1
4) let 只支持整数运算 不支持小数运算 语法格式:
let sum=1+1 输入结果: echo $sum
[root@shell ~]# let sum=1+1 [root@shell ~]# echo $sum 2 [root@shell ~]# let sum=100-100 [root@shell ~]# echo $sum 0 [root@shell ~]# let sum=100*100 [root@shell ~]# echo $sum 10000 [root@shell ~]# let sum=100/100 [root@shell ~]# echo $sum 1 [root@shell ~]# let sum=$num1+$num2 [root@shell ~]# echo $sum 110 [root@shell ~]# let i++ [root@shell ~]# echo $i 1 [root@shell ~]# let i++ [root@shell ~]# echo $i 2 [root@shell ~]# let i++ [root@shell ~]# echo $i 3 [root@shell ~]# echo i++ ===== i=i+1 i++ ===== i=i+1 [root@shell ~]# unset i [root@shell ~]# let i=i+1 [root@shell ~]# echo $i 1 [root@shell ~]# let i=i+1 [root@shell ~]# echo $i 2 [root@shell ~]# let i++ [root@shell ~]# let i++ [root@shell ~]# let i++
5) bc 使用yum安装 yum -y install bc 支持整数和小数运算 语法格式: echo 10*10|bc
[root@shell ~]# echo 10*10|bc 100 [root@shell ~]# echo 10/10|bc 1 [root@shell ~]# echo 10-10|bc 0 [root@shell ~]# echo 10+10|bc 20 [root@shell ~]# echo 10+10.2|bc 20.2 [root@shell ~]# echo 10.5+10.2|bc 20.7 [root@shell ~]# echo 10.5*10.2|bc 107.1
其他运算 支持整数和小数运算 awk python awk语法: awk 'BEGIN{print 10*10}'
[root@shell ~]# awk 'BEGIN{print 10*10}' 100 [root@shell ~]# awk 'BEGIN{print 10-10}' 0 [root@shell ~]# awk 'BEGIN{print 10/10}' 1 [root@shell ~]# awk 'BEGIN{print 10+10}' 20 [root@shell ~]# awk 'BEGIN{print 10^10}' 10000000000 [root@shell ~]# echo 10 10|awk '{print $1*$2}' 100 [root@shell ~]# echo 10 10|awk '{print $1^$2}' 10000000000
案例: 做一个加减乘除的计算器 要求用户输入两个输入 显示加减乘除后的结果 使用三种传参方式 sh count.sh 10 10 输入的结果
10+10=20 10-10= 10*10= 10/10= [root@shell ~]# cat count.sh #!/bin/sh [ $# -ne 2 ] && echo "输入两个参数多了不行" && exit
#1.直接传参
echo $1+$2=$[$1+$2] echo $1-$2=$[$1-$2] echo $1*$2=$[$1*$2] echo $1/$2=$[$1/$2]
#2 赋值传参
num1=$1 num2=$2 echo $num1+$num2=$[$num1+$num2] echo $num1-$num2=$[$num1-$num2] echo $num1*$num2=$[$num1*$num2] echo $num1/$num2=$[$num1/$num2]
#3 read传参
read -p "输入两个参数:
" num1 num2 echo $num1+$num2=$[$num1+$num2] echo $num1-$num2=$[$num1-$num2] echo $num1*$num2=$[$num1*$num2] echo $num1/$num2=$[$num1/$num2]
条件表达式之文件判断 语法格式: 方法1 test 表达式 文件|目录 例如: test -e /etc/passwd 方法2 [ -e file ] 常用表达式:
[ -e file|dir ] # 文件存在则为真 执行&& 否则执行|| [ -f file ] # 判断文件是否存在并且为普通文件 [ -d dir ] # 判断文件存在并且为目录 [ -r file ] # 文件存在并且可读 [ -w file ] # 文件存在并且有写入权限 [ -x file ] # 文件存在并且可执行
[root@shell ~]# test -e /etc/passwd [root@shell ~]# echo $? 0 [root@shell ~]# test -e /etc/passwd;echo $? 0 [root@shell ~]# test -e /etc/passwdddddddd;echo $? 1 [root@shell ~]# test -e /etc/passwd && echo "文件存在" || echo "文件不存在" 文件存在 [root@shell ~]# test -f /etc/passwd && echo "文件存在" || echo "文件不存在" 文件存在 [root@shell ~]# test -d /etc/passwd && echo "文件存在" || echo "文件不存在" 0 文件不存在 [root@shell ~]# test -x /etc/passwd && echo "文件存在" || echo "文件不存在" 文件不存在 [root@shell ~]# test -r /etc/passwd && echo "文件存在" || echo "文件不存在" 文件存在 [root@shell ~]# test -w /etc/passwd && echo "文件存在" || echo "文件不存在" 文件存在 [root@shell ~]# [ -e /etc/hosts ] && echo "文件存在" || echo "文件不存在" 文件存在 [root@shell ~]# [ -f /etc/hosts ] && echo "文件存在" || echo "文件不存在" 文件存在 [root@shell ~]# [ -d /etc/hosts ] && echo "文件存在" || echo "文件不存在" 文件不存在 [root@shell ~]# [ -d /etc/ ] && echo "文件存在" || echo "文件不存在" 文件存在 [root@shell ~]# [ -r /etc/ ] && echo "文件存在" || echo "文件不存在" 文件存在 [root@shell ~]# [ -w /etc/hosts ] && echo "文件存在" || echo "文件不存在" 文件存在 [root@shell ~]# [ -x /etc/hosts ] && echo "文件存在" || echo "文件不存在" 文件不存在 [root@shell ~]# [ -x test.sh ] && echo "文件存在" || echo "文件不存在" 文件存在 判断中可以使用命令的方式进行取值判断 [root@shell ~]# [ -f `ls test.sh` ] 使用and和or判断多个文件 [root@shell ~]# [ -f /etc/hosts -a -f /etc/passwd ] [root@shell ~]# [ -f /etc/hosts -a -f /etc/passwd ] && echo "文件存在" || echo "文件不存在" 文件存在 [root@shell ~]# [ -f /etc/hostsss -a -f /etc/passwd ] && echo "文件存在" || echo "文件不存在" 文件不存在 [root@shell ~]# [ -f /etc/hostsss -o -f /etc/passwd ] && echo "文件存在" || echo "文件不存在" 文件存在 [root@shell ~]# [ -f /etc/hostsss -o -f /etc/passwddd ] && echo "文件存在" || echo "文件不存在" 文件不存在
案例1:
使用变量赋值一个目录,判断目录是否存在,如果存在则打包压缩,如果不存在则提示目录不存在 [root@shell ~]# dir=/etc/;[ -d $dir ] && tar zcvf test.tar.gz $dir || echo "文件不存在"
案例2: 判断一个目录是否存在 如果不存在则创建
[root@shell ~]# [ -d /hehe ] && mkdir hehe [root@shell ~]# ll hehe ls: cannot access hehe: No such file or directory [root@shell ~]# ll /hehe ls: cannot access /hehe: No such file or directory [root@shell ~]# [root@shell ~]# [ -d hehe ] && mkdir hehe [root@shell ~]# ll hehe ls: cannot access hehe: No such file or directory [root@shell ~]# [ -d hehe ] || mkdir hehe [root@shell ~]# ll hehe total 0
案例3: 判断环境变量文件函数库是否存在 存在则执行.
[root@shell ~]# cat test.sh #!/bin/sh [ -f /etc/init.d/functions ] && . /etc/init.d/functions ping -c2 $1 &>/dev/null [ $? -eq 0 ] && action "ping $1 is ok" true || action "ping $1 is error" false
-
数值比较 整数 格式: test 整数1 表达式 整数2 [ 整数1 表达式 整数2 ]
表达式:
-eq 等于 -ne 不等于 -gt 大于 -ge 大于等于 -lt 小于 -le 小于等于
[] 判断支持正则 必须使用[[]] 在正则中使用以下符号
= != >= >< ><= [root@shell ~]# [ 10 -eq 10 ] && echo "成立" || echo "不成立" 成立 [root@shell ~]# [ 10 -ne 10 ] && echo "成立" || echo "不成立" 不成立 [root@shell ~]# [ 10 -ne 100 ] && echo "成立" || echo "不成立" 成立 [root@shell ~]# [ 10 -gt 100 ] && echo "成立" || echo "不成立" 不成立 [root@shell ~]# [ 10 -lt 100 ] && echo "成立" || echo "不成立" 成立 [root@shell ~]# [ 100 -le 100 ] && echo "成立" || echo "不成立" 成立
使用变量的方式比较
[root@shell ~]# echo $num1 100 [root@shell ~]# [ $num1 -le $num2 ] && echo "成立" || echo "不成立" 不成立 [root@shell ~]# echo $num2 10 [root@shell ~]# [ $num1 -gt $num2 ] && echo "成立" || echo "不成立" 成立 [root@shell ~]# [ $num1 -ne $num2 ] && echo "成立" || echo "不成立" 成立 [root@shell ~]# [ $num1 -ne $num2 ] && echo "成立" || echo "不成立" [root@shell ~]# name='I am lizhenya' [root@shell ~]# echo $name I am lizhenya [root@shell ~]# for i in $name;do echo $i;done I am lizhenya [root@shell ~]# for i in $name;do echo ${#i};done 1 2 8 [root@shell ~]# for i in $name;do [ ${#i} -lt 3 ];done [root@shell ~]# for i in $name;do [ ${#i} -lt 3 ]&& echo $i;done I am [root@shell ~]# for i in $name;do [ ${#i} -gt 3 ]&& echo $i;done lizhenya [root@shell ~]# [ 10 != 10 ] && echo "成立" || echo "不成立" 不成立 [root@shell ~]# [ 100 > 10 ] && echo "成立" || echo "不成立" 成立 [root@shell ~]# [ 100 >= 10 ] && echo "成立" || echo "不成立" -bash: [: 100: unary operator expected 不成立 [root@shell ~]# [ 100 < 10 ] && echo "成立" || echo "不成立" 成立 [root@shell ~]# [[ 100 < 10 ]] && echo "成立" || echo "不成立" 不成立 [root@shell ~]# [[ 100 > 10 ]] && echo "成立" || echo "不成立" 成立
案例1: 使用read或直接传参方式写一个数值比较脚本(使用数值表达式) 要求 输入两个数字 两个数字需要加判断 输入结果
sh count.sh 100 10 100>10 [root@shell ~]# cat count.sh #!/bin/sh [ $1 -gt $2 ] && echo "$1>$2" [ $1 -lt $2 ] && echo "$1<$2" [ $1 -eq $2 ] && echo "$1=$2"
案例2: 统计磁盘使用率 如果超过3 则发送邮件通知管理员,正常则输出当前使用率 第一个步骤: 取出磁盘使用率的数值
[root@shell ~]# df -h|awk 'NR==2{print $(NF-1)}' 4%
第二个步骤: 比较 优化取到的值
[root@shell ~]#df -h|awk 'NR==2{print $(NF-1)}'|awk -F% '{print $1}'
进行比较
[root@shell ~]# [ `df -h|awk 'NR==2{print $(NF-1)}'|awk -F% '{print $1}'` -gt 3 ] && echo mail..... || echo ok mail.....
第三个步骤: 写入脚本
[root@shell ~]# cat disk.sh #!/bin/bash use_disk=`df -h|awk 'NR==2{print $(NF-1)}'` [ ${use_disk%\%} -gt 80 ] && echo sendmail........ || echo "当前的磁盘使用率为 $use_disk" [root@shell ~]# sh disk.sh
当前的磁盘使用率为 4%
案例3: 统计内存使用率 如果超过3 则发送通知管理员,否则输出当前的内存使用率 内存使用率的计算方式 使用的除以总共的乘以100 统计内存使用率:
[root@shell ~]# free|awk 'NR==2{print $3/$2*100}' 5.46261 数值比较 [ `free|awk 'NR==2{print $3/$2*100}'|awk -F. '{print $1}'` -gt 3 ] && echo sendmail.... || echo ok 写入脚本 [root@shell ~]# cat use_mem.sh #!/bin/sh use_mem=`free|awk 'NR==2{print $3/$2*100}'` [ ${use_mem%.*} -gt 80 ] && echo sendmail...... || echo "当前内存使用率 ${use_mem}%" [root@shell ~]# sh use_mem.sh 当前内存使用率 5.4916% [root@shell ~]# cat use_mem.sh #!/bin/sh . /etc/init.d/functions use_mem=`free|awk 'NR==2{print $3/$2*100}'` [ ${use_mem%.*} -lt 3 ] && action "sendmail......" false || action "当前内存使用率 ${use_mem}%" true
案例4: 统计当前的负载 负载大于1 则发送通知 否则输出当前的负载使用情况
yum -y install httpd systemctl start httpd ab -n800000 -c400 http://10.0.0.61/index.html [root@shell ~]# cat use_load.sh #!/bin/sh use_load=`uptime|awk '{print $(NF-2)}'` [ ${use_load%.*} -gt 1 ] && echo sendmail............. || echo "当前系统的负载为 ${use_load%,}"
案例5: 统计系统的主机名称 虚拟平台 系统版本 内核版本 eth0 IP地址 外网网卡IP地址 当前内存和磁盘的使用率 输出的结果: 当前系统主机名称为: shell 当前系统内存使用率: 20%
[root@lb02 ~]# cat static.sh #!/bin/bash eth0_cfg='/etc/sysconfig/network-scripts/ifcfg-eth0' old_ip=`ifconfig eth0|awk 'NR==2{print $2}'|awk -F. '{print $NF}'` read -p "please input hostname: " name read -p "please input New IP: " IP hostnamectl set-hostname $name sed -i "s#$old_ip#$IP#g" $eth0_cfg grep $IP $eth0_cfg use_virtualization=`hostnamectl|awk 'NR==6{print $NF}'` [ ${use_virtualization} ] && echo "当前系统的虚拟平台为 ${use_virtualization}" use_oprete=`hostnamectl|awk 'NR==7{print $(NF-1)}'` [ ${use_oprete} ] && echo "当前系统版本为 ${use_oprete}" use_kernal=`hostnamectl|awk 'NR==9{print $NF}'` [ ${use_kernal} ] && echo "当前系统内核版本为 ${use_kernal}" use_mem=`free|awk 'NR==2{print $3/$2*100}'` [ ${use_mem%.*} -gt 80 ] && echo sendmail...... || echo "当前内存使用率 ${use_mem}%" use_disk=`df -h|awk 'NR==2{print $(NF-1)}'` [ ${use_disk%\%} -gt 80 ] && echo sendmail........ || echo "当前的磁盘使用率为 $use_disk"
-
多整数比较
-a and -o or [ 10 -eq 10 -a 100 -gt 50 -o 100 -ne 100 ] [root@shell ~]# [ 10 -eq 10 ] [root@shell ~]# [ 10 -eq 10 ] && echo "成立" || echo "不成立" 成立 [root@shell ~]# [root@shell ~]# [ 10 -eq 10 -a 100 -gt 50 ] && echo "成立" || echo "不成立" 成立 [root@shell ~]# [ 10 -ne 10 -a 100 -gt 50 ] && echo "成立" || echo "不成立" 不成立 [root@shell ~]# [root@shell ~]# [ 10 -eq 10 -a 100 -gt 50 -o 100 -ne 100 ] && echo "成立" || echo "不成立" 成立 [root@shell ~]# [ 10 -eq 10 -o 100 -ne 100 ] && echo "成立" || echo "不成立" 成立 在正则中使用 && || [root@shell ~]# [[ 10 -eq 10 && 100 -gt 20 ]] && echo "成立" || echo "不成立" 成立 [root@shell ~]# [[ 10 -eq 10 || 100 -gt 20 ]] && echo "成立" || echo "不成立" 成立 [root@shell ~]# [[ 10 -eq 11 || 100 -gt 20 ]] && echo "成立" || echo "不成立" 成立
-
字符串比对
[root@shell ~]# [ user = user ] && echo "成立" || echo "不成立" 成立 [root@shell ~]# [ user = userrr ] && echo "成立" || echo "不成立" 不成立 [root@shell ~]# [ $USER = userrr ] && echo "成立" || echo "不成立" 不成立 [root@shell ~]# [ $USER = user ] && echo "成立" || echo "不成立" 不成立 [root@shell ~]# echo $USER root [root@shell ~]# [ $USER = root ] && echo "成立" || echo "不成立" 成立 -n //nozero 字符串长度不为0时为真 -z //zero 字符串长度为0时为真 [root@shell ~]# test="" [root@shell ~]# [ -z $test ] && echo "成立" || echo "不成立" 成立 [root@shell ~]# test="aaa" [root@shell ~]# [ -z $test ] && echo "成立" || echo "不成立" 不成立 [root@shell ~]# [ -n $test ] && echo "成立" || echo "不成立" 成立
案例: 传参的时候不允许为空
[root@shell ~]# cat test.sh #!/bin/sh [ -f /etc/init.d/functions ] && . /etc/init.d/functions [ -z $1 ] && echo "请输入url" && exit ping -c2 $1 &>/dev/null [ $? -eq 0 ] && action "ping $1 is ok" true || action "ping $1 is error" false [root@shell ~]# cat count.sh #!/bin/sh [ $# -ne 2 ] && echo "请输入两个参数" && exit [ -z $1 ] && echo "请输入两个参数" && exit [ -z $2 ] && echo "请输入两个参数" && exit [ $1 -gt $2 ] && echo "$1>$2" [ $1 -lt $2 ] && echo "$1<$2" [ $1 -eq $2 ] && echo "$1=$2" read -p "请输入你的姓名: " name [ -z $name ] && echo "请输入姓名" && exit echo $name
案例: 判断输入的是否为数字
[root@shell ~]# expr 1 + 1.5 expr: non-integer argument [root@shell ~]# echo $? 2 [root@shell ~]# expr 1 + qq expr: non-integer argument [root@shell ~]# echo $? 2 [root@shell ~]# expr 1 + 234 235 [root@shell ~]# echo $? 0 [root@shell ~]# cat count.sh #!/bin/sh [ $# -ne 2 ] && echo "请输入两个参数" && exit expr $1 + $2 &>/dev/null [ $? -ne 0 ] && echo "请输入两个整数" && exit [ $1 -gt $2 ] && echo "$1>$2" [ $1 -lt $2 ] && echo "$1<$2" [ $1 -eq $2 ] && echo "$1=$2"
-
正则比对
[root@shell ~]# [[ $user =~ ^r ]] && echo "成立" || echo "不成立" 成立 [root@shell ~]# [[ user =~ ^r ]] && echo "成立" || echo "不成立" 不成立 [root@shell ~]# [[ user =~ user ]] && echo "成立" || echo "不成立" 成立 [root@shell ~]# [[ user =~ r$ ]] && echo "成立" || echo "不成立" 成立 [root@shell ~]# [[ user =~ r$ && oldboy =~ ^o ]] && echo "成立" || echo "不成立" 成立 [root@shell ~]# [[ user =~ r$ && oldboy =~ ^a ]] && echo "成立" || echo "不成立" 不成立 [root@shell ~]# [[ user =~ r$ || oldboy =~ ^a ]] && echo "成立" || echo "不成立" 成立 [root@shell ~]# [[ $num1 =~ ^[0-9]+$ ]] && echo "成立" || echo "不成立" 不成立 [root@shell ~]# cat count.sh #!/bin/sh [ $# -ne 2 ] && echo "请输入两个参数" && exit [[ ! $1 =~ ^[0-9]+$ ]] && echo "请输入整数" && exit [ $1 -gt $2 ] && echo "$1>$2" [ $1 -lt $2 ] && echo "$1<$2" [ $1 -eq $2 ] && echo "$1=$2"
练习 判断输入的是否为整数 判断输入的是否为空 expr -z [[ 正则判断 ]]
-
if判断 语法格式:
[ 10 -eq 10 ] && echo ok || echo error if [ 10 -eq 10 ];then echo ok fi
单分支语法: 一个条件 一个结果 [ 你有钱 ] && 我就嫁给你
if [ 你有钱 ];then 我就嫁给你 fi if [ 你有钱 ] then 我就嫁给你 fi 双分支语法: 一个条件 两个结果 if [ 你有钱 ];then [ 你有钱 ] && 我就嫁给你 || 拜拜 我就嫁给你 else 拜拜 fi
多分支语法: 多个条件 多个结果
-
if [ 你有钱 ]; then 我就嫁给你 elif [ 你爸是李刚 ]; then 我也嫁给你 elif [ 你活好 ]; then 我倒贴 elif [ 你在老男孩学习 ]; then 先谈恋爱吧 else 拜拜 fi
-
案例: expr 判断输入的是否为整数
[root@shell ~]# cat count.sh #!/bin/sh expr $1 + $2 &>/dev/null if [ $? -ne 0 ];then echo "请输入两个整数" exit fi
使用$# 判断传参的总个数 if [ $# -ne 2 ] then echo "请输入两个参数" exit fi 使用正则匹配判断输入的是否为整数 if [[ ! $1$2 =~ ^[0-9]+$ ]]; then echo "请输入整数" exit fi 使用-z判断是否为空 read -p "please input hostname: " name if [ -z $name ];then echo "请输入姓名" exit fi
案例: 不同的操作系统版本安装不用的YUM源 获取操作系统版本号:
[root@shell ~]# awk '{print $(NF-1)}' /etc/redhat-release 7.6.1810 [root@oldboyedu-c6 ~]# awk '{print $(NF-1)}' /etc/redhat-release 6.9 [root@shell ~]# cat yum.sh #!/bin/sh #判断网络是否正常 #ping的返回结果使用if判断 os_version=`awk '{print $(NF-1)}' /etc/redhat-release` if [[ $os_version =~ ^7 ]];then
判断wget是否存在
which wget &>/dev/null if [ $? -ne 0 ];then yum -y install wget &>/dev/null if [ $? -eq 0 ];then echo "install wget is ok.............." else echo "请检查你的网络或者源" fi fi mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo elif [[ $os_version =~ ^6 ]];then echo yum6.................. else echo "请检查你的系统版本" fi
if案例: 根据需求安装不同版本的PHP
菜单: 列出当前脚本的功能
[root@shell ~]# cat menu.sh #!/bin/sh while true do echo -e "\t\t\t##################" echo -e "\t\t\t#1. INSTLL PHP7.1#" echo -e "\t\t\t#2. INSTLL PHP7.2#" echo -e "\t\t\t#3. INSTLL PHP7.3#" echo -e "\t\t\t#4. INSTLL PHP7.4#" echo -e "\t\t\t#5. INSTLL MySQL #" echo -e "\t\t\t#6. exit #" echo -e "\t\t\t##################" read -p "请输入需要安装的PHP的序号[1|2|3]: " num if [ $num -eq 1 ];then echo yum7.1.................... elif [ $num -eq 2 ];then echo yum7.2................... elif [ $num -eq 3 ];then echo yum7.3................... elif [ $num -eq 4 ];then echo yum7.4................... elif [ $num -eq 5 ];then echo MySQL................... elif [ $num -eq 6 ];then exit fi done
案例: 使用菜单的方式和if判断 显示当前系统的 内存 CPU负载 登录系统用户 当前的磁盘使用 退出 菜单: echo cat
[root@shell ~]# cat os_menu.sh #!/bin/sh menu(){ cat<<EOF 1 f. 查看系统内存 2 u. 查看CPU负载 3 w. 查看当前用户 4 d. 查看当前磁盘 5 e. exit 6 h. 显示当前菜单 EOF } menu while true do read -p "请输入要查看系统信息的编号或字母[1|f]: " num if [ $num = 1 -o $num = "f" ];then free -h elif [ $num = 2 -o $num = u ];then uptime elif [ $num = 3 -o $num = w ];then w elif [ $num = 4 -o $num = d ];then df -h elif [ $num = 6 -o $num = h ];then menu else exit fi done
案例:猜数字游戏 系统随机生成一个数字, 让用户输入这个数字,如果大了 则提示大 小了则提示小了 相等则提示用户猜对了.
-
生成随机数
[root@shell ~]# echo $((RANDOM%100+1)) 29 [root@shell ~]# cat ran.sh #!/bin/sh ran=`echo $((RANDOM%100+1))` while true do let i++ read -p "请输入你要猜的数字1-100: " num if [ $num -gt $ran ];then echo "你输入的大了" elif [ $num -lt $ran ];then echo "你输入的小了" else echo "恭喜你猜对了" echo "总共猜了 $i 次" exit fi done
case语句 语法格式:
case 变量 in 匹配变量1) 执行命令序列 ;; 匹配变量2) 执行命令序列 ;; 匹配变量3) 执行命令序列 ;; *) 执行命令序列 esac
案例: 查看系统信息
[root@shell ~]# cat case.sh #!/bin/bash menu(){ cat<<EOF d. 查看磁盘 f. 查看内存 w. 查看用户 u. 查看负载 h. 显示菜单 e. 退出脚本 EOF } menu while true do read -p "请输入内容[d|f|w]: " test case $test in d) df -h ;; f) free -h ;; w) w ;; h) clear menu ;; *) echo "请输入菜单显示的系统项" esac done
案例: shell跳板机
[root@shell ~]# cat jumpserver.sh #!/bin/sh . /etc/init.d/functions WEB01=10.0.0.7 WEB02=10.0.0.8 LB01=172.16.1.5 LB02=10.0.0.6 MySQL=10.0.0.51 NFS=10.0.0.31 BACKUP=10.0.0.41 menu1(){ cat<<EOF 1. 运维 2. 开发 3. 测试 4. 菜单 EOF } menu1 menu(){ cat<<EOF 1. WEB01-10.0.0.7 2. WEB02-10.0.0.8 3. LB01-10.0.0.5 4. LB02-10.0.0.6 5. MySQL-10.0.0.51 6. NFS-10.0.0.31 7. 显示菜单 8. 返回上一级菜单 EOF } menu2(){ cat<<EOF 1.MySQL-10.0.0.51 2.LB01-10.0.0.5 3.显示菜单 4.返回上一级菜单 EOF } trap "" INT TSTP HUP#禁止ctrl+c ctrl+d等 while true do read -p "选择你的岗位:[1|2|3]: " jd if [[ ! $jd =~ ^[0-9]+$ ]];then echo "必须输入数字" continue fi if [ $jd -eq 1 ];then while true do let i++ read -p "请输入运维的口令: " op [ -z $op ] && echo "请输入正确的口令,不允许为空" && continue if [ $op = "woxiangjinqu" ];then action "口令正确欢迎运维大神归来" /bin/true break else action "口令不对重新输入!!!" /bin/false if [ $i -ge 3 ];then /root/game.sh fi fi done menu while true do read -p "请输入你要连接的服务器的编号[1|2|3|4菜单]: " num case $num in 1) ssh $WEB01 ;; 2) ssh $WEB02 ;; 3) ssh $LB01 ;; 4) ssh $LB02 ;; 5) ssh $MySQL ;; 7) clear menu ;; houmen) exit ;; 8) menu1 break ;; *) echo "请输入正确的服务器编号" esac done elif [ $jd = 2 ];then while true do read -p "请输入开发的口令: " dev [ -z $dev ] && echo "请输入正确的口令,不允许为空" && continue if [ $dev = "wos" ];then action "口令正确欢迎代码搬运工" /bin/true break else action "口令不对重新输入!!!" /bin/false fi done menu2 while true do read -p "请输入连接的服务器编号:[1|2]: " num2 case $num2 in 1) ssh $MySQL ;; 2) ssh $LB01 ;; 3) clear menu2 ;; 4) men1 break ;; houmen) exit ;; *) echo "请输入正确的编号: [1|2]" esac done elif [ $jd = 4 ];then menu1 fi done
------------------------------精简版----------------------------
[root@shell ~]# cat jump.sh #!/bin/sh . /etc/init.d/functions WEB01=10.0.0.7 WEB02=10.0.0.8 LB01=172.16.1.5 LB02=10.0.0.6 MySQL=10.0.0.51 NFS=10.0.0.31 BACKUP=10.0.0.41 menu(){ cat<<EOF 1. WEB01-10.0.0.7 2. WEB02-10.0.0.8 3. LB01-10.0.0.5 4. LB02-10.0.0.6 5. MySQL-10.0.0.51 6. NFS-10.0.0.31 7. 显示菜单 EOF } menu trap "" INT TSTP HUP while true do read -p "请输入你要连接的服务器的编号[1|2|3|4菜单]: " num case $num in 1|web01) ssh $WEB01 ;; 2|web02) ssh $WEB02 ;; 3|lb01) ssh $LB01 ;; 4) ssh $LB02 ;; 5) ssh $MySQL ;; 7) clear menu ;; houmen) exit ;; *) echo "请输入正确的服务器编号" esac done
-------------------------------------------------------------
案例: nginx启动脚本
[root@shell ~]# /usr/sbin/nginx # nginx启动方式 [root@shell ~]# /usr/sbin/nginx -s stop # Nginx停止 [root@shell ~]# /usr/sbin/nginx -s reload # 重载 [root@shell ~]# /usr/sbin/nginx -s stop && /usr/sbin/nginx # 先停止后启动 [root@shell ~]# cat start_nginx.sh #!/bin/sh . /etc/init.d/functions case $1 in start) if [ `ps axu|grep nginx|grep master|wc -l` -eq 0 ];then /usr/sbin/nginx if [ $? -eq 0 ];then action "Nginx $1 is" /bin/true else action "Nginx $1 is" /bin/false fi else action "Nginx $1 is 已经在运行" /bin/true fi ;; stop) /usr/sbin/nginx -s stop ;; reload) /usr/sbin/nginx -s reload ;; restart) /usr/sbin/nginx -s stop sleep 1 /usr/sbin/nginx ;; status) if [ `ps axu|grep nginx|grep master|wc -l` -eq 0 ];then echo "Nginx is not running......" else pid=`ps axu|grep nginx|grep master|awk '{print $2}'` port=`netstat -tnulp|grep nginx|grep master|grep 80|awk '{print $4}'` echo "当前的Nginx端口号为 $port" echo "当前的NgnxPID号为: $pid" fi ;; *) echo "USAGE: $0 [start|stop|restart|reload|status]" esac
交互脚本expect
1.简单实现免交互登陆 expect是一款自动化的脚本解释型的工具 脚本开头 expect脚本一般以#!/usr/bin/expect 开头,类似bash脚本。 常用后缀 expect脚本常常以.exp或者.ex结束。 expect主要命令 spawn 新建一个进程,这个进程的交互由expect控制 expect 等待接受进程返回的字符串,直到超时时间,根据规则决定下一步操作 send 发送字符串给expect控制的进程 set 设定变量为某个值 set user oldboy exp_continue 重新执行expect命令分支 [lindex $argv 0] 获取expect脚本的第1个参数 [lindex $argv 1] 获取expect脚本的第2个参数 set timeout -1 设置超时方式为永远等待 set timeout 30 设置超时时间为30秒 interact 将脚本的控制权交给用户,用户可继续输入命令 expect eof 等待spawn进程结束后退出信号eof
安装expect
yum -y install expect #!/usr/bin/expect spawn ssh root@10.0.0.5 expect { "yes/no" { send "yes\r"; exp_continue } "password:" { send "centos\r" }; } interact
2.expect定义变量实现交互方式
#!/usr/bin/expect set ip 10.0.0.5 set user root set password centos set timeout 5 spawn ssh $user@$ip expect { "yes/no" { send "yes\r"; exp_continue } "password:" { send "$password\r" }; }
#交互方式 interact 案例
批量获取在线主机, 进行秘钥批量分发
cat for_ip.sh #!/usr/bin/bash #setup1 拿到IP地址 >ip.txt for i in {1..10} do ip=10.0.0.$i { ping -c1 $ip &>/dev/null if [ $? -eq 0 ];then echo "$ip" >> ip.txt fi }& done #2.生成对应的密钥 if [ ! -f ~/.ssh/id_rsa ];then ssh-keygen -P "" -f ~/.ssh/id_rsa fi
#3.批量分发密钥
while read line do /usr/bin/expect << EOF set pass 1 set timeout 2 spawn ssh-copy-id $line -f expect { "yes/no" { send "yes\r"; exp_continue} "password:" { send "1\r"} } expect eof EOF done<ip.txt
for 循环
语法结构: for 变量 in 变量值 变量值可以是数字 字母 变量 命令 do 执行的命令序列 具体的执行动作 可以和变量无关 done
for.sh for i in 框子(苹果 梨子 香蕉 黄瓜) do echo $i 第一个循环: i=苹果 echo 苹果 第二次循环: i=梨子 echo 梨子 第四次循环 i=黄瓜 echo 黄瓜 done [root@shell scripts]# cat for.sh for i in 1 10 20 30 do echo $i done [root@shell scripts]# cat for.sh for i in `seq 10` do echo $i done [root@shell scripts]# cat for.sh for i in `seq 5` do echo ok done
案例: 数字从1加到100 笔试题
[root@shell scripts]# cat for.sh for i in `seq 100` do sum=$[$sum+$i] done echo $sum [root@shell scripts]# cat for.sh for i in $(seq 100) do sum=$(($sum+$i)) or sum=$((sum+i)) done echo $sum [root@shell scripts]# seq -s + 100|bc 5050 扩展使用awk [root@shell scripts]# seq 100|awk '{i=i+$1}END{print i}' 5050 [root@shell scripts]# echo {1..100}| awk -F " " '{for(i=1;i<NF+1;i++){j=j+i;}print j;}' 5050
案例: 批量pin 获取在线的IP地址 能ping通说明在线
10.0.0.0/24 10.0.0.1-10.0.0.254 [root@shell scripts]# cat ping.sh #!/bin/sh for i in {1..254} do { IP=10.0.0.$i ping -c2 -W1 $IP &>/dev/null if [ $? -eq 0 ];then echo "当前 $IP 在线" fi } & done wait echo "完成在线取IP.........."
案例: 批量创建10个用户 用户前缀名oldboy oldboy1 oldboy2 oldboy3 ... oldboy10 要求判断用户是否存在 不存在则创建 存在提示用户已经存在 为每个用户创建123456密码 并且判断密码是否创建成功 使用随机密码 并且把用户名和密码统一输出到文件中
[root@shell scripts]# vim user.sh #!/bin/sh for i in {1..10} do useradd oldboy$i done [root@shell scripts]# cat user.sh #!/bin/bash for i in {1..10} do userdel -r oldboy$i done [root@shell scripts]# cat user1.sh #!/bin/sh for i in {1..10} do id oldboy$i &>/dev/null if [ $? -eq 0 ];then echo "useradd: user oldboy$i already exists" else useradd oldboy$i if [ $? -eq 0 ];then echo "Create oldboy$i is ok" else echo "Create oldboy$i is error" fi fi done [root@shell scripts]# cat user2.sh #!/bin/bash for i in {1..10} do id oldboy$i &>/dev/null if [ $? -eq 0 ];then echo "用户已经存在" else useradd oldboy$i if [ $? -eq 0 ];then echo "Create oldboy$i is ok" else echo "Create oldboy$i is error" fi fi done -------------------------- [root@shell scripts]# cat user.sh #!/bin/sh > passwd.txt for i in {1..10} do pass=`date +%N|md5sum|cut -c1-8` id oldboy$i &>/dev/null if [ $? -eq 0 ];then echo "useradd: user oldboy$i already exists" else useradd oldboy$i if [ $? -eq 0 ];then echo "Create oldboy$i is ok" echo $pass|passwd --stdin oldboy$i &>/dev/null if [ $? -eq 0 ];then echo "设置密码成功" else echo "设置密码失败" fi echo "oldboy$i $pass" >>passwd.txt else echo "Create oldboy$i is error" fi fi done
用户的前缀手动输入 创建的个数手动输入 for循环 结合case语句方式批量创建用户
[root@shell scripts]# cat user1.sh #!/bin/sh read -p "请输入用户的前缀名称: " prefix [ -z $prefix ] && echo "请输入用户前缀" && exit read -p "请输入创建用户的个数: " num if [[ ! $num =~ ^[0-9]+$ ]];then echo "请输入整数" fi for i in `seq $num` do echo $prefix$i done read -p "你确定要创建以上的用户吗?[y|Y|yes|n|N|no]: " re for a in `seq $num` do user=$prefix$a case $re in y|Y|yes) id $user &>/dev/null if [ $? -eq 0 ];then echo "用户存在" else useradd $user &>/dev/null if [ $? -eq 0 ];then echo "用户创建成功" else echo "用户创建失败" fi fi ;; n|N|no) exit ;; *) echo "USAGE: $0 [请输入前缀个数....]" esac done
while循环
语法格式: while 条件表达式 为真则执行 否则不执行 do 命令序列 done
[root@shell scripts]# cat while.sh while true do echo ok sleep 2 done [root@shell scripts]# cat while.sh a=0 while [ $a -le 3 ] do let a++ echo ok done [root@shell scripts]# sh while1.sh ok ok ok ok [root@shell scripts]# cat while1.sh a=1 while [ $a -le 3 ] do let a++ echo ok done [root@shell scripts]# sh while1.sh ok ok ok
使用while循环从1加到100
[root@shell scripts]# cat while.sh a=1 while [ $a -le 100 ] do sum=$[$sum+$a] let a++ done echo $sum
while读取文件
for循环取值方式: 按照空格取值 [root@shell scripts]# cat test.sh #!/bin/sh for i in `cat /etc/hosts` do echo $i done while循环取值方式: 按照行取值 while read line do echo $line done</etc/hosts
案例: 通过读取文件的方式创建用户名
user.txt zs1 old2 tes1 test2 lisi [root@shell scripts]# cat while.sh #!/bin/sh while read line do user=`echo $line|awk '{print $1}'` pass=`echo $line|awk '{print $2}'` useradd $user echo $pass|passwd --stdin $user done<user.txt
流程控制语句 exit 退出当前的脚本 break 跳出本层循环 continue 省略当前循环的后面的代码.从头开始执行
#!/bin/sh while true do echo ok continue echo hehe done echo done............... exit 退出当前整个脚本 [root@shell scripts]# cat while2.sh #!/bin/sh for i in `seq 10` do useradd oldboy$i &>/dev/null if [ $? -eq 0 ];then echo "Create oldboy$i success" else exit fi done break 跳出本层循环 [root@shell scripts]# cat while2.sh #!/bin/sh for i in `seq 10` do useradd oldboy$i &>/dev/null if [ $? -eq 0 ];then echo "Create oldboy$i success" else break fi done echo done................ [root@shell scripts]# sh while2.sh Create oldboy1 success Create oldboy2 success Create oldboy3 success Create oldboy4 success done................ continue [root@shell scripts]# cat while2.sh #!/bin/sh for i in `seq 10` do useradd oldboy$i &>/dev/null if [ $? -eq 0 ];then echo "Create oldboy$i success" else continue fi done echo done................ [root@shell scripts]# sh while2.sh Create oldboy1 success Create oldboy2 success Create oldboy3 success Create oldboy4 success Create oldboy6 success Create oldboy7 success Create oldboy8 success Create oldboy9 success Create oldboy10 success done................
fun函数
1.命令的集合,完成特定功能的代码块 2.便于代码复用 3.和变量类似 只能先定义 在调用,只定义不调用 不执行 函数的定义: [root@shell scripts]# cat fun.sh #!/bin/sh fun(){ echo "函数的第一种定义方式" }
function fun1(){ echo "函数的第二种定义方式" } function fun2 { echo "函数的第三种定义方式" } fun fun1 fun2 [root@shell scripts]# sh fun.sh 函数的第一种定义方式 函数的第二种定义方式 函数的第三种定义方式 函数的传参 函数的传参第一种方式 在函数的后面调用 [root@shell scripts]# cat fun1.sh #!/bin/sh fun(){ if [ -f $1 ];then echo "文件存在" else echo "文件不存在" fi } fun /etc/hosts [root@shell scripts]# sh -x fun1.sh /etc/passwd + fun /etc/hosts + '[' -f /etc/hosts ']' + echo 文件存在 文件存在 [root@shell scripts]# cat fun1.sh #!/bin/sh fun(){ if [ -f $1 ];then echo "$1 文件存在" else echo "$1 文件不存在" fi } fun $2 [root@shell scripts]# sh fun1.sh /etc/passwd /etc/hosts /etc/hosts 文件存在 ------------------------- [root@shell scripts]# cat fun1.sh #!/bin/sh fun(){ if [ -f $1 ];then echo "$1 文件存在" else echo "$1 文件不存在" fi } fun $2 $1 [root@shell scripts]# sh fun1.sh /etc/passwd /etc/hosts /etc/hosts 文件存在 [root@shell scripts]# cat fun1.sh #!/bin/sh fun(){ if [ -f $2 ];then echo "$2 文件存在" else echo "$2 文件不存在" fi } fun $2 $1 [root@shell scripts]# sh fun1.sh /etc/passwd /etc/hosts /etc/passwd 文件存在 函数传参的第二种方式: 调用当前shell中的变量 [root@shell scripts]# cat fun1.sh #!/bin/sh file=$1 fun(){ if [ -f $file ];then echo "$file 文件存在" else echo "$file 文件不存在" fi } fun 函数的本地变量: 只能在函数体中生效 在其他的shell语句中不生效 local file=/etc/hosts [root@shell scripts]# vim fun1.sh #!/bin/sh fun(){ local file=/etc/hosts if [ -f $file ];then echo "$file 文件存在" else echo "$file 文件不存在" fi } fun echo $file 案例: [root@shell scripts]# cat fun2.sh #!/bin/sh num1=10 fun(){ for i in `seq 10` do total=$[$i+$num1] done } fun echo $total [root@shell scripts]# sh fun2.sh 20 注意变量位置 [root@shell scripts]# cat fun2.sh #!/bin/sh fun(){ for i in `seq 10` do total=$[$i+$num1] done } num1=10 fun echo $total [root@shell scripts]# sh fun2.sh 20 [root@shell scripts]# cat fun2.sh #!/bin/sh fun(){ for i in `seq 10` do total=$[$i+$1] done echo $total } fun $1 fun $2 fun $3 [root@shell scripts]# sh fun2.sh 10 20 30 20 30 40 [root@shell scripts]# cat fun2.sh #!/bin/sh fun(){ for i in `seq 10` do total=$[$i+$1] done echo $total } fun $2 $1 $3 [root@shell scripts]# sh fun2.sh 20 30 10 40 函数的返回值: return [root@shell scripts]# cat fun1.sh #!/bin/sh file=$1 fun(){ if [ -f $file ];then return 50 else return 100 fi } fun echo $? [root@shell scripts]# sh fun1.sh /etc/hostssss 100 [root@shell scripts]# sh fun1.sh /etc/hosts 50 [root@shell scripts]# cat fun1.sh #!/bin/sh file=$1 fun(){ if [ -f $file ];then return 50 else return 100 fi } fun if [ $? -eq 50 ];then echo "文件存在" else echo "文件不存在" fi [root@shell scripts]# cat fun1.sh #!/bin/sh file=$1 fun(){ echo 50 return 100 } num=`fun` echo "当前函数的返回值为: $?" echo "当前函数的执行结果为: $num" [root@shell scripts]# sh fun1.sh /etc/hosts 当前函数的返回值为: 100 当前函数的执行结果为: 50 [root@shell scripts]# cat fun1.sh #!/bin/sh file=$1 fun(){ echo 50 return 100 } num=`fun` name=oldboy echo "当前函数的返回值为: $?" echo "当前函数的执行结果为: $num" [root@shell scripts]# sh fun1.sh /etc/hosts 当前函数的返回值为: 0 当前函数的执行结果为: 50 函数在其他shell中调用 类似变量 [root@shell scripts]# cat fun.sh /bin/sh fun(){ echo "函数的第一种定义方式" } function fun1(){ echo "函数的第二种定义方式" } function fun2 { echo "函数的第三种定义方式" } [root@shell scripts]# fun -bash: fun: command not found [root@shell scripts]# . fun.sh [root@shell scripts]# fun 函数的第一种定义方式 [root@shell scripts]# fun1 函数的第二种定义方式 [root@shell scripts]# fun2 函数的第三种定义方式
数组
1.数组的分类
普通数组
只能使用整数来定义
关联数组
可使用数字和字符来定义
变量:存储单个元素的内存空间 数组:存储多个元素的连续的内存空间,相当于多个变量的集合 数组名和索引 索引:编号从0开始,属于数值索引 注意:索引可支持使用自定义的格式,而不仅是数值格式,即为关联索引,bash4.0版本之后开始支持 bash的数组支持稀疏格式(索引不连续) 声明数组:
declare -a ARRAY_NAME declare -A ARRAY_NAME 关联数组
注意:两者不可相互转换 高级变量用法-有类型变量 Shell变量一般是无类型的,但是bash Shell提供了declare和typeset两个命令用于指定变量的类型,两个命令是等价的 declare [选项] 变量名 -r 声明或显示只读变量 -i 将变量定义为整型数 -a 将变量定义为数组 -A 将变量定义为关联数组 -f 显示已定义的所有函数名及其内容 -F 仅显示已定义的所有函数名 -x 声明或显示环境变量和函数 -l 声明变量为小写字母 declare –l var=UPPER -u 声明变量为大写字母 declare –u var=lower
间接变量引用
bash Shell提供了两种格式实现间接变量引用 eval tempvar=\$$variable1 tempvar=${!variable1} 示例:
[root@server ~]# N=NAME [root@server ~]# NAME=wangxiaochun [root@server ~]# N1=${!N} [root@server ~]# echo $N1 wangxiaochun [root@server ~]# eval N2=\$$N [root@server ~]# echo $N2 wangxiaochun
创建临时文件
mktemp命令:创建并显示临时文件,可避免冲突 mktemp [OPTION]... [TEMPLATE] TEMPLATE: filenameXXX X至少要出现三个 OPTION: -d: 创建临时目录 -p DIR或--tmpdir=DIR:指明临时文件所存放目录位置 示例: mktemp /tmp/testXXX tmpdir=`mktemp –d /tmp/testdirXXX` mktemp --tmpdir=/testdir testXXXXXX
安装复制文件
install命令: install [OPTION]... [-T] SOURCE DEST 单文件 install [OPTION]... SOURCE... DIRECTORY install [OPTION]... -t DIRECTORY SOURCE... install [OPTION]... -d DIRECTORY...创建空目录 选项: -m MODE,默认755 -o OWNER -g GROUP 示例:
install -m 700 -o wang -g admins srcfile desfile install –m 770 –d /testdir/installdir
2.数组的格式
变量名称=变量的值 数组名称[索引]=(元素的值)
3.定义数组
第一种定义方式
索引定义
默认索引从0开始
[root@shell scripts]# array[1]=cm [root@shell scripts]# array[5]=zs [root@shell scripts]# array[6]=ls
第二种定义方式
一次性定义多个值
[root@shell scripts]# array=(ll lz lw lz)
第三种定义方式
混合定义的方式
[root@shell scripts]# array=(qq aa cc [20]=tt [22]=yy)
第四种定义方式
命令定义
[root@shell scripts]# ls /tmp/ nging.pid vmware-root_6223-1681855427 [root@shell scripts]# array=(`ls /tmp`)
4.调用数组 declare -a 查看当前定义所有的普通数组
[root@shell scripts]# echo $array # 默认只能查看下标为0的值 mysql [root@shell scripts]# [root@shell scripts]# echo ${array[*]} # 查看数组中的所有内容 mysql shell redis [root@shell scripts]# echo ${array[@]} mysql shell redis [root@shell scripts]# echo ${!array[@]} # 查看所有的下标 0 1 2 [root@shell scripts]# echo ${array[1]} # 通过下标查看值 shell [root@shell scripts]# echo ${array[2]} redis [root@shell scripts]# echo ${array[0]} mysql [root@shell scripts]# echo ${#array[*]} # 查看元素的个数 3
案例: 判断当前公司中几台服务器的IP地址是否能ping通
61.135.169.121 123.126.55.41 10.0.0.254 8.8.8.8
定义数组:
[root@shell scripts]# IP=(61.135.169.121 123.126.55.41 10.0.0.254 8.8.8.8)
获取数组的值:
[root@shell scripts]# echo ${IP[*]} 61.135.169.121 123.126.55.41 10.0.0.254 8.8.8.8
for循环方式去ping每个IP
[root@shell scripts]# for i in ${IP[*]};do echo $i;done 61.135.169.121 123.126.55.41 10.0.0.254 8.8.8.8 [root@shell scripts]# for i in ${IP[*]};do ping -c2 -W1 $i;done [root@shell scripts]# cat array.sh #!/bin/sh IP=( 61.135.169.121 123.126.55.41 10.0.0.254 8.8.8.8 10.0.0.51 ) for i in ${IP[*]} do ping -c2 -W1 $i &>/dev/null if [ $? -eq 0 ];then echo "ping $i is ok............" else echo "ping $i is error........." fi done
关联数组: 下标可以使用数字和字符串 声明关联数组: declare -A 数组名称 使用下标方式定义数组
[root@shell scripts]# array[index1]=redis [root@shell scripts]# array[index2]=mysql [root@shell scripts]# array[index3]=shell [root@shell scripts]# echo ${array[*]} redis mysql shell
第二种方式定义数组
[root@shell scripts]# array=([0]=zabbix [index4]=kvm [index5]=mongo) [root@shell scripts]# echo $array[*] zabbix[*] [root@shell scripts]# echo ${array[*]} kvm mongo zabbix [root@shell scripts]# echo ${!array[*]} index4 index5 0
关联数组案例: 统计文件中性别出现的次数
cat sex.txt m f f m m m x [root@shell scripts]# sort sex.txt|uniq -c 2 f 4 m 1 x [root@shell scripts]# sort sex.txt|uniq -c|sort -rn 4 m 2 f 1 x [root@shell scripts]# cat sex.txt |uniq -c|sort -rn 3 m 2 f 1 x 1 m [root@shell scripts]# cat sex.txt |sort f f m m m m x [root@shell scripts]# cat sex.txt |sort |uniq -c 2 f 4 m 1 x [root@shell scripts]# cat sex.txt |sort |uniq -c|sort -rn 4 m 2 f 1 x [root@shell scripts]# cat s.sh for i in `cat sex.txt` do if [ $i = m ];then let m++ elif [ $i = f ];then let f++ else let x++ fi done echo "m出现了 $m 次" echo "f出现了 $f 次" echo "x出现了 $x 次" [root@shell scripts]# sh s.sh m出现了 4 次 f出现了 2 次 x出现了 1 次 -------------------------- [root@shell scripts]# let a++ [root@shell scripts]# echo $a 1 [root@shell scripts]# let array[b]++ [root@shell scripts]# echo ${array[b]} 1 [root@shell scripts]# let a++ [root@shell scripts]# let b++ [root@shell scripts]# let b++ [root@shell scripts]# let a++ [root@shell scripts]# let array[b]++ [root@shell scripts]# let array[a]++ [root@shell scripts]# let array[a]++ [root@shell scripts]# [root@shell scripts]# echo $a 3 [root@shell scripts]# echo ${array[b]} 2 [root@shell scripts]# echo ${array[a]} 2 ----------------------------- cat s.sh # 执行过程 declare -A sex for i in `cat sex.txt` do let sex[$i]++ -------------- • i=m • let sex[m]++ --- • i=f • let sex[f]++ --- • i=m • let sex[m]++ • done • echo "m出现了 $m 次" • echo "f出现了 $f 次" • echo "x出现了 $x 次" [root@shell scripts]# cat s.sh • declare -A sex • for i in `cat sex.txt` • do • let sex[$i]++ • done • echo "m出现了 ${sex[m]} 次" • echo "f出现了 ${sex[f]} 次" • echo "x出现了 ${sex[x]} 次" • [root@shell scripts]# sh s.sh • m出现了 4 次 • f出现了 2 次 • x出现了 1 次 遍历数组: [root@shell scripts]# cat s.sh declare -A sex for i in `cat sex.txt` do let sex[$i]++ done #echo "m出现了 ${sex[m]} 次" #echo "f出现了 ${sex[f]} 次" #echo "x出现了 ${sex[x]} 次" for a in ${!sex[*]} do echo "$a 出现了 ${sex[$a]} 次" done [root@shell scripts]# sh s.sh f 出现了 2 次 m 出现了 4 次 x 出现了 1 次