Fork me on GitHub

shell编程实战

shell 基础编程

一、前言

由于工作方向的转变,现在每天大量用到linux和shell基本编程,而每当想实现一个功能时,都需要google,其实这是非常耽误时间的,我觉得如果想快速掌握一门技术,系统学习是必不可少的,这比遇到问题后一个一个零散的学习强的多,如果你系统的去掌握了某个领域的知识点后,在根据时间工作上遇到的问题去学习,往往能达到事半功倍的效果。我会把我学习到的知识都记录下来,会给出一些例子,我认为例子非常重要,因为一旦自己忘记了某个知识,准备查找的时候,其实自己很少会去看概念,往往会直接看例子,而如果这个例子又是你自己写的话,重复的看几遍,这个知识你基本上就会了。我这个文档会尽量以例子为驱动,对于参数会给出完整的英文。

二、变量的用法

2-1 变量替换和测试

用法 备注
${变量#匹配规则} 从头开始匹配,最短删除
${变量##匹配规则} 从头开始匹配,最长删除
${变量%匹配规则} 从尾开始匹配,最短删除
${变量%%匹配规则} 从尾开始匹配,最长删除
${变量/旧字符/新字符} 替换变量内的旧字符为新字符,只替换第一个
${变量//旧字符/新字符} 替换变量内的旧字符为新字符,全部替换

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
"i love you, do you love me"
tmp=${var##*ove}
echo $tmp
tmp=${var##*ove}
echo $tmp
tmp=${var%ove*}
echo $tmp
tmp=${var%%ove*}
echo $tmp
tmp=${var/love/LOVE}
echo $tmp
tmp=${var//love/LOVE}
echo $tmp

2-2 字符串处理

  • 计算字符串长度

    1
    2
    3
    4
    5
    6
    1、${#string}
    2、expr length $string
    var="i love you"
    echo ${#var}
    expr length ${var}
  • 获取字符串中的子串

    1
    2
    3
    4
    1、${string:pos}
    2、${string:pos:len}
    3、${string:(-pos)}
    4、expr substr $string $pos $len

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/sh
var="i love you"
tmp=${var:1}
echo "$tmp"
tmp=${var:1:2} # 下标是从0开始的
echo "${tmp}"
tmp=${var:(-5)}
echo "${tmp}"
tmp=`expr substr "${var}" 1 10` # 注意expr下标是从1开始的
echo "${tmp}"

一个完整的字符串处理例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#!/usr/bin/env bash
#
string="Bigdata process framework is Hadoop,Hadoop is an open source project"
function tips_info
{
echo "******************************************"
echo "*** (1) 打印string长度"
echo "*** (2) 在整个字符串中删除Hadoop"
echo "*** (3) 替换第一个Hadoop为Mapreduce"
echo "*** (4) 替换全部Hadoop为Mapreduce"
echo "******************************************"
}
function print_len
{
# -z表示判断字符串长度是否为0
if [ -z "$string" ];then
echo "Error,string is null"
exit 1
else
echo "${#string}"
fi
}
function del_hadoop
{
if [ -z "$string" ];then
echo "Error,string is null"
exit 1
else
echo "${string//Hadoop/}"
fi
}
function rep_hadoop_mapreduce_first
{
if [ -z "$string" ];then
echo "Error,string is null"
exit 1
else
echo "${string/Hadoop/Mapreduce}"
fi
}
function rep_hadoop_mapreduce_all
{
if [ -z "$string" ];then
echo "Error,string is null"
exit 1
else
echo "${string//Hadoop/Mapreduce}"
fi
}
while true
do
echo "【string=\"$string\"】"
tips_info
read -p "Please Switch a Choice: " choice
# case, esac(反写esac); 相当于switch case break
case "$choice" in
1)
echo
echo "Length Of String is: `print_len`"
echo
continue
;;
2)
echo
echo "删除Hadoop后的字符串为:`del_hadoop`"
echo
;;
3)
echo
echo "替换第一个Hadoop的字符串为:`rep_hadoop_mapreduce_first`"
echo
;;
4)
echo
echo "替换第一个Hadoop的字符串为:`rep_hadoop_mapreduce_all`"
echo
;;
q|Q)
exit 0
;;
*)
echo "error,unlegal input,legal input only in { 1|2|3|4|q|Q }"
continue
;;
esac
done

2-3 命令替换

1
2
1、`command`
2、$(command)

例子1:打印linux下所有用户名

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
#
index=1
for user in `cat /etc/passwd | awk 'BEGIN{FS=":"} {print $1}'`
do
echo "This is $index user: $user"
index=$(($index + 1))
done

例子2:打印日期相关

1
2
3
4
5
6
7
8
#!/bin/bash
#
echo "This year have passed $(date +%j) days"
echo "This year have passed $(($(date +%j)/7)) weeks"
echo "There is $((365 - $(date +%j))) days before new year"
echo "There is $(((365 - $(date +%j))/7)) weeks before new year"

例子3:判断进程是否存在,如果不存在则拉起

1
2
3
4
5
6
7
8
#!/bin/bash
#
nginx_process_num=$(ps -ef | grep nginx | grep -v grep | wc -l)
if [ $nginx_process_num -eq 0 ];then
systemctl start nginx
fi

2-4 有类型变量

都是通过declare 命令和typeset命令来定义变量类型

declare

  • -r: 将变量设为只读
  • -i: 将变量设为整数
  • -a: 将变量设为数组
  • -f: 显示此脚本前定义过的所有函数及内容
  • -F: 仅显示此脚本前定义过的函数名
  • -x: 将变量声明为环境变量

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ declare -i num1
$ num2=100
$ num1=$num2+10
$ echo $num1
110
$ declare -r var2="hello"
$ var2="hello2"
-bash: var2: readonly variable
$ declare -a arr
$ arr=(1 2 3)
$ echo ${arr[@]}
1 2 3

2-5 bash 数学运算之expr

两种方法

1
expr $num1 operator $num2 #推荐使用(智能进行整数运算)
1
$(($num1 operator $num2))

例子1

进行1+2+3+…+100的运算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/bin/bash
#
while true
do
read -p "pls input a positive number: " num
expr $num + 1 &> /dev/null
# $?表示上面运行的结果状态,如果$?运行成功,$?=0, 否则是一个非0数,而expr只能进行整数运算,这样就可以保证输入的为整数
if [ $? -eq 0 ];then
if [ `expr $num \> 0` -eq 1 ];then
for((i=1;i<=$num;i++))
do
sum=`expr $sum + $i`
done
echo "1+2+3+....+$num = $sum"
exit
fi
fi
echo "error,input enlegal"
continue
done

2-6 bash 数学运算之 bc

bc是bash内建的运算器,支持浮点数运算, 内建变量scale可以设置,默认为0

例子

1
2
3
4
5
6
7
8
#!/bin/bash
#
read -p "num1: " num1
read -p "num2: " num2
num3=`echo "scale=4;$num1/$num2" | bc`
echo "$num1 / $num2 = $num3"

三、函数的用法

3-1 函数定义和使用

定义

1
2
3
4
5
function name {
command1
command2
echo abc
}

如何调用函数

  • 直接使用函数名调用,可以想象成shell中的一条命令
  • 函数内部可以直接使用参数$1,$2…$n

函数调用

1
function_name $1 $2

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/bin/bash
#
# 表示当前文件运行时候的进程号
this_pid=$$
while true
do
ps -ef | grep nginx | grep -v grep | grep -v $this_pid &> /dev/null
# $?表示执行的状态,执行失败为非0
if [ $? -eq 0 ];then
echo "Nginx is running well"
sleep 3
else
systemctl start nginx
echo "Nginx is down,Start it...."
fi
done

3-2 向函数传递参数

例子:简单的计算器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/bin/bash
#
function calcu
{
case $2 in
+)
echo "`expr $1 + $3`"
;;
-)
echo "`expr $1 - $3`"
;;
\*)
echo "`expr $1 \* $3`"
;;
/)
echo "`expr $1 / $3`"
;;
esac
}
calcu $1 $2 $3

