近期编程时遇到的两件事
1. 给goldmark-mermaid提了个pr
前段时间对博客做了次重构,重构时用到了一个goldmark-mermaid的golang包,它是用来让我的博客支持mermaid的,goldmark-mermaid支持从server端来渲染mermaid,也支持从client端来渲染mermaid,我的服务部置在一台0.5H0.5G的机器上,server端渲染mermaid需要安装无头浏览器插件和npm包,对机器的性能有要求。因此我只能通过client来渲染了。在设置goldmark-mermaid参数的时候,发现client模式不支持传theme参数,theme只能用默认参数,因此在这里提了个PR148来支持这个功能。
2. 一个清理物料包的shell脚本的修改
Linux里面有一个“一切皆文件”的思想,shell脚本用于处理文件也是十分方便的。我们有一个清理过期物料包的需求,将超过多少天的,并且版本包数量超过了多少个的物料包执行删除操作。我首先通过find +mtime把天数超过了的物料包找出来写到一个old.list里,再通过find将所有的物料包路径找出来写到all.list里面,然后再遍历物料包名称,通过grep来过滤all.list来找出每一个物料包的所有版本,传给ls -t按时间倒排,取出数量超了的记到clear.list里面。然后再通过grep -f 取clear.list和old.list的交集,将结果写到to_delete.list里面。几行命令就行了。
1#!/bin/bash
2#*******************************
3...
4main() {
5 # 清理日志
6 >${LOG_FILE}
7
8 cd "${PKG_DIR}"
9
10 # 1. 获取所有物料包的绝对路径
11 find "${PKG_DIR}" -name '*.tgz' -type f >/tmp/all_software.info
12
13 # 2. 获取指定天数前的物料包绝对路径
14 find "${PKG_DIR}" -name '*.tgz' -type f -mtime +${KEEP_DAYS} >/tmp/old_software.info
15
16 # 3. 获取latest目录下所有的物料包相对路径
17 if [ -d "${PKG_DIR}/latest" ]; then
18 cd "${PKG_DIR}/latest"
19 find ./ -name '*.tgz' >/tmp/latest.info
20 else
21 >/tmp/latest.info
22 fi
23
24 >/tmp/clear_file.info
25
26 while read -r file_path; do
27 # 获取文件关键字,如:yaml/ocloud-tcenter-yunapi2-api-server
28 file_key=$(echo "${file_path}" | sed 's/^\.//' | sed 's/\..*//')
29 # 根据时间排序,剔除最新的N个(文件名相同的算一个,取先读到的),N之后的全部旧版本
30 grep "${file_key}" /tmp/all_software.info | xargs ls -t 2>/dev/null | awk -F/ '{if($NF!=a){print};a=$NF}' | awk -v keep="${KEEP_VERSIONS}" 'NR>keep' >>/tmp/clear_file.info
31 done </tmp/latest.info
32
33 # 4. 交集:既是旧文件又不是最新N个的,才删除
34 grep -f /tmp/clear_file.info /tmp/old_software.info >/tmp/to_delete.info
35
36 if [ "${DRY_RUN}" -eq 1 ]; then
37 echo "以下文件将被删除(dry-run模式,不会实际删除):" | tee "${LOG_FILE}"
38 cat /tmp/to_delete.info | tee -a "${LOG_FILE}"
39 else
40 echo "开始删除以下文件:" | tee "${LOG_FILE}"
41 cat /tmp/to_delete.info | tee -a "${LOG_FILE}"
42 cat /tmp/to_delete.info | xargs rm -v | tee -a "${LOG_FILE}"
43 fi
44
45 echo "清理完成,日志见 ${LOG_FILE}"
46}
47
48main "$@"
49
但是领导说写的临时文件太多了,要求把这些临时文件全部用变量代替。起初我觉得应该也不是难事,但真正做起来才发现用shell来编程实在是太麻烦了,还好有AI来做这种事。下面是在AI帮助下的实现。
1...
2# 用法:extract_file_key path
3# 提取文件关键字,例如:
4# 输入: /aaa/bbb/ccc/yaml/ocloud-tcenter-yunapi2-api-server.111.222.tgz
5# 输出: yaml/ocloud-tcenter-yunapi2-api-server
6extract_file_key() {
7 local path="$1"
8
9 filename=$(basename "$path")
10 # 去掉从第一个 '.' 开始的版本号部分
11 name="${filename%%.*}"
12 # 取父目录名
13 local parent
14 dirpath=$(dirname "${path}")
15 parent="$(basename ${dirpath})"
16
17 # 返回 目录名/文件名前缀
18 printf '%s/%s' "$parent" "$name"
19}
20
21# 用法:filter_array_by_keyword out_array_name key src_array_name
22# 从数组 src_array_name 中筛选出 basename 包含 key 的项,结果赋给 out_array_name
23filter_array_by_keyword() {
24 local out_array_name="$1"
25 local key="$2"
26 local src_array_name="$3"
27 local src=()
28 while IFS= read -r -d '' item; do
29 src+=("$item")
30 done < <(eval "printf '%s\\0' \"\${${src_array_name}[@]}\"")
31
32 local matches=()
33 local p
34 for p in "${src[@]}"; do
35 if [[ "$(extract_file_key "$p")" == "$key" ]]; then
36 matches+=("$p")
37 fi
38 done
39
40 # 将结果写回目标数组
41 eval "$out_array_name=(\"\${matches[@]}\")"
42}
43
44main() {
45 mkdir -p ${BACKUP_DIR}
46 LOG_FILE="/data/backup/tcs/clear_info_$(date +%F).log"
47 # 清理日志
48 >${LOG_FILE}
49
50 cd "${PKG_DIR}"
51
52 all_packages=()
53 latest_packages=()
54 to_delete=()
55
56 # 获取所有物料包的绝对路径
57 while IFS= read -r -d '' f; do
58 all_packages+=("$f")
59 done < <(find "${PKG_DIR}" -type f -name '*.tgz' -print0)
60
61 # 获取latest目录下所有的物料包绝对路径, latest是软链包目录(查找以 .tgz 结尾的符号链接)
62 while IFS= read -r -d '' f; do
63 latest_packages+=("$f")
64 done < <(find "${PKG_DIR}/latest" -type l -name '*.tgz' -print0)
65
66 for file_path in "${latest_packages[@]}"; do
67 # 获取文件关键字,如:yaml/ocloud-tcenter-yunapi2-api-server
68 file_key="$(extract_file_key "$file_path")"
69 matches_array=()
70 # 获取所有匹配该关键字的物料包版本, 存入 matches_array
71 filter_array_by_keyword matches_array "$file_key" all_packages
72 echo "查找物料包:${file_key},共找到 ${#matches_array[@]} 个版本" | tee -a "${LOG_FILE}"
73 echo "匹配到的版本列表:" | tee -a "${LOG_FILE}"
74 printf '%s\n' "${matches_array[@]}" | tee -a "${LOG_FILE}"
75 # 数量小于 KEEP_VERSIONS,跳过
76 if [ "${#matches_array[@]}" -le "${KEEP_VERSIONS}" ]; then
77 echo "物料包 ${file_key} 版本数不足 ${KEEP_VERSIONS} 个,不进行版本清理。" | tee -a "${LOG_FILE}"
78 continue
79 fi
80 # 将 matches_array 按 mtime 降序排序
81 local -a lines=()
82 local p mtime
83 for p in "${matches_array[@]}"; do
84 mtime=$(stat -c %Y -- "$p" 2>/dev/null || echo 0)
85 lines+=("${mtime}|${p}")
86 done
87
88 IFS=$'\n' sorted=($(printf '%s\n' "${lines[@]}" | sort -t'|' -k1,1nr))
89 unset IFS
90
91 # KEEP_VERSIONS 之后的版本,且修改时间早于 KEEP_DAYS 的,删除
92 for ((i = KEEP_VERSIONS; i < ${#sorted[@]}; i++)); do
93 # split by |
94 entry="${sorted[i]}"
95 entry_path="${entry#*|}"
96 entry_time="${entry%%|*}"
97 entry_date=$(date -d @"${entry_time}" +%F)
98 if [ "${entry_time}" -gt $(($(date +%s) - KEEP_DAYS * 86400)) ]; then
99 echo "保留物料包(在保留天数内):${entry_path}, 修改日期: ${entry_date}" | tee -a "${LOG_FILE}"
100 continue
101 fi
102 echo "即将删除过期物料包: ${entry_path}, 修改日期: ${entry_date}" | tee -a "${LOG_FILE}"
103 to_delete+=("${entry_path}")
104 done
105
106 done
107
108 if [ "${DRY_RUN}" -eq 1 ]; then
109 echo "以下文件将被删除(dry-run模式,不会实际删除):" | tee -a "${LOG_FILE}"
110 printf '%s\n' "${to_delete[@]}" | tee -a "${LOG_FILE}"
111 else
112 echo "开始删除以下文件:" | tee "${LOG_FILE}"
113 printf '%s\n' "${to_delete[@]}" | tee -a "${LOG_FILE}"
114 printf '%s\n' "${to_delete[@]}" | xargs rm -v | tee -a "${LOG_FILE}"
115 fi
116
117 echo "清理完成,日志见 ${LOG_FILE}"
118}
119
120main "$@"
121
为什么不写临时文件难度就上升了呢。shell的函数传参基本只能基于字符串来传,但是这里有很多地方需要用到将文件按行记录到数组里面,然后将数组传递到函数。由于shell的变量的作用域默认是global的。所以将一个数组传给函数大体上有两种方式:1)传变量的名字,在函数里面通过eval来取变量数组的内容取出来;2)将数组转为字符串传给函数。这两种方式都很麻烦。我们的shell版本比较低,mapfile, read_array等命令都支持的不太好,基本用不了。所以数组的处理就用了如代码中那样很原始的方式:通过eval把变量数组的内容取出来,这时取的是一个字符串,再通过while循环来按行读这个变量存到数组里面,最终函数才能传递数组参数了。
这也算得上是碰到不懂行的领导,做起事来处处是难处的一个明显的例子了。

评论列表:
暂无评论 😭