MINIBLOG

Blog Note Tags Links About
Home Search
Mar 11, 2026
miniyuan

文本编辑和脚本


注:

  • 事实上可以直接在 VSCode 中编辑,vim 感觉在虚拟机上用起来不爽。当然你还可以下载 VSCode Vim 插件。

题目 2

可以使用

vim $(bash ./question2.sh)

来传递参数给 vim。直接使用 | xagrs 是错误的,vim 会警告

Warning: Input is not from a terminal

因为 vim 需要 stdin 和 stdout 是终端而不能是管道。实际测试发现错误命令可以正常编辑,但是在关闭 vim 后,终端回显不会恢复。

题目 3

编写一个 shell 脚本 code_stats.sh,统计 ABACUS 软件里 source 目录的代码文件数量和总行数。

题目 3 源代码

#!/bin/bash

filename="source/"

# 利用上一次作业题目四任务4的指令
# 尽量给"${filename}"加上引号,否则文件名中有空格会有问题
# 统计 .cpp 文件数量
cpp_file_tot=$(find "${filename}" -name '*.cpp' | wc -l)
# 统计 .h 文件数量
h_file_tot=$(find "${filename}" -name '*.h' | wc -l)

# 利用上一次作业题目五任务3的指令
# 统计 .cpp 文件总行数
cpp_line_tot=$(find "${filename}" -name '*.cpp' -exec wc -l {} \; | awk '{sum += $1} END {print sum}')
# 统计 .h 文件总行数
h_line_tot=$(find "${filename}" -name '*.h' -exec wc -l {} \; | awk '{sum += $1} END {print sum}')

echo "${filename}*.cpp files: ${cpp_file_tot}"
echo "${filename}*.h files: ${h_file_tot}"
echo "lines in ${filename}*.cpp files: ${cpp_line_tot}"
echo "lines in ${filename}*.h files: ${h_line_tot}"

题目 3 运行结果

source/*.cpp files: 1068
source/*.h files: 698
lines in source/*.cpp files: 349570
lines in source/*.h files: 75416

题目 4

编写 shell 脚本 comment_rate.sh,统计 ABACUS 软件里 source 目录代码注释率。

题目 4 源代码

#!/bin/bash

filename="source/"

# 按照上一题计算 .cpp .h 文件数量和总行数
cpp_file_tot=$(find "${filename}" -name '*.cpp' | wc -l)
h_file_tot=$(find "${filename}" -name '*.h' | wc -l)

cpp_line_tot=$(find "${filename}" -name '*.cpp' -exec wc -l {} \; | awk '{sum += $1} END {print sum}')
h_line_tot=$(find "${filename}" -name '*.h' -exec wc -l {} \; | awk '{sum += $1} END {print sum}')

# 计算 .cpp .h 文件注释行数
# ^[[:space:]]*\/\/ 表示若干空白字符后接 // 开头
# ^[[:space:]]*\/\* 表示若干空白字符后接 /* 开头
# ^[[:space:]]*\* 表示若干空白字符后接 * 开头
# \*\/[[:space:]]*$ 表示 */ 后接若干空白字符结尾
# 注意不考虑空白字符统计行数会有所减少
cpp_comment_tot=$(find "${filename}" -name '*.cpp' -exec awk '/^[[:space:]]*\/\// || /^[[:space:]]*\/\*/ || /^[[:space:]]*\*/ || /\*\/[[:space:]]*$/' {} \; | wc -l)
h_comment_tot=$(find "${filename}" -name '*.h' -exec awk '/^[[:space:]]*\/\// || /^[[:space:]]*\/\*/ || /^[[:space:]]*\*/ || /\*\/[[:space:]]*$/' {} \; | wc -l)

# 计算 .cpp .h 文件注释率
# 保留两位小数,使用百分数
cpp_comment_rate=$(echo "scale=2; ${cpp_comment_tot} * 100 / ${cpp_line_tot}" | bc)
h_comment_rate=$(echo "scale=2; ${h_comment_tot} * 100 / ${h_line_tot}" | bc)

echo "${filename}*.cpp files: ${cpp_file_tot}"
echo "${filename}*.h files: ${h_file_tot}"
echo "lines in ${filename}*.cpp files: ${cpp_line_tot}"
echo "lines in ${filename}*.h files: ${h_line_tot}"

echo "comment lines in ${filename}*.cpp files: ${cpp_comment_tot}"
echo "comment lines in ${filename}*.h files: ${h_comment_tot}"
echo "comment rate for ${filename}*.cpp files: ${cpp_comment_rate}%"
echo "comment rate for ${filename}*.h files: ${h_comment_rate}%"

题目 4 运行结果