3-3 函数返回值

return

  • 使用return 返回值, 只能返回1-255的整数
  • 函数使用return返回值,通常只是用来供其他地方调用获取状态,因此通常仅返回0或1;0表示成功, 1表示失败

echo

  • echo可以返回任何字符串结果
  • 通常用于返回数据,比如一个字符串值或者列表值

例子一:检测nginx是否在运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
#
this_pid=$$
function is_nginx_running
{
ps -ef | grep nginx | grep -v grep | grep -v $this_pid &> /dev/null
if [ $? -eq 0 ];then
return
else
return 1
fi
}
is_nginx_running && echo "Nginx is running" || echo "Nginx is stoped"

例子二:获取系统用户的所有用户名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/bash
#
function get_users
{
users=`cat /etc/passwd | awk 'BEGIN{FS=":"} {print $1}'`
echo $users
}
user_list=`get_users`
index=1
for u in $user_list
do
echo "The $index user is : $u"
index=$(($index+1))
done

3-4 局部变量和全局变量

  • 不做特殊声明,Shell中变量都是全局变量
  • 大型脚本中函数中慎用全局变量

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/bash
#
var1="Hello world"
function test
{
# 赋值的时候操作费两边不要有空格
var3=55
local var2=87
}
# 只有当test被调用了的时候, var3才会生效
test
echo $var1
echo $var2
echo $var3

3-5 函数库

  • 经常使用的重复代码封装成函数文件
  • 一般不直接执行,而是由其他脚本调用
  • 库文件名的后缀是任意的,但一般使用.lib
  • 库文件通常没有可执行权限
  • 库文件无需和脚本在同级目录,只需在脚本中引用时指定
  • 第一行一般使用/bin/echo, 输出警告信息,避免用户执行

例子

lib文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/bin/echo "是系统函数,不要执行"
function add
{
echo "`expr $1 + $2`"
}
function reduce
{
echo "`expr $1 - $2`"
}
function multiple
{
echo "`expr $1 \* $2`"
}
function divide
{
echo "`expr $1 / $2`"
}
function sys_load
{
echo "Memory Info"
echo
free -m
echo
echo "Disk Usage"
echo
df -h
echo
}

调用

1
2
3
4
5
6
7
8
9
#!/bin/bash
# 引入系统文件,注意使用绝对路径
. /home/wujunqi/wujunqilib/base_function.lib
add 12 23
reduce 90 30
multiple 12 12
divide 12 2
sys_load

四、find命令

4-1 find使用

语法格式

1
find [路径] [选项] [操作]

常用选项:

  • -name

    1
    find /etc -name '*conf' #查找/etc目录下以conf结尾的文件
  • -iname

    1
    find . -iname aa # 查找当前目录下文件名为aa的文件,不区分大小写
  • -user

    1
    find . -user hdfs # 查找文件属主为hdfs的所有文件
  • -group

    1
    find . -group yarn # 查找文件属组为yarn的所有文件
  • -type

1
2
3
4
5
6
find . -type f # f:文件
find . -type d # d:目录
find . -type c # c:字符设备文件
find . -type b # b:块设备文件
find . -type l # l:链接文件
find . -type p # p:管道文件
  • -size

    1
    2
    3
    4
    #-n 小于大小n的文件
    find /etc -size -10000c # 查找/etc目录下小于10000字节的文件
    #+n 大于小于n的文件
    find /etc -size +1M # 查找/etc目录下大于1M的文件
  • -mtime

    1
    2
    3
    4
    #-n n天以内修改的文件
    find /etc -mtime -5 -name '*.conf' # 查找/etc目录下5天之内修改且以conf结尾的文件
    #+n n天以外修改的文件
    find /etc -mtime +10 -user root #查找/etc目录下10天之前修改且属主为root的文件
  • -mmin

1
2
3
4
# -n n分钟以内修改的文件
find /etc -mmin +30 # 查找/etc目录下30分钟之前修改的文件
# +n n分钟以外修改的文件
find /etc -mmin -30 -type d # 查找/etc目录下30分钟之内修改的目录
  • -mindepth n

    1
    2
    # 表示从n级子目录开始搜索
    find /etc -mindepth 3 # 在/etc下的3级子目录开始搜索
  • -maxdepth n

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 表示最多搜索到n级子目录
find /etc -maxdepth 3 -name '*.conf' #在/etc下搜索符合条件的文件,但最多搜索到2级子目录
find ./etc/ -type f -name '*.conf' -size +10k -maxdepth 2
```
* -prune
> prune英文意思是:修剪。通常和-path一起使用,用于将特定目录排除在搜索条件之外
```sh
# 查找当前目录下所有普通文件,但排除test目录
find . -path ./etc -prune -o -type f
# 查找当前目录下所有普通文件,但排除etc和opt目录
find . -path ./etc -prune -o -path ./opt -prune -o -type f
# 查找当前目录下所有普通文件,但排除etc和opt目录,但属主为hdfs
find . -path ./etc -prune -o -path ./opt -prune -o -type f -a -user hdfs
# 查找当前目录下所有普通文件,但排除etc和opt目录,但属主为hdfs,且文件大小必须大于500字节
find . -path ./etc -prune -o -path ./opt -prune -o -type f -a -user hdfs -a -size +500c
```
* -exec
> 对搜索到的文件执行特定的操作,格式为```-exec 'command' {} \;
1
2
3
4
5
6
7
8
#例子1:搜索/etc下的文件(非目录),文件名以conf结尾,且大于10k,然后将其删除
find ./etc/ -type f -name '*.conf' -size +10k -exec rm -f {} \;
# 例子2:将/var/log/目录下以log结尾的文件,且更改时间在7天以上的删除
find /var/log/ -name '*.log' -mtime +7 -exec rm -rf {} \;
# 例子3:搜索条件和例子1一样,只是不删除,而是将其复制到/root/conf目录下
find ./etc/ -size +10k -type f -name '*.conf' -exec cp {} /root/conf/ \;
  • -ok

    和exec功能一样,只是每次操作都会给用户提示

