Author: liuchao
Email: mirschao@163.com
Github: https://github.com/mirschao
云计算脱产班: Shell任务自动化编程实战一、Shell含义诠释1.1 什么是shell1.2 什么是shell脚本?二、Shell基本语法2.1 变量 variable2.2 流程控制之 if2.3 流程控制之 case2.4 循环控制 for2.5 数据结构 array2.6 函数 function2.7 Expect自动应答三、Shell文本处理3.1 正则表达式3.1.1 正则表达式速查手册之 校验数字表达式3.1.2 正则表达式速查手册之 校验字符的表达式3.1.3 正则表达式速查手册之 特殊需求表达式3.2 sed文本修改3.3 awk文本切割3.3.1 日志处理速查手册
shell本意指“壳”, 但在IT领域中多指 “指挥操作系统完成任务执行或信息展示的指令集”, 就比如 Windows中的PowerShell、macOS中的zsh、Linux操作系统中的shell等都是用来控制操作系统的指令集; 通过 类shell指令可完成在无操作界面的系统上完成任务发布和任务部署, 熟练编写指令以及指令集文件(脚本)也是运维工程师必备的技能之一;
对于shell而言几乎是占领了所有服务端操作系统的交互式命令行; 比如在CentOS上使用的ls
, cd
等都是属于shell指令集的, 使用它们可以完成特定的任务以及展示特定的信息, 而这些指令又可以配合选项、参数来完成更加复杂的操作, 这就形成了丰富的shell指令集, 让运维工程师能够使用命令行完成更加复杂高级的操作;
shell脚本指的是: “一个存储有逻辑、有顺序的多个命令的文本文件”; 对于一些比较复杂的任务, 通常需要使用很多条指令来完成, 并在执行时要保障执行的顺序, 以及执行的逻辑; 而且对于命令的选型也是有严格要求的, 为了更加顺滑的执行要避免使用具备交互属性的指令;
For Example PR: 本公司前端开发框架采用 Vue
, 前端开发人员在研发过程中经常需要对项目进行打包构建并运行测试, CTO为简化研发过程中的测试难度, 需要你编写一个脚本来帮助研发一键执行测试.
x#> 需求分解:
#> 1. 询问研发在编码完成后如何进行构建打包?
#> 2. 询问研发在打包完成后需要上传到什么环境上测试?
#> 3. 询问研发是否有固定的测试环境?若没有, 请问如何测试?
#> 例如得到的回应: 打包按照vue自带的方式进行, 需要在nginx上测试, 有个固定的开发环境是172.16.121.13
#> 你应该做的:
#> 1. 查询vue的官网中对vue项目的打包构建方式以及命令
#> 2. 查询nginx如何部署vue项目
#> 3. 如何部署nginx
#> 4. 开始尝试编写脚本, 并解决其中遇到的问题
对于在shell脚本中, 经常使用的就是变量了, 变量是由变量名和值组成的, 变量名字可以被赋予不同的值, 想要在脚本中调用该值, 可使用变量名进行调用; 变量声明的方式为 NAME=VALUE
, 注意等号两边是没有空格的, 此处严格要求; 调用值时使用 ${NAME}
进行调用, 大括号可有效避免其他字符“混入”变量名中;
xxxxxxxxxx
#
# coding: utf-8
# author: liuchao
# email: mirs_chao@163.com
# usage: define variable.
#> 普通赋值
varA="hello world"
echo ${varA}
#> 命令执行结果赋值 $() 可替换为 ``
varB=$(date +%Y%m%d%H%M%S)
echo ${varB}
#> 读取键入值赋值
read -p "please input: " varC
echo ${varC}
在shell脚本中, 由于要顾及到文件的执行逻辑性, 所以需要使用到条件判断来增加脚本的逻辑性; 在生产中单分支条件判断使用的频次是较为突出的, 多分支在流程控制上使用的较为突出, 但从整体代码易读性而言单分支条件判断配合取反思想可以达到意想不到的效果;
xxxxxxxxxx
if condition; then
statement(s)
fi
上述代码框架中的segment可以继续编写if条件判断, 但值得注意的是需要将框架写全, 也就是注意整体性; CONDITION所编写的表达式必须返回True/False这两种结果; CONDITION经常使用 []
来获取命令以及表达式的布尔值结果从而重定向到对应的分支中, 来形成流程上的控制;
xxxxxxxxxx
#
# coding: utf-8
# author: liuchao
# email: mirs_chao@163.com
# usage: if condition segment.
IPADDRESS="10.9.12.34"
ping -c3 ${IPADDRESS} &>/dev/null
if [ $? -eq 0 ];then
echo "ping successfully of ${IPADDRESS}"
fi
在上述的代码片段中, 涉及到变量的定义和if条件判断的框架; 但对于双引号中调用变量会有的一个疑问是: 双引号和单引号有没有区别呢?答案是肯定的; 单引号中不能调用变量, 因为单引号是强引, 所以并不会解释调用变量的字符$
; 对于条件判断中的 []
符号, 是用来将其内部表达式的布尔值取出来的;
表达式中的$?
是用来获取上条命令执行的状态返回码用的, 当返回0时即为正确, 返回非0时即为错误。特殊符号还有很多比如:
$0
:代表当前执行文件的文件名称, 当然如果是带路径执行的会把路径也返回出来;
$#
:代表当前脚本执行时增加的参数个数, 是个数字;
$$
:代表当前脚本执行时所分配的pid号;
$@
:代表传递给脚本或者函数的所有参数;
$n
:代表位置参数, 1 <= n <= 正无穷
;
当然还包含 -eq
这样的比较运算符, 用来比较数字的大小, 不能被用来比较字符串等其他字符, 类似的符号还有:
-ne
:不等于;
-gt
:大于;
-lt
:小于;
-ge
:大于等于;
-le
:小于等于;
如果想判断文件或者目录存在与否, 可以按照下方的写法进行逻辑判断:
[ -f FILENAME ]
用来判断单个文件是否存在;
[ -d DIRNAME ]
用来判断目录是否存在;
xxxxxxxxxx
#
# coding: utf-8
# author: liuchao
# email: mirs_chao@163.com
# usage: if condition segment of file exist or not.
FILENAME='example.txt'
if [ -f ${FILENAME} ];then
echo "this file is exist"
else
echo "this file is not exist"
fi
# 生产中处理问题:
if [ ! -f ${FILENAME} ];then
touch ${FILENAME}
echo "该文件经检测不存在, 但已被创建在当前目录下"
fi
作业:
编写一个脚本, 要求能够实现对软件的识别, 例如netstat指令识别, 如果系统中不存在该指令则安装net-tools软件包;
编写一个脚本, 要求能够实现对指定目录的判断(是否存在), 不存在则创建且要输出提示;
使用if流程控制也可以实现多条件判断, 但会使代码显得臃肿不易阅读, 所以大多数时候编写shell脚本都配合单分支及取反思想来实现判断需求; 而case是天生的多条件判断分支模型, 当分支较多并且判断条件比较简单时使用 case in 语句就比较方便了;
xxxxxxxxxx
case expression in
pattern1)
statement1
;;
pattern2)
statement2
;;
pattern3)
statement3
;;
……
*)
statementn
esac
case、in 和 esac 都是 Shell 关键字, expression 表示表达式, pattern 表示匹配模式:
expression 既可以是一个变量、一个数字、一个字符串, 还可以是一个数学计算表达式, 或者是命令的执行结果, 只要能够得到 expression 的值就可以;
pattern 可以是一个数字、一个字符串, 甚至是一个简单的正则表达式;
case 会将 expression 的值与 pattern1、pattern2、pattern3 逐个进行匹配:
如果 expression 和某个模式(比如 pattern2)匹配成功, 就会执行这模式(比如 pattern2)后面对应的所有语句(该语句可以有一条, 也可以有多条), 直到遇见双分号;;
才停止; 然后整个 case 语句就执行完了, 程序会跳出整个 case 语句, 执行 esac 后面的其它语句;
如果 expression 没有匹配到任何一个模式, 那么就执行*)
后面的语句(*
表示其它所有值), 直到遇见双分号;;
或者esac
才结束。*)
相当于多个 if 分支语句中最后的 else 部分;
对*)
的几点说明:
case in 语句中的*)
用来“托底”, 万一 expression 没有匹配到任何一个模式, *)
部分可以做一些“善后”工作, 或者给用户一些提示;
可以没有*)
部分。如果 expression 没有匹配到任何一个模式, 那么就不执行任何操作;
除最后一个分支外(这个分支可以是普通分支, 也可以是*)
分支), 其它的每个分支都必须以;;
结尾, ;;
代表一个分支的结束, 不写的话会有语法错误。最后一个分支可以写;;
, 也可以不写, 因为无论如何, 执行到 esac 都会结束整个 case in 语句。
case经常搭配getops
指令实现自定义命令的编写, 可编写出和Linux中自建命令相同的使用效果; getopts命令是用来解析Shell脚本命令行参数的工具, getopts命令参数包含需要被识别的选项字符, 如果选项字符后面跟着一个冒号, 则表明该字符选项需要一个命令行参数(选项字符与对应的命令行参数之间以空格分隔)(注意:冒号&问号不能被用作为选项字符)。getopts命令每次被调用时, 它会将下一个选项字符放置到变量中, OPTARG
则可以拿到参数值;如果option前面加冒号, 则代表忽略错误
xxxxxxxxxx
#
# coding: utf-8
# author: liuchao
# email: mirs_chao@163.com
# usage: display command.
HELPINFO="
用法: createvm [hapn]... \n
Create a virtual machine and create a fixed \n
IP address, vnc port, virtual machine name \n
\t -h, --help 获取帮助信息\n
\t -a, --address 验证IP地址\n
\t -p, --port 验证端口\n
\t -n, --name 设置名称\n
退出状态:\n
\t 0 正常\n
\t 1404 一般问题 (例如:没有对应的选项)\n
\t x403 严重问题 (例如:设置参数不正确)\n
"
options=$(getopt -l "help,autostart,address:,port:,name:" -o "h:a:p:n:" -a -- "$@")
if [ $? -ne 0 ];then
exit 1404
fi
eval set -- "${options}"
while true;do
case $1 in
-h|--help)
echo -e ${HELPINFO}
exit 0
;;
-a|--address)
ping -c3 $2 &>/dev/null
if [ $? -eq 0 ];then
echo "$2 该地址好像在网络中的另外一台终端中正在使用, 请尝试其他IP地址"
exit 1403
fi
ADDRESS=$2
;;
-p|--port)
ss -anptu | grep ":$2" &>/dev/null
if [ $? -eq 0 ];then
echo "$2 该端口正在使用中, 请更换端口继续"
exit 2403
fi
PORT=$2
;;
-n|--name)
grep $2 /etc/passwd &>/dev/null
if [ $? -eq 0 ];then
echo "$2 已经存在, 请更换其他名字"
exit 3403
fi
NAME=$2
;;
--)
shift
break
;;
esac
shift
done
echo "ipaddress: ${ADDRESS}"
echo "port: ${PORT}"
echo "name: ${NAME}"
作业:
编写一个命令式脚本, 要求根据指定的选项, 实现探测指定IP地址是否活跃、实现探测端口是否活跃、实现帮助信息输出的功能;
编写一个命令式脚本, 要求根据指定的选项, 实现对指定目录的判断是否存在, 实现对指定文件的判断是否存在;
思考题: 如何像使用 ls
一样使用上述编写的脚本, 即在任意位置均可使用(形式: scripts_name options parameter);
在shell脚本中, 经常要处理很多重复的动作和任务, 并在每次处理的时候根据不同的结果要导向不同的处理方式, 也就是说for、while这类循环控制通常都会在循环体中使用if或case来进行流程上的控制; 循环可以使机器帮助程序员完成重复且复杂的任务, 提高生产效率的同时又能保证其精确性;
xxxxxxxxxx
#> Frame One:
for i in {1..n}; do
segment(s)
done
#> Frame Two:
for ((i=0; i<n; i++)); do
segment(s)
done
在for循环中处理重复任务时, 通常会在得到结果时又有后续的循环未执行, 此时却需要停下来, 那么这种情况就需要到break
指令了; 在for循环中处理任务的过程中, 某些循环内容不符合本次处理的规范, 所以要跳过这次循环, 那么就需要用continue
指令了;
xxxxxxxxxx
#
# coding: utf-8
# author: liuchao
# email: mirs_chao@163.com
# usage: stop & jump
for (( i=0; i<=99; i++ )); do
if (( i % 17 == 3 )); then
# continue
echo ${i}
break
fi
# echo ${i}
done
在for循环中, 由于需要重复的处理循环体内的任务, 但有个缺点是下一个循环体开始要等待上一个循环体结束, 所以当循环次数少时, 时间还可以接受, 但当循环次数增加, 则执行完整个循环的时间就会呈指数增加; 为了避免掉时间维度的问题就需要使用到异步方式进行循环, 以此来缩短整体循环的执行时间; 这也是常说的宏观层面的并发执行;
xxxxxxxxxx
#
# coding: utf-8
# author: liuchao
# email: mirs_chao@163.com
# usage: Concurrency options.
FIFOFILE='/tmp/pipeline'
CONCURRENCY_NUMBER=${1}
if [ ! -f ${FIFOFILE} ]; then
mkfifo ${FIFOFILE}
exec 6<>${FIFOFILE}
rm -rf ${FIFOFILE}
fi
# generic token bucket
for (( i=1; i<=${CONCURRENCY_NUMBER}; i++ )); do
echo "${i}" >&6
done
# concurrency option
START_TIME=$(date +%s)
for host in 10.9.12.{2..254}; do
read -u 6
{
ping -c3 ${host} &>/dev/null
if [ $? -eq 0 ]; then
echo "${host}" >>/tmp/onlineAddress.txt
fi
echo >&6
}&
done
wait
END_TIME=$(date +%s)
exec 6>&-
let TOTAL_TIME=${END_TIME}-${START_TIME}
echo "Finish!!! totalTime: ${TOTAL_TIME}"
数组是能够存储任意数据的容器, 使用数组可以很好的将不同类型的数据组织在一起, 且易于使用; 数组的声明 array=(A B C)
数组中每个元素使用空格分割, 获取数组中的单个元素使用 $array[n]
n的取值范围为 0 <= n <= ${#array[@]}-1
;
xxxxxxxxxx
#
# coding: utf-8
# author: liuchao
# email: mirs_chao@163.com
# usage: array define and opera
array=('192.168.121.23' '192.168.121.45' '192.168.121.67' '192.168.121.89')
#> array len:
echo ${#array[@]}
#> array element:
for element in ${array[*]}; do
echo ${element}
done
#> array element of index:
for (( i=0; i<${#array[@]}; i++ )); do
echo ${array[${i}]}
done
#> add element:
array[4]='192.168.121.11'
#> del element:
unset array[4]
#> modify element:
array[0]='10.9.14.12'
在shell中上述的一维数组有时并不满足关联需求, 比如统计某个词在文章中出现的次数, 这就需要用到关联数组了; 关联数组的定义形式: declare -A array
; 此形式可声明一个关联数组, 在关联数组中有 键值对
键在关联数组中唯一, 值不唯一; 每个键可对应一个值;
xxxxxxxxxx
#
# coding: utf-8
# author: liuchao
# email: mirs_chao@163.com
# usage: declare array define and opera
declare -A array
#> add key-value:
array['name']='tom'
array['sex']='man'
array['age']='18'
array['hight']='178'
#> del key-value:
none
#> modify key-value:
array['name']='tony'
#> select key-value:
for key in ${!array[@]}; do
echo "$key: ${array[${key}]}"
done
在编写shell脚本时, 经常会在不同的位置使用相同的代码来实现数据的处理, 这时如果将相同的代码编写两次, 就会造成代码的冗余性增强, 不易于阅读, 且在不同文件中也会存在这样的问题; 如果使用函数就能很方便的解决这个问题, 让代码更加易于阅读, 且结构更加清晰;
xxxxxxxxxx
function name() {
statements
[return value]
}
在同一个文件中, 在不同位置调用函数:
xxxxxxxxxx
#
# coding: utf-8
# author: liuchao
# email: mirs_chao@163.com
# usage: define function.
function helloworld() {
echo "hello world"
}
helloworld
helloworld
helloworld
构建函数库, 在不同文件中调用函数:
xxxxxxxxxx
[root@localhost tmp]# tree
.
├── main.sh
└── source_library.sh
#
# coding: utf-8
# author: liuchao
# email: mirs_chao@163.com
# usage: function library
function helloworld() {
echo "hello world"
}
function hellokitty() {
echo "hello kitty"
}
function hellodog() {
echo "hello dog"
}
#
# coding: utf-8
# author: liuchao
# email: mirs_chao@163.com
# usage: opera source_library.sh's function.
source ${PWD}/source_library.sh
helloworld
hellokitty
hellodog
函数传参:
xxxxxxxxxx
#
# coding: utf-8
# author: liuchao
# email: mirs_chao@163.com
# usage: function translate paramter
function show(){
echo "Tutorial: $1"
echo "URL: $2"
echo "Author: "$3
echo "Total $# parameters"
}
show shell http://www.hiops.icu/csharp/ Tom
函数作用域:
xxxxxxxxxx
#
# coding: utf-8
# author: liuchao
# email: mirs_chao@163.com
sum=100
function getsum(){
local sum=0
for n in $@; do
((sum+=n))
done
echo $sum
}
total=$(getsum 10 20 55 15)
echo $total
对于在shell脚本中经常不可避免的需要对系统的提示进行手动键入, 这时在遇到系统键入提示时, 就需要使用自动应答函数来实现对系统键入提示的自动输入, 既可以提高效率又可以避免重复劳动;
xxxxxxxxxx
#
# coding: utf-8
# author: liuchao
# email: mirs_chao@163.com
IPADDRESS='10.9.12.12'
PASSWD='MirsChao123!!'
if ! which expect &>/dev/null; then
yum -y install expect
fi
/bin/expect <<-EOF
set timout 3
spawn ssh-copy-id ${IPADDRESS}
expect "yes/no" { send "yes\r" }
expect "password:" { send "${PASSWD}\r" }
expect eof
EOF
正则表达式是编程语言中最重要的一个学习环节, 也是晋升高级开发的必经之路, 正则表达式可以帮助开发人员尽可能多的匹配到符合条件的数据, 在shell中经常与 grep
搭配使用来过滤出符合条件的数据; 对于正则表达式而言在任何语言中都是通用的, 包含元字符和拓展元字符两部分;
推荐两款用于学习 正则表达式的工具 devtoys
客户端 以及 iHateRegex
网站;
文本定位符 | 含义 | 例子 | 说明 |
---|---|---|---|
^ | 行首 | ^abc | 匹配以abc开头的行 |
$ | 行尾 | abc$ | 匹配以abc结尾的行 |
< | 单词开头 | \<is | 仅在单词开头时匹配is |
> | 单词末尾 | on\> | 仅在单词结尾匹配on |
搜索文本时,仅根据字符分类来选择字符会很有用。字符的基本类别是数字和字母和其他字符(例如空格和标点符号)
字符 | 含义 | 例子 | 说明 | |
---|---|---|---|---|
. | 任意单个字符 | a.b | 匹配 “a任意一个字符b” | |
\d | 任意单个数字(在使用grep时需加P参数) [0-9] | a\db | 匹配 “a任意一个数字b” | |
\D | 任意单个非数字(在使用grep时需加P参数) | a\Db | 匹配 “a任意一个标点或特殊字符或下划线b” | |
\w | 任意单个字母、数字、下划线 | a\wb | ...... | |
\s | 任意单个空白字符(空格、制表符) | a\sb | ...... |
匹配字符的数量,例如你可以精确匹配六个空格,或定位长度在四到八位之间的数字、字符串
字符 | 含义 | 例子 | 说明 |
---|---|---|---|
* | 前导符零个到多个 | hlio*ns | 匹配hlions、hlins、hliooooons |
{m} | 前导符出现m次 | hlio{3}ns | 匹配hliooons |
{m,} | 前导符至少出现m次 | hlio{3, }ns | 匹配hliooons、hliooooons、hlioooooons |
{m, n} | 前导符至少出现m次, 至多出现n次 | hlio{3,5}ns | 匹配hliooons、hlioooons、hliooooons |
? | 前导符出现0次或1次 | hlio?ns | 匹配hlions、hlins |
+ | 前导符出现1次或多次 | hlio+ns | 匹配hlions、hliooooooooooons |
字符集是符合搜索条件的字符的列表,通过将一组字符括在方括号中来指示字符集。
字符范围是字符集的一种它在字符之间使用 - 来暗示它们之间的整个字符范围,以及开始和结束字符本身
字符 | 含义 | 例子 | 说明 |
---|---|---|---|
[abc] | 方括号中任意一个字符 | [abcefg] | 匹配abcefg中任意一个字符 |
[^abc] | 除了方括号中任意一个字符 | [^abcefg] | 匹配除了abcefg中的人一个字符(取反) |
[0-9] | 0到9共十个阿拉伯数字 | [0-9] | 匹配0~9范围内的数 0<=x <=9 |
[a-zA-Z] | 匹配a~z或A~Z范围内的一个字符 | [a-zA-Z] | ...... |
(acd|b) | 匹配a或者b (a&b) | (a|b) | 小括号的意义: 分组, 被小括号扩起来的两个逻辑条件为一个整体 |
最后说一下如果想匹配正则表达式中的特殊字符,比如 \, ^, *, {, [ 等,因为它们有特殊含义,所以需要另外加 \ 来表示,比如 * 表示 \*,\ 表示 \\
匹配所有的IP地址
xxxxxxxxxx
1-255.0-255.0-255.0-255
一位数: [0-9]
两位数: [1-9][0-9]
三位数: 1[0-9]{2} 2[0-4][0-9] 25[0-5]
([1-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])
匹配11位电话号
xxxxxxxxxx
13
15
16
17
18
19
^1(3|[5-9])[0-9]{9}$
匹配年月日日期 格式2018-08-06
匹配长度为8-10位的用户密码 : 包含数字字母下划线
数字:^[0-9]*$
n位的数字:^\d{n}$
至少n位的数字:^\d{n,}$
m-n位的数字:^\d{m,n}$
零和非零开头的数字:^(0|[1-9][0-9]*)$
非零开头的最多带两位小数的数字:^([1-9][0-9]*)+(.[0-9]{1,2})?$
带1-2位小数的正数或负数:^(\-)?\d+(\.\d{1,2})?$
正数、负数、和小数:^(\-|\+)?\d+(\.\d+)?$
有两位小数的正实数:^[0-9]+(.[0-9]{2})?$
有1~3位小数的正实数:^[0-9]+(.[0-9]{1,3})?$
非零的正整数:^[1-9]\d*$ 或 ^([1-9][0-9]*){1,3}$ 或 ^\+?[1-9][0-9]*$
非零的负整数:^\-[1-9][]0-9"*$ 或 ^-[1-9]\d*$
非负整数:^\d+$ 或 ^[1-9]\d*|0$
非正整数:^-[1-9]\d*|0$ 或 ^((-\d+)|(0+))$
非负浮点数:^\d+(\.\d+)?$ 或 ^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$
非正浮点数:^((-\d+(\.\d+)?)|(0+(\.0+)?))$ 或 ^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$
正浮点数:^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$ 或 ^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$
负浮点数:^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$ 或 ^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$
浮点数:^(-?\d+)(\.\d+)?$ 或 ^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$
汉字:^[\u4e00-\u9fa5]{0,}$
英文和数字:^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$
长度为3-20的所有字符:^.{3,20}$
由26个英文字母组成的字符串:^[A-Za-z]+$
由26个大写英文字母组成的字符串:^[A-Z]+$
由26个小写英文字母组成的字符串:^[a-z]+$
由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$
由数字、26个英文字母或者下划线组成的字符串:^\w+$ 或 ^\w{3,20}
中文、英文、数字包括下划线:^[\u4E00-\u9FA5A-Za-z0-9_]+$
中文、英文、数字但不包括下划线等符号:^[\u4E00-\u9FA5A-Za-z0-9]+$ 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$
可以输入含有^%&',;=?$\"
等字符:[^%&',;=?$\x22]+
禁止输入含有~的字符[^~\x22]+
Email地址:^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$
域名:[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.?
InternetURL:[a-zA-z]+://[^\s]* 或 ^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$
手机号码:^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$
电话号码("XXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"和"XXXXXXXX):^(\(\d{3,4}-)|\d{3.4}-)?\d{7,8}$
国内电话号码(0511-4405222、021-87888822):\d{3}-\d{8}|\d{4}-\d{7}
身份证号(15位、18位数字):^\d{15}|\d{18}$
短身份证号码(数字、字母x结尾):^([0-9]){7,18}(x|X)?$ 或 ^\d{8,18}|[0-9x]{8,18}|[0-9X]{8,18}?$
帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$
密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):^[a-zA-Z]\w{5,17}$
强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间):^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$
日期格式:^\d{4}-\d{1,2}-\d{1,2}
一年的12个月(01~09和1~12):^(0?[1-9]|1[0-2])$
一个月的31天(01~09和1~31):^((0?[1-9])|((1|2)[0-9])|30|31)$
sed是个流编辑器, 可以修改文件中的内容, 按照增删改查顺序学习
xxxxxxxxxx
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
298374837483
172.16.0.254
10.1.1.1
xxxxxxxxxx
#> 查
[root@localhost ~]# sed -n '1p' a.txt 打印第1行
[root@localhost ~]# sed -n '1,5p' a.txt 打印1到5行
[root@localhost ~]# sed -n '$p' a.txt 打印最后1行
xxxxxxxxxx
#> 增
[root@localhost ~]# sed -i '$a99999' a.txt 文件最后一行下面增加内容
[root@localhost ~]# sed 'a99999' a.txt 文件每行下面增加内容
[root@localhost ~]# sed '5a99999' a.txt 文件第5行下面增加内容
[root@localhost ~]# sed '$i99999' a.txt 文件最后一行上一行增加内容
[root@localhost ~]# sed 'i99999' a.txt 文件每行上一行增加内容
[root@localhost ~]# sed '6i99999' a.txt 文件第6行上一行增加内容
[root@localhost ~]# sed '/^bin/ihello' a.txt 以bin开头行的上一行插入内容
sed -i '/^$/a9999' a.txt
xxxxxxxxxx
#> 改
[root@localhost ~]# sed '5chello world' a.txt 替换文件第5行内容 change
[root@localhost ~]# sed 'chello world' a.txt 替换文件所有行内容
[root@localhost ~]# sed '1,5chello world' a.txt 替换文件1到5号内容为hello world
[root@localhost ~]# sed '/^bin/c13141314' a.txt 替换以bin开头的行
sed 's/old_str/new_str/g' a.txt
xxxxxxxxxx
#> 删
[root@localhost ~]# sed '1d' a.txt 删除文件第1行
[root@localhost ~]# sed '1,5d' a.txt 删除文件1到5行
[root@localhost ~]# sed '$d' a.txt 删除文件最后一行
[root@localhost ~]# sed '/1.*/d' a.txt 删除正则匹配的所有行
xxxxxxxxxx
#> 搜索并替换
[root@localhost ~]# sed -i 's@root@ROOT@' a.txt #将每一行匹配的第一个小写root替换为ROOT
[root@localhost ~]# sed -i 's/root/ROOT/g' a.txt #将文件中所有的小写root替换为ROOT
[root@localhost ~]# sed -i 's/^#//g' a.txt #将所有#开头的行的#号去掉
#当要匹配的内容包含/, 则以@作为分隔符,或者自定义,但要保证一样
[root@localhost ~]# sed -n 's@/sbin/nologin@itcast@gp' a.txt
[root@localhost ~]# sed -n 's/\/sbin\/nologin/itcast/gp' a.txt
#号为分隔符,10s表示第十行进行替换
[root@localhost ~]# sed -n '10s#/sbin/nologin#itcast#p' a.txt
uucp:x:10:14:uucp:/var/spool/uucp:itcast
[root@localhost ~]# sed -n '1,5s/^/#/p' a.txt >a.txt 注释掉文件的1-5行内容
#root:x:0:0:root:/root:/bin/bash
#bin:x:1:1:bin:/bin:/sbin/nologin
#daemon:x:2:2:daemon:/sbin:/sbin/nologin
#adm:x:3:4:adm:/var/adm:/sbin/nologin
#lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
如果要将文件中的内容进行修改, 请使用 -i 选项, 要想 在sed中增加多个匹配模式或动作, 使用 -e
awk 'BEGIN{} 条件{} END{}' 文件名
横纵坐标
xxxxxxxxxx
[root@shell-scripts shelltext]# cat shellfile
hello lemonban
test
tomcat Test jenkins
lemon apache test
selenium appium httpclient
java python
TEST
[root@shell-scripts shelltext]# awk 'BEGIN{ FS=" " } NR==3{ print $2 }END{}' textshell
Test
#> NR 表示行号
#> NF 表示列 直接写表示最后一列
awk中定义变量
xxxxxxxxxx
[root@shell-scripts shelltext]# nr=3
[root@shell-scripts shelltext]# awk -v awknr="${nr}" 'BEGIN{ FS=" " } NR==awknr{ print $2 } END{}' textshell
Test
awk计算
xxxxxxxxxx
[root@shell-scripts shelltext]# df | tail -2
devtmpfs 485804 0 485804 0% /dev
tmpfs 497836 0 497836 0% /dev/shm
tmpfs 497836 7788 490048 2% /run
tmpfs 497836 0 497836 0% /sys/fs/cgroup
/dev/mapper/centos-root 17811456 1457428 16354028 9% /
/dev/sda1 1038336 140364 897972 14% /boot
tmpfs 99568 0 99568 0% /run/user/0
[root@shell-scripts shelltext]# df | tail -n +2 | awk '{ sum+=$4 }END{ print sum }'
19323092
df --total
awk数组(重点)
xxxxxxxxxx
[root@shell-scripts shelltext]# cat ip_list.txt
a b c 172.16.34.11 abc ips[172.16.34.11]++ => {172.16.34.11: 1}
a b c 172.16.34.11 abc ips[172.16.34.11]++. => {172.16.34.11: 2} => ips[172.16.34.11]=2
a b c 172.16.34.11 abc
a b c 172.16.34.11 abc
a b c 172.16.34.11 abc
a b c 172.16.34.11 abc
a b c 172.16.34.11 abc
a b c 172.16.34.121 abc
a b c 172.16.34.121 abc
a b c 172.16.34.121 abc
a b c 172.16.34.121 abc
a b c 172.16.34.121 abc
a b c 172.16.34.121 abc
[root@shell-scripts shelltext]# awk '{ ips[$4]++ } END{ for(ip in ips){ print ip, ips[ip] } }' ip_list.txt | sort -k2 -nr | head
172.16.34.11 7
172.16.34.121 6
awk条件判断
xxxxxxxxxx
awk 'BEGIN{ FS=":" } $3<10{ print $1, "uid", $3 }' /etc/passwd
作业:
获取当前系统cpu使用率
获取当前系统memory使用率
获取当前系统磁盘使用率
请统计出ip_list.txt中所有IP地址出现次数, 并取其前十名
输出/etc/passwd文件中, gid小于100的组; 要求输出 组名 gid
1、查看有多少个IP访问:
xxxxxxxxxx
awk '{print $1}' log_file|sort|uniq|wc -l
2、查看某一个页面被访问的次数:
xxxxxxxxxx
grep "/index.php" log_file | wc -l
3、查看每一个IP访问了多少个页面:
xxxxxxxxxx
awk '{++S[$1]} END {for (a in S) print a,S[a]}' log_file > log.txt
sort -n -t ' ' -k 2 log.txt 配合sort进一步排序
4、将每个IP访问的页面数进行从小到大排序:
xxxxxxxxxx
awk '{++S[$1]} END {for (a in S) print S[a],a}' log_file | sort -n
5、查看某一个IP访问了哪些页面:
xxxxxxxxxx
grep ^111.111.111.111 log_file| awk '{print $1,$7}'
6、去掉搜索引擎统计的页面:
xxxxxxxxxx
awk '{print $12,$1}' log_file | grep ^\"Mozilla | awk '{print $2}' |sort | uniq | wc -l
7、查看2015年8月16日14时这一个小时内有多少IP访问:
xxxxxxxxxx
awk '{print $4,$1}' log_file | grep 16/Aug/2015:14 | awk '{print $2}'| sort | uniq | wc -l
8、查看访问前十个ip地址
xxxxxxxxxx
awk '{print $1}' |sort|uniq -c|sort -nr |head -10 access_log
uniq -c 相当于分组统计并把统计数放在最前面
xxxxxxxxxx
cat access.log|awk '{print $1}'|sort|uniq -c|sort -nr|head -10
cat access.log|awk '{counts[$(11)]+=1}; END {for(url in counts) print counts[url], url}
9、访问次数最多的10个文件或页面
xxxxxxxxxx
cat log_file|awk '{print $11}'|sort|uniq -c|sort -nr | head -10
cat log_file|awk '{print $11}'|sort|uniq -c|sort -nr|head -20
awk '{print $1}' log_file |sort -n -r |uniq -c | sort -n -r | head -20
访问量最大的前20个ip
10、通过子域名访问次数,依据referer来计算,稍有不准
xxxxxxxxxx
cat access.log | awk '{print $11}' | sed -e ' s/http:\/\///' -e ' s/\/.*//' | sort | uniq -c | sort -rn | head -20
11、列出传输大小最大的几个文件
xxxxxxxxxx
cat www.access.log |awk '($7~/\.php/){print $10 " " $1 " " $4 " " $7}'|sort -nr|head -100
12、列出输出大于200000byte(约200kb)的页面以及对应页面发生次数
xxxxxxxxxx
cat www.access.log |awk '($10 > 200000 && $7~/\.php/){print $7}'|sort -n|uniq -c|sort -nr|head -100
13、如果日志最后一列记录的是页面文件传输时间,则有列出到客户端最耗时的页面
xxxxxxxxxx
cat www.access.log |awk '($7~/\.php/){print $NF " " $1 " " $4 " " $7}'|sort -nr|head -100
14、列出最最耗时的页面(超过60秒的)的以及对应页面发生次数
xxxxxxxxxx
cat www.access.log |awk '($NF > 60 && $7~/\.php/){print $7}'|sort -n|uniq -c|sort -nr|head -100
15、列出传输时间超过 30 秒的文件
xxxxxxxxxx
cat www.access.log |awk '($NF > 30){print $7}'|sort -n|uniq -c|sort -nr|head -20
16、列出当前服务器每一进程运行的数量,倒序排列
xxxxxxxxxx
ps -ef | awk -F ' ' '{print $8 " " $9}' |sort | uniq -c |sort -nr |head -20
17、查看apache当前并发访问数
对比httpd.conf中MaxClients的数字差距多少
xxxxxxxxxx
netstat -an | grep ESTABLISHED | wc -l
18、可以使用如下参数查看数据
xxxxxxxxxx
ps -ef|grep httpd|wc -l
1388
统计httpd进程数,连个请求会启动一个进程,使用于Apache服务器。
表示Apache能够处理1388个并发请求,这个值Apache可根据负载情况自动调整
xxxxxxxxxx
netstat -nat|grep -i "80"|wc -l
4341
netstat -an会打印系统当前网络链接状态,而grep -i “80”是用来提取与80端口有关的连接的,wc -l进行连接数统计。 最终返回的数字就是当前所有80端口的请求总数
xxxxxxxxxx
netstat -na|grep ESTABLISHED|wc -l
376
netstat -an会打印系统当前网络链接状态,而grep ESTABLISHED 提取出已建立连接的信息。然后wc -l统计 最终返回的数字就是当前所有80端口的已建立连接的总数。
xxxxxxxxxx
netstat -nat||grep ESTABLISHED|wc
可查看所有建立连接的详细记录
19、输出每个ip的连接数,以及总的各个状态的连接数
xxxxxxxxxx
netstat -n | awk '/^tcp/ {n=split($(NF-1),array,":");if(n<=2)++S[array[(1)]];else++S[array[(4)]];++s[$NF];++N} END {for(a in S){printf("%-20s %s\n", a, S[a]);++I}printf("%-20s %s\n","TOTAL_IP",I);for(a in s) printf("%-20s %s\n",a, s[a]);printf("%-20s %s\n","TOTAL_LINK",N);}'
20、其他的收集
分析日志文件下 2012-05-04 访问页面最高 的前20个 URL 并排序
xxxxxxxxxx
cat access.log |grep '04/May/2012'| awk '{print $11}'|sort|uniq -c|sort -nr|head -20
查询受访问页面的URL地址中 含有 www.abc.com 网址的 IP 地址
xxxxxxxxxx
cat access_log | awk '($11~/\www.abc.com/){print $1}'|sort|uniq -c|sort -nr
获取访问最高的10个IP地址 同时也可以按时间来查询
xxxxxxxxxx
cat linewow-access.log|awk '{print $1}'|sort|uniq -c|sort -nr|head -10
时间段查询日志时间段的情况
xxxxxxxxxx
cat log_file | egrep '15/Aug/2015|16/Aug/2015' |awk '{print $1}'|sort|uniq -c|sort -nr|head -10
分析 2015/8/15 到 2015/8/16 访问”/index.php?g=Member&m=Public&a=sendValidCode”的IP倒序排列
xxxxxxxxxx
cat log_file | egrep '15/Aug/2015|16/Aug/2015' | awk '{if($7 == "/index.php?g=Member&m=Public&a=sendValidCode") print $1,$7}'|sort|uniq -c|sort -nr
($7~/.php/) $7里面包含.php的就输出,本句的意思是最耗时的一百个PHP页面
xxxxxxxxxx
cat log_file |awk '($7~/\.php/){print $NF " " $1 " " $4 " " $7}'|sort -nr|head -100
列出最最耗时的页面(超过60秒的)的以及对应页面发生次数
xxxxxxxxxx
cat access.log |awk '($NF > 60 && $7~/\.php/){print $7}'|sort -n|uniq -c|sort -nr|head -100
统计网站流量(G)
xxxxxxxxxx
cat access.log |awk '{sum+=$10} END {print sum/1024/1024/1024}'
统计404的连接
xxxxxxxxxx
awk '($9 ~/404/)' access.log | awk '{print $9,$7}' | sort
统计http status
xxxxxxxxxx
cat access.log |awk '{counts[$(9)]+=1}; END {for(code in counts) print code, counts[code]}'
cat access.log |awk '{print $9}'|sort|uniq -c|sort -rn
每秒并发
xxxxxxxxxx
watch "awk '{if($9~/200|30|404/)COUNT[$4]++}END{for( a in COUNT) print a,COUNT[a]}' log_file|sort -k 2 -nr|head -n10"
带宽统计
xxxxxxxxxx
cat apache.log |awk '{if($7~/GET/) count++}END{print "client_request="count}'
找出某天访问次数最多的10个IP
xxxxxxxxxx
cat /tmp/access.log | grep "20/Mar/2011" |awk '{print $3}'|sort |uniq -c|sort -nr|head
当天ip连接数最高的ip都在干些什么
xxxxxxxxxx
cat access.log | grep "10.0.21.17" | awk '{print $8}' | sort | uniq -c | sort -nr | head -n 10
小时单位里ip连接数最多的10个时段
xxxxxxxxxx
awk -vFS="[:]" '{gsub("-.*","",$1);num[$2" "$1]++}END{for(i in num)print i,num[i]}' log_file | sort -n -k 3 -r | head -10
找出访问次数最多的几个分钟
xxxxxxxxxx
awk '{print $1}' access.log | grep "20/Mar/2011" |cut -c 14-18|sort|uniq -c|sort -nr|head
取5分钟日志
if [ $DATE_MINUTE != $DATE_END_MINUTE ] ;then #
则判断开始时间戳与结束时间戳是否相等
START_LINE=sed -n "/$DATE_MINUTE/=" $APACHE_LOG|head -n1
#如果不相等,则取出开始时间戳的行号,与结束时间戳的行号
查看tcp的链接状态
xxxxxxxxxx
netstat -nat |awk '{print $6}'|sort|uniq -c|sort -rn
netstat -n | awk '/^tcp/ {++S[$NF]};END {for(a in S) print a, S[a]}'
netstat -n | awk '/^tcp/ {++state[$NF]}; END {for(key in state) print key,"\t",state[key]}'
netstat -n | awk '/^tcp/ {++arr[$NF]};END {for(k in arr) print k,"\t",arr[k]}'
netstat -n |awk '/^tcp/ {print $NF}'|sort|uniq -c|sort -rn
netstat -ant | awk '{print $NF}' | grep -v '[a-z]' | sort | uniq -c
netstat -ant|awk '/ip:80/{split($5,ip,":");++S[ip[1]]}END{for (a in S) print S[a],a}' |sort -n
netstat -ant|awk '/:80/{split($5,ip,":");++S[ip[1]]}END{for (a in S) print S[a],a}' |sort -rn|head -n 10
awk 'BEGIN{printf ("http_code\tcount_num\n")}{COUNT[$10]++}END{for (a in COUNT) printf a"\t\t"COUNT[a]"\n"}'
查找请求数前20个IP(常用于查找攻来源):
xxxxxxxxxx
netstat -anlp|grep 80|grep tcp|awk '{print $5}'|awk -F: '{print $1}'|sort|uniq -c|sort -nr|head -n20
netstat -ant |awk '/:80/{split($5,ip,":");++A[ip[1]]}END{for(i in A) print A[i],i}' |sort -rn|head -n20
用tcpdump嗅探80端口的访问看看谁最高
xxxxxxxxxx
tcpdump -i eth0 -tnn dst port 80 -c 1000 | awk -F"." '{print $1"."$2"."$3"."$4}' | sort | uniq -c | sort -nr |head -20
查找较多time_wait连接
xxxxxxxxxx
netstat -n|grep TIME_WAIT|awk '{print $5}'|sort|uniq -c|sort -rn|head -n20
找查较多的SYN连接
xxxxxxxxxx
netstat -an | grep SYN | awk '{print $5}' | awk -F: '{print $1}' | sort | uniq -c | sort -nr | more
根据端口列进程
xxxxxxxxxx
netstat -ntlp | grep 80 | awk '{print $7}' | cut -d/ -f1
查看了连接数和当前的连接数
xxxxxxxxxx
netstat -ant | grep $ip:80 | wc -l
netstat -ant | grep $ip:80 | grep EST | wc -l
查看IP访问次数
xxxxxxxxxx
netstat -nat|grep ":80"|awk '{print $5}' |awk -F: '{print $1}' | sort| uniq -c|sort -n
Linux命令分析当前的链接状况
xxxxxxxxxx
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
watch "netstat -n | awk '/^tcp/ {++S[\$NF]} END {for(a in S) print a, S[a]}'"
# 通过watch可以一直监控
LAST_ACK 5 #关闭一个TCP连接需要从两个方向上分别进行关闭,双方都是通过发送FIN来表示单方向数据的关闭,当通信双方发送了最后一个FIN的时候,发送方此时处于LAST_ACK状态,当发送方收到对方的确认(Fin的Ack确认)后才真正关闭整个TCP连接;
SYN_RECV 30 # 表示正在等待处理的请求数;
ESTABLISHED 1597 # 表示正常数据传输状态;
FIN_WAIT1 51 # 表示server端主动要求关闭tcp连接;
FIN_WAIT2 504 # 表示客户端中断连接;
TIME_WAIT 1057 # 表示处理完毕,等待超时结束的请求数;