source/*.cpp files: 1068
source/*.h files: 698
lines in source/*.cpp files: 349570
lines in source/*.h files: 75416
comment lines in source/*.cpp files: 29276
comment lines in source/*.h files: 16836
comment rate for source/*.cpp files: 8.37%
comment rate for source/*.h files: 22.32%

题目 5

编写一个 shell 脚本 file_depth_analysis.sh,分析 ABACUS 软件 source 目录下文件的依赖深度关系。

分析

任务分为四步:

  1. 提取依赖关系,将依赖关系保存入 ./tmp.txt 中
  2. 利用 ./tmp.txt 中的依赖关系构建依赖图
  3. 根据依赖图计算依赖深度
  4. 输出结果

难点:

  1. #include 的文件有本地头文件的和系统头文件两种,依赖关系应当只考虑本地头文件。在实际处理时简单认为 "" 引用的是本地头文件,<> 引用的是系统头文件。

  2. 对于本地头文件的引用方式,有可能是相对路径(如使用 ./,../),也有可能是绝对路径。在计算时需要统一转化为绝对路径,否则文件名重名时会有问题。实际检测发现有众多文件重名,举例如下:

    文件名: prepare_unitcell.h
    路径:
        - source/source_estate/module_dm/test/prepare_unitcell.h
        - source/source_estate/test/prepare_unitcell.h
        - source/source_cell/module_neighbor/test/prepare_unitcell.h
        - source/source_cell/test/prepare_unitcell.h
        - source/source_lcao/module_deltaspin/test/prepare_unitcell.h
        - source/source_lcao/module_hcontainer/test/prepare_unitcell.h
        - source/source_io/test_serial/prepare_unitcell.h
        - source/source_io/test/prepare_unitcell.h
    
    文件名: lapack.h
    路径:
        - source/source_base/module_container/base/third_party/lapack.h
        - source/source_base/module_container/ATen/kernels/lapack.h
    
    文件名: memory.h
    路径:
        - source/source_base/module_container/ATen/kernels/memory.h
        - source/source_base/memory.h
    
    # 略
  3. 还可能有 #ifdef 的问题(暂时不考虑)

全局变量设置

#!/bin/bash

dirname="source"
tmpfile="tmp.txt"
declare -A deps depth processing # 关联数组
# deps[filename] 表示 filename 依赖的本地头文件,用 | 分隔
# depth[filename] 表示 filename 的依赖深度,循环依赖则为 -1
# processing[filename] 表示 filename 是否正在计算深度,用于发现循环依赖
declare -a max_files leaves # 索引数组
# max_files 储存深度最大的文件名
# leaves 储存叶子文件名

解析文件依赖

# 解析文件依赖的函数
parse_file_deps() {
    local file        # 当前解析的文件名
    local source_path # 文件以 source/ 开头的绝对路径
    local source_dir  # 文件以 source/ 开头的目录路径

    file=$1
    source_path="${dirname}/${file#*/}"
    source_dir=$(dirname "$source_path")

    awk -v src="$source_path" -v dir="$source_dir" '
        # 本地头文件(LOCAL 开头)
        /^[[:space:]]*#include[[:space:]]*"/ {
            split($0, a, "\"")
            inc = a[2]
            
            # 简化依赖文件路径
            if (inc ~ /^source\//) {                      # source/ 开头的绝对路径
                abs = inc
            } else if (inc ~ /^\// && inc ~ /source\//) { # 包含 source/ 的绝对路径
                sub(/.*source\//, "source/", inc)
                abs = inc
            } else {                                      # 不包含 source/ 的相对路径
                abs = dir "/" inc                         # 拼接当前目录
                gsub(/\/\.\//, "/", abs)                  # 处理 ./
                gsub(/\/+/, "/", abs)                     # 合并多个 /
                while (abs ~ /\/\.\.\//) {                # 处理 ../
                    sub(/\/[^\/]+\/\.\.\//, "/", abs)
                }
            }
            printf "LOCAL|%s|%s\n", src, abs
        }
        # 系统头文件(SYS 开头)
        /^[[:space:]]*#include[[:space:]]*</ {
            split($0, a, "[<>]")
            printf "SYS|%s|%s\n", src, a[2]
        }
    ' "$file"
}

# 收集所有依赖关系
: > "$tmpfile"              # 创建(或者清空)$tmpfile
while IFS= read -r file; do # 读取整行
    parse_file_deps "$file" >> "$tmpfile"
done < <(find "$dirname" \( -name "*.cpp" -o -name "*.h" \))

# 构建依赖图
while IFS='|' read -r type src inc; do # 按照 | 进行分割
    # ${var:+val} 表示条件追加,也即 var 已设置时返回 val,否则返回空
    [[ "$type" == "LOCAL" ]] && deps[$src]+="${deps[$src]:+|}${inc}"
done < "$tmpfile"

# 为所有被依赖的文件初始化空列表
for src in "${!deps[@]}"; do # @ 表示获取所有元素
    # 建议使用 <<< (Here String) 传递单行字符串
    IFS='|' read -ra incs <<< "${deps[$src]}"
    for inc in "${incs[@]}"; do
        [[ -n "$inc" && -z "${deps[$inc]}" ]] && deps[$inc]=""
    done
done