逻辑运算符:
默认情况下是-a

  • -a 与
  • -o 或
  • -not|! 非
1
2
3
4
5
6
7
8
9
#例子1:查找当前目录下,属主不是hdfs的所有文件
find . -not -user hdfs # 使用-not
find . ! -user hdfs # 使用 !
#例子2:查找当前目录下,属主属于hdfs,且大小大于300字节的文件
find . -type f -a -user hdfs -a -size +300c
#例子3:查找当前目录下的属主为hdfs或者以xml结尾的普通文件
find . -type f -a \( -user hdfs -o -name '*.xml' \)

4-2 find、locate、whereis 和 which 总结及适用场景分析

locate

  • 文件查找命令,所属软件包mlocate
  • 不同于find命令是爱整块磁盘中搜索,locate命令在数据库文件中查找
  • find是默认全部匹配, locate则是默认部分匹配
  • updatedb命令:会立即更新文件系统的文件,该命令在后台cron计划任务中定期执行
    1
    2
    更新 /var/lib/mlocate/mlocate.db
    配置 /etc/updatedb.conf

例子

1
locate php.ini

whereis

一般用户查找二进制文件

  • -b: 只返回二进制文件

  • -m: 只返回帮助文档文件

  • -s: 只返回源代码文件

which

仅查找二进制程序文件

  • -b: 只返回二进制文件

五、grep知识

5-1 grep 和 egrep

格式1

1
grep [option][pattern][file1,file2...]

格式2

1
command | grep [option][pattern]

参数说明

  • -v: 不显示匹配行信息,只显示不匹配的内容
  • -i: 搜索时候忽略大小写(ignore)
  • -n: 显示行号(number)
  • -r: 递归搜索(recursion)
  • -E: 支持扩展正则表达式(extend)
  • -F: 不按正则表达式匹配,按照字符串字面意思匹配(face)
  • -c: 只显示匹配行总数(count)
  • -w: 匹配整词: 匹配的单词,必须前后有空格
1
grep -w love file_name
  • -l: 只显示文件名,不显示内容(list)
  • -s: 不显示错误信息

六、sed 知识

6-1 sed 的工作模式

sed(stream editor), 流编辑器,对标准输出或文件逐行进行处理

使用格式1

1
stdout | sed [option] "pattern command"

使用格式2

1
sed [option] "pattern command" file

6-2 sed 的选项

  • -n: 只打印模式匹配行
  • -f: 编辑动作保存在文件中, 指定文件执行
  • -r: 支持扩展正则表达式
  • -i: 直接修改文件内容
  • -e: 非常重要,以选项中指定的 script 来处理输入的文本文件。可以指定多个

6-3 sed 中的 pattern 详解

  • 10command: 匹配到第10行
  • 10,20command: 匹配从第10行开始,到第16行结束
  • 10,+5command: 匹配从第10行开始,共匹配5行
  • /pattern1/command: 匹配到pattern1的行
  • /pattern1/,/pattern2/command: 匹配到pattern1的行开始,到匹配到pattern2的行结束
  • 10,/pattern1/command: 匹配从第10行开始,到匹配当pattern1的行结束
  • /pattern1/, 10command: 匹配到pattern1行开始,到第10行匹配结束

6-4 sed 中的编辑命令详解

查询

  • p

删除

  • d

增加

  • a: 行后追加
  • i: 行前追加
  • r: 外部文件读入,行后追加
  • w: 匹配行写入外部文件

例子

1
2
sed -i '/^root/,/^wujunqi/i aaaaaa' tmp.txt # 是添加一行
sed -i '/^root/,/^wujunqi/r abc.txt' tmp.txt # 在以root开头到以wujunqi开头的所有行后面添加abc.txt 的内容

6-5 利用 sed 查找文件内容

1
2
3
4
5
sed -n "17p" /etc/passwd # 打印第17行
sed -n "10,20p" /etc/passwd
sed -n "10,+5p" /etc/passwd
sed -n "/^root/p" /etc/passwd
sed -n "/^root/,/a/p" /etc/passwd

列子
处理一个类似mysql配置文件my.cnf的文件,编写脚本实现以下功能:①输出文件有几段, ②并且针对每个段可以统计匹配参数总个数

文件my.cnf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[client]
port=3306
socket=/tmp/mysql.socket
#ThisSegmentForserver
[server]
innodb_buffer_pool_size=91750M
#thisisonlyforthemysqldstandalonedaemon
[mysqld]
port=3306
socket=/tmp/mysql.sock
basedir=/usr/local/mysql
#ThisSegmentFormysqld_safe
[mysqld_safe]
log-error=/var/log/mariadb/mariadb.log
pid-file=/var/run/mariadb/mariadb.pid
max_connections=1000
#thisisonlyforembeddedserver
[embedded]
gtid_mode=on
enforce_gtid_consistency=1
#usethisgroupforoptionsthatolderserversdon'tunderstand
[mysqld-5.5]
key_buffer_size=32M

脚本实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/bin/bash
#
FILE_NAME=my.cnf
function get_all_segments
{
echo "`sed -n '/\[.*\]/p' $FILE_NAME | sed -e 's/\[//g' -e 's/\]//g'`"
}
function count_items_in_segment
{
# ^$表示空行, ^#表示以#开头的行,只去掉注释行
items=`sed -n "/\[$1\]/,/\[.*\]/p" $FILE_NAME | grep -v "^#" | grep -v "^$" | grep -v "\[.*\]"`
index=0
for item in $items
do
index=`expr $index + 1`
done
echo $index
}
number=0
for segment in `get_all_segments`
do
number=`expr $number + 1`
items_count=`count_items_in_segment $segment`
echo "$number: $segment $items_count"
done

6-6 利用 sed 删除文件内容

用法总结:

1
2
3
4
5
6
7
1、1d
2、5,10d
3、10,+10d
4、/pattern1/d
5、/pattern1/,/pattern2/d
6、/pattern1/,20d
7、15,/pattern1/d

