avatar

Catalog
从代码中学习shell(一)

前言

4月底的时候,写了一些shell脚本,实现了某个项目功能的自动化,虽然shell基本算是过时了,但在一些场景,它可能比Python用起来更方便。由于对shell不算很熟悉,因此在编写脚本时查询了些许资料。

现在,想将这些知识点总结下来,我选择的方式是从代码中学习,虽然读别人的代码会比较头疼,但这代码是自己写过一遍,更熟悉一些。其次读代码的方式,相比直接看定义,更偏向实践,尽管找想要的功能可能不方便,但是只需稍作修改,就可以拿去直接用了。

需求分析

  1. 从服务器下载压缩包文件
  2. 将压缩包文件解压,拿到json文件
  3. 将json文件分离成不同部分,并调用python脚本完成对数据库的查询
  4. 将查询的结果及对应的分离后的json文件回传至服务器
  5. 每天下午2点自动完成上述操作

代码分析

下载文件

shell
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
#!/usr/bin/sh

# 1.首先是shell中定义函数的方式,直接用函数名就可以调用,不必加上括号
# function xxx() {
# ...
# }
# xxx

function download_file() {

# 2.定义函数中要用到的局部变量,定义时等号两边不能有空格
ftp_user="pokemon"
ftp_pass="pokemon@123456"
ftp_host="ftp://65.34.87.12"
subdir="Type/"

# 3.执行`date -I`指令可以获得日期,形式为2022-05-20
# 通过${date//'-'/}可以替换掉日期中的符号'-',得到字符串20220520,此部分可以参考文末链接
# 接下来定义一个数组,数组元素用空格分隔开
date=`date -I`
date_pat=${date//'-'/}
file_pat_array=("Grass" "Fire" "Water")

# 4.用curl指令,获取ftp服务器指定目录下的文件内容
# -l参数,列出目录下的内容
# -s参数,静默模式,不显示传输过程和错误信息
# -u参数,指定用户:密码
files=$(curl -l -s -u ${ftp_user}:${ftp_pass} ${ftp_host}/${subdir})

# 5.遍历目录下的所有文件,和数组中定义的文件开头与当天日期进行字符串匹配,
# 若发现文件名有形如"Grass???20220520???"格式的,就将其下载。
# 这里if语句比较,需要用连续两个中括号;遍历数组则用${arr[*]}的形式
for f in ${files[*]};do
for file_pat in ${file_pat_array[*]};do
if [[ $f == $file_pat*$date_pat* ]]
then
echo "start download $f ..."
info=$(curl -O -u ${ftp_user}:${ftp_pass} ${ftp_host}/${subdir}/${f})
echo "finish download $f"
fi
done
done
}

解压与解密

shell
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
# Decrypt And Extract Zip Data
function decrypt_gzip() {

dat=".dat"
json_pattern=".json"
dir="/"

# 1.解压压缩包文件,$1指定了传入的第一个参数,例如`./test arg1`
# 这里在循环中匹配了目录文件,.dat文件和.json文件,这么写是因
# 为刚好这个压缩包只有这三种类型文件
for line in `tar -xvzf $1`;do
if [[ $line == *$json_pattern ]]
then
json_file=$line
elif [[ $line == *$dat ]]
then
dat_file=$line
elif [[ $line == *$dir ]]
then
dir=$line
fi
done

# 2.这里使用jq命令处理json文件,安装与使用放在文末
# 然后截取字符串,去掉第一个和最后一个字符
str=$(jq '.sign' $json_file)
sign=${str:1:-1}

# 3.这部分是通过外部给定的解法,先解出key,再用key解开.dat包得到压缩文件
# 这里解.dat包,参数的含义需要通过man enc查看
key=$(./license-sdk-go.license-sdk-go -p AAAAA-BBBBB-CCCCC-11111-22222.lic -d $sign)
openssl enc -aes-256-cbc -d -K $key -iv 0 -in $dat_file -out out.tar.gz

# 4.最后解压解密后的压缩包,删除不必要的文件
tar -xvzf out.tar.gz
rm out.tar.gz
rm -r $dir
}

文件过滤

shell
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
# Classify Different Data Into Json
function classify_data() {

water_pat="Water_all"
fire_pat="Fire_all"
grass_pat="Grass_all"
date=`date -I`
date_pat=${date//'-'/}

# 这部分主要逻辑都在这,过滤信息:
# 1."*.gz"的操作用来遍历当前目录下的所有.gz文件
# 2.根据预先定义的pattern,过滤出对应的.gz文件
# 3.调用前面的函数decrypt_gzip进行解密与解压
# 4.将解压后目录中的.json文件移至当前目录,其余删除
# 5.通过grep提取包含特定字段的行,附加到/重定向到指定.json文件中
# 6.这里补充一个指令,wc可以用来查看文件的字节/字/行的数量
# 7.将提取出的.json文件的内容作为参数,调用其它目录下的python脚本结果保存到新的.json文件中
for f in *.gz;do
if [[ $f == $water_pat* ]]
then
echo $f
decrypt_gzip $f
mv package/all/water_info.json `pwd`
rm -rf package/
grep '"type": "SINGLE"' water_info.json >> single_plaintext${date_pat}.json
grep '"type": "DOUBLE"' water_info.json > double_crypttext.json
rm -f pokemon_info.json
python3 ../query_match/match_info_map.py ./double_crypttext.json double_plaintext${date_pat}.json
rm -f double_crypttext.json
rm -f $f

elif [[ $f == $fire_pat* ]]
then
echo $f
decrypt_gzip $f
mv package/all/fire_info.json `pwd`
rm -rf package/
grep '"type": "SINGLE"' fire_info.json >> single_plaintext${date_pat}.json
rm -f fire_info.json
rm -f $f

elif [[ $f == $grass_pat* ]]
then
echo $f
decrypt_gzip $f
mv package/all/grass_info.json `pwd`/grass_info${date_pat}.json
rm -rf package/
python3 ../query_match/match_info_map.py ./grass_info${date_pat}.json grass_info_hash_map${date_pat}.json
rm -f $f
fi
done
}

文件上传

shell
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
# Upload Parsed Data
function upload_data() {

classify_data
ftp_user="pokemon"
ftp_pass="pokemon@123456"
ftp_host="ftp://65.34.87.12"
subdir="Result/"
date=`date -I`
date_pat=${date//'-'/}

# 1.这里通过md5sum计算MD5值,获取到对应文件的哈希值
md5sum single_plaintext${date_pat}.json >> pokemon_md5check${date_pat}
md5sum double_plaintext${date_pat}.json >> pokemon_md5check${date_pat}
md5sum grass_info${date_pat}.json >> pokemon_md5check${date_pat}
md5sum grass_info_hash_map${date_pat}.json >> pokemon_md5check${date_pat}

# 2.将生成后的数据文件,上传服务器,并清除掉本地文件
# 3.这里上传文件需要指定参数-T,大括号内的所有文件用逗号隔开,不能有空格也不能换行!
curl -u ${ftp_user}:${ftp_pass} ${ftp_host}/${subdir}/ -T \
"{single_plaintext${date_pat}.json,double_plaintext${date_pat}.json,grass_info${date_pat}.json,grass_info_hash_map${date_pat}.json,pokemon_md5check${date_pat}}"

rm -rf *${date_pat}.json
}

# 3.最后进行函数调用,这里upload_data会调用classify_data,
# classify_data内部又会调用decrypt_gzip。但是没有函数
# 调用download_file,所以需要手动调用
download_file
upload_data

定时任务

shell
1
2
3
4
5
6
7
# 最后手动设置一个定时任务
# crontab -e设定时程表,-r删除目前的时程表,-l列出目前的时程表
# f1 f2 f3 f4 f5 program
# 分钟(0-59) 小时(0-23) 一个月中的第几天(1-31) 月份(1-12) 星期几(0-6) 执行的程序
# 这里设定程序,会先切到对应的目录,否则直接用绝对路径执行,内部调用其它脚本时参数可能会出错
crontab -e
0 14 * * * cd /home/pokemon_mission && ./download_decrypt_classify_upload.sh

参考链接

  1. C编程:Shell函数详解(函数定义、函数调用)
  2. C编程:Shell函数参数
  3. CSDN:Linux Shell 字符串替换
  4. 博客园:CentOS7安装jq
  5. 简书:jq简易教程
  6. wangchujiang博客:jq
  7. Just Code:jq解析 json 实例
  8. CSDN:Shell 数组遍历的3种方法
  9. CSDN:curl 命令的使用:HTTP请求、下载文件、FTP上传下载
Author: cataLoc
Link: http://cata1oc.github.io/2019/04/22/%E4%BB%8E%E4%BB%A3%E7%A0%81%E4%B8%AD%E5%AD%A6%E4%B9%A0shell01/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
    微信
  • 支付寶
    支付寶