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 目录下文件的依赖深度关系。
分析
任务分为四步:
- 提取依赖关系,将依赖关系保存入
./tmp.txt中 - 利用
./tmp.txt中的依赖关系构建依赖图 - 根据依赖图计算依赖深度
- 输出结果
难点:
-
#include的文件有本地头文件的和系统头文件两种,依赖关系应当只考虑本地头文件。在实际处理时简单认为""引用的是本地头文件,<>引用的是系统头文件。 -
对于本地头文件的引用方式,有可能是相对路径(如使用
./,../),也有可能是绝对路径。在计算时需要统一转化为绝对路径,否则文件名重名时会有问题。实际检测发现有众多文件重名,举例如下:文件名: 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 # 略 -
还可能有
#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