例子一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 删除/etc/passwd中的第15行
sed -i '15d' /etc/passwd
# 删除/etc/passwd中的第8行到第14行的所有内容
sed -i '8,14d' passwd
# 删除/etc/passwd中的不能登录的用户(筛选条件:/sbin/nologin)
sed -i '/\/sbin\/nologin/d' passwd
# 删除/etc/passwd中以mail开头的行,到以yarn开头的行的所有内容
sed -i '/^mail/,/^yarn/d' passwd
# 删除/etc/passwd中第一个不能登录的用户,到第13行的所有内容
sed -i '/\/sbin\/nologin/,13d' passwd
# 删除/etc/passwd中第5行到以ftp开头的所有行的内容
sed -i '5,/^ftp/d' passwd
# 删除/etc/passwd中以yarn开头的行到最后行的所有内容
sed -i '/^yarn/,$d' passwd #$表示最后一行

典型例子

1
2
3
4
# 删除配置文件中的所有注释行和空行
sed -i '/[:blank:]*#/d;/^$/d' nginx.conf #多个操作使用;分隔
# 在配置文件中所有不以#开头的行前面添加*符号,注意:以#开头的行不添加
sed -i 's/^[^#]/\*&/g' nginx.conf

6-7 利用 sed 修改文件内容

修改用法总结

  • 1s/old/new/
  • 5,10s/old/new/
  • 10,+10s/old/new/
  • /pattern1/s/old/new/
  • /pattern1/,/pattern2/s/old/new/
  • /pattern1/,20s/old/new/
  • 15,/pattern1/s/old/new/
  • s/pattern/string/g: g表示全部行内全部匹配
  • s/pattern/string/2g: 同一行内, 只替换前2个匹配的, 匹配所有行
  • s/pattern/string/ig: 匹配时忽略大小写

例子1

1
2
3
4
5
6
7
8
9
10
11
12
# 修改/etc/passwd中第1行中第1个root为ROOT
sed -i '1s/root/ROOT/' passwd
# 修改/etc/passwd中第5行到第10行中所有的/sbin/nologin为/bin/bash
sed -i '5,10s/\/sbin\/nologin/\/bin\/bash/g' passwd
# 修改/etc/passwd中匹配到/sbin/nologin的行,将匹配到行中的login改为大写的LOGIN
sed -i '/\/sbin\/nologin/s/login/LOGIN/g' passwd
# 修改/etc/passwd中从匹配到以root开头的行,到匹配到行中包含mail的所有行。修改内为将这些所有匹配到的行中的bin改为HADOOP
sed -i '/^root/,/mail/s/bin/HADOOP/g' passwd
# 修改/etc/passwd中从匹配到以root开头的行,到第15行中的所有行,修改内容为将这些行中的nologin修改为SPARK
sed -i '/^root/,15s/nologin/SPARK/g' passwd
# 修改/etc/passwd中从第15行开始,到匹配到以yarn开头的所有航,修改内容为将这些行中的bin换位BIN
sed -i '15,/^yarn/s/bin/BIN/g' passwd

例子2

1
2
3
4
5
6
#!/bin/bash
#
old_str=hadoop
new_str=HADOOP
sed -i "s/$old_str/$new_str/g" str.txt # 匹配模式中存在变量,建议使用双引号

反向引用

  • &和\1: 引用模式匹配到整个串,\1可以匹配子串

例子

1
2
3
4
5
6
# 在file中搜索以1开头,然后跟任意两个字符串,以e结尾的字符串中添加r
sed "s/1..e/&r/g" file
sed "s/\(1..e\)/\1r/g" file
# 对上面任意的两个字符替换为love
sed -i "s/1\(..\)e/\1r/g" tmp.txt

6-11 利用 sed 追加文件内容

  • -a: 在匹配行后面追加
  • -i: 在匹配行前面追加
  • -r: 将文件内容追加到匹配行后面
  • -w: 将匹配行写入指定文件

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# -a
# passwd文件第10行后面追加"Add Line Behind"
sed -i '10a Add Line Begind' passwd
# passwd文件第10行到第20行,每一行后面都追加"Test Line Behind"
sed -i '10,20a Test Line Behind' passwd
# passwd文件匹配到/bin/bash的行后面追加"Insert Line For /bin/bash Behind"
sed -i '/\/bin\/bash/a Insert Line For /bin/bash Behind' passwd
# -i
# passwd文件匹配到以yarn开头的行,在匹配航前面追加"Add Line Before"
sed -i '/^yarn/i Add Line Before' passwd
# passwd文件每一行前面都追加"Insert Line Before Every Line"
sed -i 'i Insert Line Before Every Line' passwd
# -r
# 将/etc/fstab文件的内容追加到passwd文件的第20行后面
sed -i '20r /etc/fstab' passwd
# 将/etc/inittab文件内容追加到passwd文件匹配/bin/bash行的后面
sed -i '/\/bin\/bash/r /etc/inittab' passwd
# 将/etc/vconsole.conf文件内容追加到passwd文件中特定行后面,匹配以ftp开头的行,到第18行的所有行
sed -i '/^ftp/,18r /etc/vconsole.conf' passwd
# -w
#将passwd文件匹配到/bin/bash的行追加到/tmp/sed.txt文件中
sed -i '/\/bin\/bash/w /tmp/sed.txt' passwd
# 将passwd文件从第10行开始,到匹配到hdfs开头的所有行内容追加到/tmp/sed-1.txt
sed -i '10,/^hdfs/w /tmp/sed-1.txt' passwd

七、awk 知识

7-1 awk 的工作模式

awk是一个文件处理工具,通常用于处理数据并生成结果报告

语法格式

  • 'BEGIN{} pattern {commands} END{}' file_name```
    1
    * ```standard output | awk 'BEGIN{} pattern {commands} END{}'

解释

  • BEGIN{} 正式处理数据之前执行
  • pattern: 匹配模式,只有匹配了次模式的,才进行command, 默认对所有行进行处理
  • {commands}: 处理命令,可能多行,用“;”分隔
  • END{}: 处理完所有匹配数据后执行

7-2 awk 的内置变量

内置变量 含义
$0 整行内容
$1-$n 当前行的第1-n个字段
NF 当前行的字段个数,也就是有多上列(number filed)
NR 当然行的行号,从1开始计数(Number Row)
FNR 多文件处理时,每个文件单独记录行号 (File Number Row)
FS 字段分割符,不指定时默认以空格或tab键分割 (Field Separator)
RS 行分隔符,不指定时以回车分割\n (Row Separator)
OFS 输出字段分隔符 (Output Filed Separator)
ORS 输出行分隔符 (Output Row Separator)
FILENAME 处理文件的文件名
ARGC 命令行参数个数
ARGV 命令行参数数组

例子

