avatar

近期编程时遇到的两件事


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循环来按行读这个变量存到数组里面,最终函数才能传递数组参数了。

这也算得上是碰到不懂行的领导,做起事来处处是难处的一个明显的例子了。

评论列表:

暂无评论 😭