# 所有键的总数(也即本地头文件总数)
total=${#deps[@]}

计算依赖深度

# 递归计算深度(DFS)
calc_depth() {
    local file=$1 # 要计算深度的文件名
    local max=-1  # 子依赖的最大深度

    [[ -n "${depth[$file]}" ]] && return     # 已计算
    [[ "${processing[$file]}" == "1" ]] && { # 正在处理,说明产生循环依赖
        depth[$file]=-1                      # 循环依赖的深度为 -1
        return
    }

    processing[$file]=1 # 标记正在处理

    IFS='|' read -ra children <<< "${deps[$file]}"
    for child in "${children[@]}"; do
        [[ -z "$child" ]] && continue
        calc_depth "$child"                                     # 递归计算子依赖的深度
        [[ ${depth[$child]} -gt $max ]] && max=${depth[$child]} # 比较最大深度
    done

    depth[$file]=$((max + 1)) # 没有子依赖则为叶子节点
    processing[$file]=0
}

# 计算所有深度
for file in "${!deps[@]}"; do
    calc_depth "$file"
done

输出结果

# 统计结果
max_depth=-1

for file in "${!depth[@]}"; do
    d=${depth[$file]}
    [[ $d -eq -1 ]] && continue

    ((d > max_depth)) && {
        max_depth=$d
        max_files=()
    }
    ((d == max_depth)) && max_files+=("$file")
    ((d == 0)) && leaves+=("$file")
done

echo "total files: $total"
echo "max depth: $max_depth"
echo ""
echo "the file of depth $max_depth:"
echo "------------------------------------------"
for f in "${max_files[@]}"; do
    # # 表示从字符串开头删除最短匹配的模式,也即删除 source/
    echo "    ${f#source/}"
done

echo ""
echo "the first 20 leave files:"
echo "------------------------------------------"
for ((i = 0; i < ${#leaves[@]} && i < 20; i++)); do
    echo "    ${leaves[$i]#source/}"
done
[[ ${#leaves[@]} -gt 20 ]] && echo "another $((${#leaves[@]} - 20)) leave files are found"

题目 5 运行结果

total files: 4170
max depth: 7

the file of depth 7:
------------------------------------------
    source_lcao/module_gint/gint_rho_gpu.cpp
    source_lcao/module_gint/gint_tau_gpu.cpp
    source_lcao/module_gint/gint_fvl.cpp
    source_lcao/module_gint/gint_vl_metagga_gpu.cpp
    source_lcao/module_gint/gint_rho.cpp
    source_lcao/module_gint/gint_fvl_meta_gpu.cpp
    source_lcao/module_gint/gint_vl.cpp
    source_lcao/module_gint/gint_vl_nspin4_gpu.cpp
    source_lcao/module_gint/gint_vl_metagga_nspin4_gpu.cpp
    source_lcao/module_gint/gint_dvlocal.cpp
    source_lcao/module_gint/gint_env_gamma.cpp
    source_lcao/module_gint/gint_vl_gpu.cpp
    source_lcao/module_gint/gint_tau.cpp
    source_lcao/module_gint/gint_vl_metagga.cpp
    source_lcao/module_gint/gint_fvl_gpu.cpp
    source_lcao/module_gint/gint_vl_nspin4.cpp
    source_lcao/module_gint/gint_fvl_meta.cpp
    source_lcao/module_gint/gint_vl_metagga_nspin4.cpp

the first 20 leave files:
------------------------------------------
    source_lcao/module_lr/source_basis/module_ao/parallel_orbitals.h
    source_lcao/module_lr/dm_trans/source_base/libm/libm.h
    source_hamilt/module_surchem/test/source_pw/module_pwdft/structure_factor.h
    source_io/module_wannier/source_base/tool_title.h
    source_lcao/source_lcao/setup_dm.h
    source_pw/module_pwdft/source_pw/module_pwdft/setup_pwwfc.h
    source_io/module_hs/source_base/global_variable.h
    source_pw/module_pwdft/source_hamilt/module_xc/exx_info.h
    source_base/projgen.h
    source_lcao/module_lr/utils/lr_util.hpp
    source_cell/test/source_estate/magnetism.h
    source_basis/module_nao/test/source_base/ylm.h
    source_lcao/module_ri/source_cell/unitcell.h
    source_lcao/module_operator_lcao/source_base/global_variable.h
    source_lcao/module_dftu/source_cell/klist.h
    source_cell/source_base/realarray.h
    source_io/module_ctrl/source_lcao/module_dftu/dftu.h
    source_lcao/module_dftu/source_estate/elecstate_lcao.h
    source_estate/module_dm/test/gtest/gtest.h
    source_io/module_wf/source_basis/module_pw/pw_basis_k.h
another 2549 leave files are found
目录
  • 题目 2
  • 题目 3
    • 题目 3 源代码
    • 题目 3 运行结果
  • 题目 4
    • 题目 4 源代码
    • 题目 4 运行结果
  • 题目 5
    • 分析
    • 全局变量设置
    • 解析文件依赖
    • 计算依赖深度
    • 输出结果
    • 题目 5 运行结果
© 2026 miniyuan. All rights reserved.
Go to miniyuan's GitHub repo