1
2
3
4
5
6
7
8
# 打印所有linux系统的所有用户
awk 'BEGIN{FS=":"}{print $1}' /etc/passwd
# 指定换行
awk 'BEGIN{FS=":";RS="---"}{print $2}' file_name
# 指定输出行分隔符,和输出列分隔符
awk 'BEGIN{FS=":";RS="---";ORS="\n\n";OFS=";"}{print $2}' file_name

7-3 awk 格式化输出之 printf

awk中的printf非常好用,用法和c语言差不多

awk格式化输出之printf总结

格式符 含义
%s 打印字符串
%d 打印10进制数
%f 打印浮点数
%x 打印16进制数
%o 打印8进制数
%e 打印数字的科学计数法格式
%c 打印单个字符的ASCII码
修饰符 含义
- 左对齐
+ 右对齐
# 显示8进制在前面加0,显示16进制在前面加0x, 打印8进制或16进制数字是在前面加#

常见例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 以字符串格式打印/etc/passwd中的第7个字段,以":"作为分隔符
awk 'BEGIN{FS=":"} {printf "%s",$7}' /etc/passwd
# 以10进制格式打印/etc/passwd中的第3个字段,占20个字符,左对齐,默认右对齐,以":"作为分隔符
awk 'BEGIN{FS=":"} {printf "%-20d\n",$3}' /etc/passwd
awk 'BEGIN{FS=":"} {printf "%-20d%s\n",$3, $4}' /etc/passwd
# 以浮点数格式打印/etc/passwd中的第3个字段,以":"作为分隔符
awk 'BEGIN{FS=":"} {printf "%0.3f\n",$3}' /etc/passwd
# 以16进制数格式打印/etc/passwd中的第3个字段,以":"作为分隔符
awk 'BEGIN{FS=":"} {printf "%#x\n",$3}' /etc/passwd
# 以8进制数格式打印/etc/passwd中的第3个字段,以":"作为分隔符
awk 'BEGIN{FS=":"} {printf "%#o\n",$3}' /etc/passwd
# 以科学计数法格式打印/etc/passwd中的第3个字段,以":"作为分隔符
awk 'BEGIN{FS=":"} {printf "%e\n",$3}' /etc/passwd

7-4 awk 模式匹配的两种用法

  • 第一种方法:RegExp
  • 第二种方法:运算符匹配

RegExp

1
2
3
4
# 匹配/etc/passwd文件行中含有root字符串的所有行
awk 'BEGIN{FS=":"}/root/{print $0}' /etc/passwd
# 匹配/etc/passwd文件行中以yarn开头的所有行
awk 'BEGIN{FS=":"}/^yarn/{print $0}' /etc/passwd

运算符匹配
| 运算符 | 含义 |
| ——| —— |
|< | 小于|
|> | 大于|
|<= | 小于等于|
|>= | 大于等于|
|== | 等于|
|!= | 不等于|
|~ | 匹配正则表达式|
|!~ | 不匹配正则表达式|

1
2
3
4
5
6
7
8
9
10
# 以:为分隔符,匹配/etc/passwd文件中第3个字段小于50的所有行信息
awk 'BEGIN{FS=":"}$3<50{print $0}' /etc/passwd
# 以:为分隔符,匹配/etc/passwd文件中第3个字段大于50的所有行信息
awk 'BEGIN{FS=":"}$3>50{print $0}' /etc/passwd
# 以:为分隔符,匹配/etc/passwd文件中第7个字段为/bin/bash的所有行信息
awk 'BEGIN{FS=":"}$7=="/bin/bash"{print $0}' /etc/passwd
# 以:为分隔符,匹配/etc/passwd文件中第7个字段不为/bin/bash的所有行信息
awk 'BEGIN{FS=":"}$7!="/bin/bash"{print $0}' /etc/passwd
# 以:为分隔符,匹配/etc/passwd中第3个字段包含3个以上数字的所有行信息
awk 'BEGIN{FS=":"}$3~/[0-9]{3,}/{print $0}' /etc/passwd

布尔运算符匹配
| 运算符 | 含义 |
| ——| —— |
||| | 或|
|&& | 与|
|! | 非|

1
2
3
4
# 以:为分隔符,匹配/etc/passwd文件中包含hdfs或yarn的所有行信息
awk 'BEGIN{FS=":"}$1=="hdfs" || $1=="yarn" {print $0}' /etc/passwd
# 以:为分隔符,匹配/etc/passwd文件中第3个字段小于50并且第4个字段大于50的所有行信息
awk 'BEGIN{FS=":"}$3<50 && $4>50 {print $0}' /etc/passwd

7-5 awk 中表达式的用法

awk动作中的表达式用法总结
| 算数运算符 | 含义 |
| ——| —— |
|+ | 加|
|- | 减|
| | 乘|
|/ | 除|
|% | 取模|
|^或*
| 乘方|
|++x | 在返回x变量之前,x变量加1|
|x++ | 在返回x变量之后,x变量加1|
|–x | 在返回x变量之前,x变量减1|
|x– | 在返回x变量之后,x变量减1|

例子1

1
2
# 使用awk计算/etc/services中的空白行数量
awk '/^$/{sum++}END{print sum}' /etc/services

例子2

1
2
3
4
5
6
7
8
9
10
#计算学生课程分数平均值,学生课程文件内容如下:
#Allen 80 90 96 98
#Mike 93 98 92 91
#Zhang 78 76 87 92
#Jerry 86 89 68 92
#Han 85 95 75 90
#Li 78 88 98 100
awk 'BEGIN{printf "%-8s%-8s%-8s%-8s%-8s%s\n","Name","Yuwen","Math","English","Pysical","Average"}{total=$2+$3+$4+$5;AVG=total/4;printf "%-8s%-8d%-8d%-8d%-8d%0.2f\n",$1,$2,3,$4,$5,AVG}' student.txt

7-6 awk 动作中的条件及循环语句

条件语句

1
2
3
4
5
6
if(条件表达式1)
动作
else if(条件表达式2)
动作
else
动作

例子1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#以:为分隔符,只打印/etc/passwd中第3个字段的数值在50-100范围内的行信息
awk 'BEGIN{
FS=":"
}
{
if($3<50)
{
printf "%-20s%-25s%-5d\n","UID<50",$1,$3
}
else if($3>50 && $3<100)
{
printf "%-20s%-25s%-5d\n","50<UID<100",$1,$3
}
else
{
printf "%-20s%-25s%-5d\n","UID>100",$1,$3
}
}' /etc/passwd

例子二

1
2
3
4
5
6
7
8
9
10
11
12
# 计算下列每个同学的平均分数,并且只打印平均分数大于90的同学姓名和分数信息,并打印各个科目的平均成绩
Name Chinese English Math Physical Average
Allen 80 90 96 98
Mike 93 98 92 91
Zhang 78 76 87 92
Jerry 86 89 68 92
Han 85 95 75 90
Li 78 88 98 100
aver Chinese_aver ......
# 实现
awk 'BEGIN{}{aver=($2+$3+$4+$5)/4; chinese_aver+=$2; englist_aver+=$3;math_aver+=$4;physical_aver+=$5;if(aver > 90){printf "%10s%10d\n",$1,aver}}END{printf "%10s%10s%10s%10s\n", chinese_aver/NR,englist_aver/NR,math_aver/NR,physical_aver/NR}' ./student.txt

循环语句

while循环

1
2
while(条件表达式)
动作

例子

1
2
# 计算1+2+3+4+...+100的和,请使用while、do while、for三种循环方式实现
awk 'BEGIN{while(i<100){sum+=i; i+=1}; printf "%d", sum}'

do while循环

1
2
3
do
动作
while(条件表达式)

例子

1
2
# 计算1+2+3+4+...+100的和,请使用while、do while、for三种循环方式实现
awk 'BEGIN{do {sum+=i; i+=1} while(i<=100) print sum}'

for循环

1
2
for(初始化计数器;计数器测试;计数器变更)
动作

例子

1
2
# 计算1+2+3+4+...+100的和,请使用while、do while、for三种循环方式实现
awk 'BEGIN{for(i=1;i<=100;i++){sum+=i} print sum}'

7-7 awk 中的字符串函数

函数 含义
length(str) 计算长度
index(str1,str2) 返回在str1中查询到的str2的位置
tolower(str) 小写转换
toupper(str) 大写转换
split(str,arr,fs) 分隔字符串,并保存到数组中
match(str,RE) 返回正则表达式匹配到的子串的位置
substr(str,m,n) 截取子串,从m个字符开始,截取n位。n若不指定,则默认截取到字符串尾部
sub(RE,RepStr,str) 替换查找到的第一个子串
gsub(RE,RepStr,str) 替换查找到的所有子串

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 以:为分隔符,返回/etc/passwd中每行中每个字段的长度
awk '
BEGIN{
FS=":"
}
{
i=1
while(i<=NF)
{
if(i==NF)
printf "%d",length($i)
else
printf "%d:",length($i)
i++
}
print ""
}' /etc/passwd
# 搜索字符串"I have a dream"中出现"ea"子串的位置
awk 'BEGIN{str="I hava a dream";location=index(str,"ea");print location}'
awk 'BEGIN{str="I hava a dream";location=match(str,"ea");print location}'
# 将字符串"Hadoop is a bigdata Framawork"全部转换为小写
awk 'BEGIN{str="Hadoop is a bigdata Framework";print tolower(str)}'
#将字符串"Hadoop is a bigdata Framawork"全部转换为大写
awk 'BEGIN{str="Hadoop is a bigdata Framework";print toupper(str)}'
#将字符串"Hadoop Kafka Spark Storm HDFS YARN Zookeeper",按照空格为分隔符,分隔每部分保存到数组array中
awk 'BEGIN{str="Hadoop Kafka Spark Storm HDFS YARN Zookeeper";split(str,arr);for(a in arr) print arr[a]}'
#搜索字符串"Tranction 2345 Start:Select * from master"第一个数字出现的位置
awk 'BEGIN{str="Tranction 2345 Start:Select * from master";location=match(str,/[0-9]/);print location}'
#截取字符串"transaction start"的子串,截取条件从第4个字符开始,截取5位
awk 'BEGIN{str="transaction start";print substr(str,4,5)}'
awk 'BEGIN{str="transaction start";print substr(str,4)}'
#替换字符串"Tranction 243 Start,Event ID:9002"中第一个匹配到的数字串为$符号
awk 'BEGIN{str="Tranction 243 Start,Event ID:9002";count=sub(/[0-9]+/,"$",str);print count,str}'
awk 'BEGIN{str="Tranction 243 Start,Event ID:9002";count=gsub(/[0-9]+/,"$",str);print count,str}'

7-8 awk 中的常用选项

函数 含义
-v 定义或引用变量
-f 指定awk命令文件
-F 指定分隔符
-V 查看awk的版本号

例子

1
2
3
4
num1=20
var="hello world"
# 引用变量的时候注意添加”“
awk -v num1="$num1" -v var1="$var" 'BEGIN{print num1, var1}'

7-9 awk 中数组的用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 定义
array=("Allen" "Mike" "Messi" "Jerry" "Hanmeimei" "Wang")
# 打印元素
echo ${array[2]}
# 打印元素个数
echo ${#array[@]}
# 打印元素长度
echo ${#array[3]}
# 给元素赋值
array[3]="Li"
# 删除元素
unset array[2];unset array
# 分片访问:
echo ${array[@]:1:3}
# 元素内容替换:
# 只替换第一个e
${array[@]/e/E} ;
#替换所有的e
${array[@]//e/E}
#数组的遍历:
for a in ${array[@]}
do
echo $a
done

在awk中,使用数组时,不仅可以使用1.2..n作为数组下标,也可以使用字符串作为数组下标

*当使用1.2.3..n时,直接使用array[2]访问元素;需要遍历数组时,使用以下形式:

1
2
3
4
str="Allen Jerry Mike Tracy Jordan Kobe Garnet"
split(str,array)
for(i=1;i<=length(array);i++)
print array[i]

  • 当使用字符串作为数组下标时,需要使用array[str]形式访问元素;遍历数组时,使用以下形式:
    1
    2
    3
    4
    5
    6
    array["var1"]="Jin"
    array["var2"]="Hao"
    array["var3"]="Fang"
    for(a in array)
    print array[a]

典型常用例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 统计主机上所有的TCP连接状态数,按照每个TCP状态分类
netstat -an | grep tcp | awk '{array[$6]++}END{for(a in array) print a,array[a]}'
# 计算横向数据总和,计算纵向数据总和
allen 80 90 87 91 348
mike 78 86 93 96 256
Kobe 66 92 82 78 232
Jerry 98 74 66 54 356
Wang 87 21 100 43 322
234 342 451 456 342
BEGIN {
printf "%-10s%-10s%-10s%-10s%-10s%-10s\n","Name","Yuwen","Math","English","Physical","Total"
}
{
total=$2+$3+$4+$5
yuwen_sum+=$2
math_sum+=$3
eng_sum+=$4
phy_sum+=$5
printf "%-10s%-10d%-10d%-10d%-10d%-10d\n",$1,$2,$3,$4,$5,total
}
END {
printf "%-10s%-10d%-10d%-10d%-10d\n","",yuwen_sum,math_sum,eng_sum,phy_sum
}

7-10 一个复杂的 awk 处理生产数据的例子

数据格式如下(db.log.201901)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
2019-01-29 00:58:30 1 Batches: user allen insert 22498 records into database:product table:detail, insert 20771 records successfully,failed 1727 records
2019-01-29 00:58:30 2 Batches: user mike insert 29378 records into database:product table:detail, insert 21426 records successfully,failed 7952 records
2019-01-29 00:58:30 3 Batches: user jerry insert 22779 records into database:product table:detail, insert 9397 records successfully,failed 13382 records
2019-01-29 00:58:30 4 Batches: user tracy insert 25232 records into database:product table:detail, insert 21255 records successfully,failed 3977 records
#统计每个人员分别插入了多少条record进数据库
BEGIN {
printf "%-10s%-20s\n","User","Total Records"
}
{
USER[$6]+=$8
}
END {
for(u in USER)
printf "%-10s%-20d\n", u,USER[u]
}
#统计每个人分别插入成功了多少record,失败了多少record
BEGIN {
printf "%-10s%-20s%-20s\n","User","Sucess_Records","Failed_Records"
}
{
SUCCESS[$6]+=$14
FAIL[$6]+=$17
}
END {
for(u in SUCCESS)
printf "%-10s%-20d%-20d\n",u,SUCCESS[u],FAIL[u]
}
# 将例子1和例子2结合起来,一起输出,输出每个人分别插入多少数据,多少成功,多少失败,并且要格式化输出,加上标题
BEGIN {
printf "%-10s%-20s%-20s%-20s\n","User","Total","Sucess","Failed"
}
{
TOTAL[$6]+=$8
SUCCESS[$6]+=$14
FAIL[$6]+=$17
}
END {
for(u in SUCCESS)
printf "%-10s%-20d%-20d%-20d\n",u,TOTAL[u],SUCCESS[u],FAIL[u]
}
## 在例子3的基础上,加上结尾,统计全部插入记录数,成功记录数,失败记录数
代码:
BEGIN {
printf "%-10s%-20s%-20s%-20s\n","User","Total","Sucess","Failed"
}
{
TOTAL[$6]+=$8
SUCCESS[$6]+=$14
FAIL[$6]+=$17
}
END {
for(u in SUCCESS)
{
total+=TOTAL[u]
success+=SUCCESS[u]
fail+=FAIL[u]
printf "%-10s%-20d%-20d%-20d\n",u,TOTAL[u],SUCCESS[u],FAIL[u]
}
printf "%-10s%-20d%-20d%-20d\n","",total,success,fail
}
#查找丢失数据的现象,也就是成功+失败的记录数,不等于一共插入的记录数。找出这些数据并显示行号和对应行的日志信息
awk '{if($8!=$14+$17) print NR,$0}' db.log.201901

八、shell操作mysql

8-1 安装 MySQL 数据库,导入测试数据

如何将tmp.sql语句导入到mysql中执行?

1
2
# school 表示数据库名称
mysql -udbuser -p123456 -h localhost school < /home/user/wujunqi/tmp.sql

8-2 Shell 脚本与 MySQL 数据库交互 (增删改查)

mysql命令参数

函数 含义
-u 用户名
-p 用户密码
-h 服务器IP地址
-D 连接的数据库
-N 不输出列信息
-B 使用tab键代替默认交互分隔符
-e 执行SQL语句
-E 垂直输出
-H 以HTML格式输出
-X 以XML格式输出

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
#写一个脚本,该脚本可以接收二个参数,参数为需要执行的SQL语句
#!/bin/bash
#
user="dbuser"
password="123456"
host="192.168.184.132"
db_name="$1"
SQL="$2"
mysql -u"$user" -p"$password" -h"$host" -D"$1" -B -e "$SQL"
#查询MySQL任意表的数据,并将查询到的结果保存到HTML文件中
mysql -udbuser -p123456 -h192.168.184.132 -D school -N -X -B -e "SELECT * FROM student;"

8-3 利用 Shell 脚本将文本数据导入到 MySQL 中

如何将文本中格式化的数据导入到MySQL数据库中?

例子1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#处理文本中的数据,将文本中的数据插入MySQL中
# 文件
2025:hao:1989-12-21:male
2026:zhang:1989-12-21:male
2027:ouyang:1989-12-21:male
2028:li:1989-12-21:female
# sql
#!/bin/bash
#
user="dbuser"
password="123456"
host="192.168.184.132"
# input file separate
IFS=":"
cat data-2.txt | while read id name birth sex
do
mysql -u"$user" -p"$password" -h"$host" -e "INSERT INTO school.student2 values('$id','$name','$birth','$sex')"
done

例子二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#处理文本中的数据,将文本中的数据插入MySQL中
# 文件
2021||hao||1989-12-21||male
2022||zhang||1989-12-21||male
2023||ouyang||1989-12-21||male
2024||li||1989-12-21||female
#sql
#!/bin/bash
#
user="dbuser"
password="123456"
host="192.168.184.132"
mysql_conn="mysql -u'$user' -p'$password' -h'$host'"
IFS="||"
cat data-2.txt | while read id name birth sex
do
mysql -u"$user" -p"$password" -h"$host" -e "INSERT INTO school.student2 values('$id','$name','$birth','$sex')"
done
# id > 1014的才添加
#!/bin/bash
#
user="dbuser"
password="123456"
host="192.168.184.132"
mysql_conn="mysql -u"$user" -p"$password" -h"$host""
cat data.txt | while read id name birth sex
do
if [ $id -gt 1014 ];then
$mysql_conn -e "INSERT INTO school.student1 values('$id','$name','$birth','$sex')"
fi
done

8-5 备份 MySQL 数据,并通过 FTP 将其传输到远端主机 - 上

备份MySQL中的库或表

mysqldump

常用参数
| 函数 | 含义 |
| ——| —— |
|-u|用户名|
|-p|密码|
|-h|服务器IP地址|
|-d|等价于–no-data只导出表结构|
|-t|等价于–no-create-info 只导出数据,不导出建表语句|
|-A|等价于–all-databases|
|-B|等价于–databases 导出一个或多个数据库|

FTP常用指令
| 函数 | 含义 |
| ——| —— |
|open|与FTP服务器建立连接,例子:open 192.168.184.3|
|user|有权限登录FTP服务器的用户名和密码,例子:user ftp_user redhat|

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#将school中的score表备份,并且将备份数据通过FTP传输到192.168.184.3的/data/backup目录下
#!/bin/bash
#
db_user="dbuser"
db_password="123456"
db_host="192.168.184.132"
ftp_user="ftp_user"
ftp_password="redhat"
ftp_host="192.168.184.3"
dst_dir="/data/backup"
time_date="`date +%Y%m%d%H%M%S`"
file_name="school_score_${time_date}.sql"
function auto_ftp
{
ftp -niv << EOF
open $ftp_host
user $ftp_user $ftp_password
cd $dst_dir
put $1
bye
EOF
}
mysqldump -u"$db_user" -p"$db_password" -h"$db_host" school score > ./$file_name && auto_ftp ./$file_name

九、一个实际例子

需求描述

  • 实现一个脚本工具,该脚本提供类似supervisor功能,可以对进程进行管理;
  • 一键查看所有进程运行状态
  • 单个或批量启动进程,单个或批量停止进程
  • 提供进程分组功能,可以按组查看进行运行状态,可以按组启动或停止该组内所有进程

查看服务器所有进程脚本app_status.sh 配置文件process.cfg

实现思路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
程序主流程设计:
./app_status.sh执行有三种情况:
1 无参数 列出配置文件中所有进程的运行信息
2 -g GroupName 列出GroupName组内的所有进程
3 process_name1 列出指定进程的运行信息
function get_all_group
说明:该函数无需输入任何参数;返回配置文件 process.cfg中所有的组信息,例如WEB、DB等
function get_all_process
说明:该函数无需输入任何参数;返回配置文件 process.cfg中所有的进程信息。
function get_process_pid_by_name
说明:该函数接收一个参数,参数为进程名称;返回值是一个PID的列表,可能有一个PID,也可能有多个
function get_process_info_by_pid
说明:该函数接收一个参数,参数为进程PID;返回值是一个进程运行信息的列表,列表包含运行状态、CPU占用率、内存占用率、进程启动时间
function is_group_in_config
说明:该函数接收一个参数,参数为组的名称;返回值是0或1,0代表该组在配置文件中,1代表该组不在配置文件中
function get_all_process_by_group
说明:该函数接收一个参数,参数为组名称;返回值是对应组内的所有进程名称列表
function get_group_by_process_name
说明:该函数接收一个参数,参数是一个进程名称;返回值是一个组名
function format_print
说明:该函数接收二个参数,第一个参数为process_name,第二个参数为组名称
返回值,是针对每一个进程PID的运行信息
function is_process_in_config
说明:该函数接收一个参数,参数为进程名称;返回值是0或1,0代表该进程在配置文件中,1代表进程不在配置文件中

具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#!/bin/bash
#
# Func: Get Porcess Status In process.cfg
# Define Variables
HOME_DIR="/root/lesson/9.1"
CONFIG_FILE="process.cfg"
this_pid=$$
function get_all_group
{
G_LIST=`sed -n '/\[GROUP_LIST\]/,/\[.*\]/p' $HOME_DIR/$CONFIG_FILE | egrep -v "(^$|\[.*\])"`
echo "$G_LIST"
}
function get_all_process
{
for g in `get_all_group`
do
P_LIST=`sed -n "/\[$g\]/,/\[.*\]/p" $HOME_DIR/$CONFIG_FILE | egrep -v "(^$|\[.*\])"`
echo "$P_LIST"
done
}
function get_process_pid_by_name
{
if [ $# -ne 1 ];then
return 1
else
pids=`ps -ef | grep $1 | grep -v grep | grep -v $0 | awk '{print $2}'`
echo $pids
fi
}
function get_process_info_by_pid
{
if [ `ps -ef | awk -v pid=$1 '$2==pid{print }' | wc -l` -eq 1 ];then
pro_status="RUNNING"
else
pro_status="STOPED"
fi
pro_cpu=`ps aux | awk -v pid=$1 '$2==pid{print $3}'`
pro_mem=`ps aux | awk -v pid=$1 '$2==pid{print $4}'`
pro_start_time=`ps -p $1 -o lstart | grep -v STARTED`
}
function is_group_in_config
{
for gn in `get_all_group`;do
if [ $gn == $1 ];then
return
fi
done
echo "Group $1 is not in process.cfg"
return 1
}
function is_process_in_config
{
for pn in `get_all_process`;do
if [ $pn == $1 ];then
return
fi
done
echo "Process $1 is not in process.cfg"
return 1
}
function get_all_process_by_group
{
is_group_in_config $1
if [ $? -eq 0 ];then
p_list=`sed -n "/\[$1\]/,/\[.*\]/p" $HOME_DIR/$CONFIG_FILE | egrep -v "(^$|^#|\[.*\])"`
echo $p_list
else
echo "GroupName $1 is not in process.cfg"
fi
}
function get_group_by_process_name
{
for gn in `get_all_group`;do
for pn in `get_all_process_by_group $gn`;do
if [ $pn == $1 ];then
echo "$gn"
fi
done
done
}
function format_print
{
ps -ef | grep $1 | grep -v grep | grep -v $this_pid &> /dev/null
if [ $? -eq 0 ];then
pids=`get_process_pid_by_name $1`
for pid in $pids;do
get_process_info_by_pid $pid
awk -v p_name=$1 \
-v g_name=$2 \
-v p_status=$pro_status \
-v p_pid=$pid \
-v p_cpu=$pro_cpu \
-v p_mem=$pro_mem \
-v p_start_time="$pro_start_time" \
'BEGIN{printf "%-20s%-12s%-10s%-6s%-7s%-10s%-20s\n",p_name,g_name,p_status,p_pid,p_cpu,p_mem,p_start_time}'
done
else
awk -v p_name=$1 -v g_name=$2 'BEGIN{printf "%-20s%-12s%-10s%-6s%-7s%-10s%-20s\n",p_name,g_name,"STOPPED","NULL","NULL","NULL","NULL"}'
fi
}
awk 'BEGIN{printf "%-20s%-10s%-10s%-6s%-7s%-10s%-20s\n","ProcessName---------","GroupName---","Status----","PID---","CPU----","MEMORY----","StartTime---"}'
if [ $# -gt 0 ];then
if [ "$1" == "-g" ];then
shift
for gn in $@;do
is_group_in_config $gn || continue
for pn in `get_all_process_by_group $gn`;do
is_process_in_config $pn && format_print $pn $gn
done
done
else
for pn in $@;do
gn=`get_group_by_process_name $pn`
is_process_in_config $pn && format_print $pn $gn
done
fi
else
for pn in `get_all_process`;do
gn=`get_group_by_process_name $pn`
is_process_in_config $pn && format_print $pn $gn
done
fi

-------------本文结束 感谢您的阅读-------------
  • 本文标题: shell编程实战
  • 本文作者: 吴军旗
  • 本文链接: http://fanqieto.top/2019/04/21/shell编程实战/
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请保留原文链接及作者!
    • 欢迎搜索及关注:番茄技术小栈,所有文章都将同步在公众号上!
坚持原创技术分享,您的支持将鼓励我继续创作!