课程来源:Hack The Box Academy Module 21(Introduction to Bash Scripting),模块页 <https://academy.hackthebox.com/module/21。本篇按小节归纳课程正文与演示,代码保留课程语义;命令行用户处统一用> user@htb[/htb]$ 占位,不保留页面会话用户名。
模块概览
- 等级:Easy / Tier 1;预计时长约 5 小时;共 10 节,包含 9 个 Interactive 练习、0 个 Assessment;页面标注最后更新约 6 年前。
- 前置基础(模块概览列出):Linux Fundamentals、Web Requests。
- 课程思路:用一个贯穿全章的 CIDR.sh 脚本,把参数、变量、条件、循环、分支、函数和 I/O 等语法串起来,边看脚本边拆语法。
章节主线
- 先建立基本认识:Bash 脚本由解释器执行,适合组合命令、过滤数据、自动化重复任务。
- 再逐个拆解脚本组成:条件执行、参数与变量、比较运算符、算术、输入输出控制、循环、分支、函数、调试。
- 每个语法点都回看 CIDR.sh 的对应片段,最终能读懂完整脚本,也能按自己的需求重新组合这些语法。
1. Bourne Again Shell:Bash 与脚本执行
脚本语言与 Bash 的定位
Bash 用于与 Unix 系操作系统交互并给系统下达命令。脚本语言与编程语言最大的区别是不需要先编译,脚本由解释器(这里是 Bash)直接读取执行。做渗透测试或运维时会面对大量数据,掌握终端、过滤和自动化脚本能明显提高处理速度;脚本还是把多个命令组合起来、把单个结果串联起来的主要方式。
Windows 环境也可以通过 Windows Subsystem for Linux (WSL) 运行 Bash,课程明确提到自 2019 年 5 月起 Windows 提供该子系统。
脚本的常见组成
课程把脚本结构拆为以下部分,也是本模块的章节顺序:
- Input & Output(输入输出)
- Arguments, Variables & Arrays(参数、变量与数组)
- Conditional execution(条件执行)
- Arithmetic(算术)
- Loops(循环)
- Comparison operators(比较运算符)
- Functions(函数)
执行方式
课程给出的三种基本执行方式:
bash script.sh <optional arguments>
sh script.sh <optional arguments>
./script.sh <optional arguments>
- bash script.sh / sh script.sh:显式指定解释器处理脚本,不要求文件有执行位。
- ./script.sh:按文件头部的 shebang 指定解释器执行,通常需要先用 chmod +x script.sh 赋予执行权限(下一节会看到)。
- 脚本本身不创建新进程,而是由调用它的解释器执行。
贯穿案例:CIDR.sh
课程用 ./CIDR.sh inlanefreight.com 演示脚本能力:先解析域名得到 IP,再交互询问用户要执行哪些检查,最后显示网段信息并 ping 主机。演示输出要点(非完整原文):
Discovered IP address(es):
165. 22.119.202
Additional options available:
1) Identify the corresponding network range of target domain.
2) Ping discovered hosts.
3) All checks.
*) Exit.
Select your option: 3
NetRange for 165.22.119.202:
NetRange: 165.22.0.0 - 165.22.255.255
CIDR: 165.22.0.0/16
Pinging host(s):
165. 22.119.202 is up.
1 out of 1 hosts are up.
脚本主体(代码保留课程命令语义,中文注释为本笔记归纳):
#!/bin/bash
# 1) 参数检查:没有参数时打印用法并退出
if [ $# -eq 0 ]
then
echo -e "You need to specify the target domain.\n"
echo -e "Usage:"
echo -e "\t$0 <domain>"
exit 1
else
domain=$1
fi
# 2) 查询 IP 对应的网段
function network_range {
for ip in $ipaddr
do
netrange=$(whois $ip | grep "NetRange\|CIDR" | tee -a CIDR.txt)
cidr=$(whois $ip | grep "CIDR" | awk '{print $2}')
cidr_ips=$(prips $cidr)
echo -e "\nNetRange for $ip:"
echo -e "$netrange"
done
}
# 3) 对网段内主机做 ping 存活检测
function ping_host {
hosts_up=0
hosts_total=0
echo -e "\nPinging host(s):"
for host in $cidr_ips
do
stat=1
while [ $stat -eq 1 ]
do
ping -c 2 $host > /dev/null 2>&1
if [ $? -eq 0 ]
then
echo "$host is up."
((stat--)); ((hosts_up++)); ((hosts_total++))
else
echo "$host is down."
((stat--)); ((hosts_total++))
fi
done
done
echo -e "\n$hosts_up out of $hosts_total hosts are up."
}
# 4) 域名解析
hosts=$(host $domain | grep "has address" | cut -d" " -f4 | tee discovered_hosts.txt)
echo -e "Discovered IP address:\n$hosts\n"
ipaddr=$(host $domain | grep "has address" | cut -d" " -f4 | tr "\n" " ")
# 5) 交互菜单
echo -e "Additional options available:"
echo -e "\t1) Identify the corresponding network range of target domain."
echo -e "\t2) Ping discovered hosts."
echo -e "\t3) All checks."
echo -e "\t*) Exit.\n"
read -p "Select your option: " opt
case $opt in
"1") network_range ;;
"2") ping_host ;;
"3") network_range && ping_host ;;
"*") exit 0 ;;
esac
- 课程按注释把脚本拆为 5 个部分:检查给定参数、识别网段、ping 已发现主机、识别域名 IP、提供可用选项。
- 脚本用到的 whois、prips、host、ping 等命令,课程直接使用,没有给出安装步骤;本笔记不补写安装教程。
2. Conditional Execution:条件执行
作用
条件执行让脚本按"条件是否成立"选择执行某段代码;没有它就只能一条命令接一条命令往下跑。条件成立时只执行对应代码段,其他分支被跳过,完成后继续执行条件块后面的命令。
shebang
- 脚本第一行通常是 #!/bin/bash,以 #! 开头,指定解释器路径。
- shebang 也可以指向其他解释器,例如 Python、Perl:
#!/usr/bin/env python
#!/usr/bin/env perl
if / elif / else / fi
课程把 if-else 与 case 并列为最基础的条件判断形式。以 CIDR.sh 参数检查为例,伪代码表达为:
if [ 参数个数等于 0 ]
then
打印 "You need to specify the target domain."
打印空行
打印 Usage
以错误码退出
else
把第一个参数赋给 domain 变量
fi
实际代码如下:
#!/bin/bash
# Check for given argument
if [ $# -eq 0 ]
then
echo -e "You need to specify the target domain.\n"
echo -e "Usage:"
echo -e "\t$0 <domain>"
exit 1
else
domain=$1
fi
- 结构词:if、then、elif、else、fi;fi 表示条件块结束。
- 本段用到的组件:#!/bin/bash 是 shebang,if-else-fi 是条件执行,echo 打印输出,#/0/$1 是特殊变量,domain 是普通变量。
只有 if 的脚本
#!/bin/bash
value=$1
if [ $value -gt "10" ]
then
echo "Given argument is greater than 10."
fi
执行结果:
- 只传 5 时没有输出;
- 传 12 时输出 Given argument is greater than 10.。
if-elif-else 组合
#!/bin/bash
value=$1
if [ $value -gt "10" ]
then
echo "Given argument is greater than 10."
elif [ $value -lt "10" ]
then
echo "Given argument is less than 10."
else
echo "Given argument is not a number."
fi
执行结果:
- 5 → less than 分支;
- 12 → greater than 分支;
- 传 HTB 这类非数字时,两个整数比较都会报 integer expression expected,最终落到 else,说明把变量当整数比较前应先确认输入确实是数字。
多条件扩展:按参数个数分流
if [ $# -eq 0 ]
then
echo -e "You need to specify the target domain.\n"
echo -e "Usage:\n\t$0 <domain>"
exit 1
elif [ $# -eq 1 ]
then
domain=$1
else
echo -e "Too many arguments given."
exit 1
fi
- $# -eq 0:没有参数 → 打印用法并退出。
- # -eq 1:恰好一个参数 → 接受 domain=1。
- 参数多于一个时由 else 承接 → 提示参数过多并以错误码退出。
3. Arguments, Variables, and Arrays:参数、变量与数组
位置参数 9
- 执行脚本时可以传最多 9 个参数:9,$0 保留给脚本名。
- 使用变量时要加 $;赋值时则不加。
user@htb[/htb]$ ./script.sh ARG1 ARG2 ARG3 ... ARG9
# 对应关系:$0 $1 $2 $3 ... $9
- 这些位置参数由 shell 自动填充,称为特殊变量(special variables)。
特殊变量
课程列出的常用特殊变量:
变量 含义 # 传入脚本的参数个数 @ 命令行参数列表 1 $$ 当前执行进程的 PID $? 上一条命令/脚本的退出状态,0 表示成功,非 0 表示失败
回到 CIDR.sh 的参数检查:
- # -eq 0 判断是否缺参数;只传一个 FQDN 时 # 的值为 1。
- $0 显示在 Usage 中,表示正在执行的脚本名。
- $1 是第一个参数,被赋给 domain 变量。
执行权限与执行方式
- 直接 ./ 执行前先设置执行位:
chmod +x cidr.sh
- 不传参数时,./cidr.sh 和 bash cidr.sh 都会打印用法并退出。
- 课程借此说明:./ 方式依赖执行位;bash cidr.sh 是显式让解释器读取脚本,即使文件没有执行位也可运行(只要可读)。
变量赋值
变量赋值时必须有空格约束:名称、=、值之间不能有空格。
- 错误写法:
variable = "this will result with an error."
结果是把 variable 当成一个命令,报 command not found: variable。
- 正确写法:
variable="Declared without an error."
echo $variable
- Bash 不严格区分字符串、整数、布尔类型,变量内容默认都按字符串处理;只有数值内容参与某些运算时才按数解释。
- 使用变量值用 {variable}。
数组
数组可以把多个值放进一个变量,适合批量扫描域名、IP 等;索引从 0 开始:
#!/bin/bash
domains=(www.inlanefreight.com ftp.inlanefreight.com vpn.inlanefreight.com www2.inlanefreight.com)
echo ${domains[0]}
- echo ${domains[0]} 输出 www.inlanefreight.com。
- 花括号用于变量展开,访问数组元素时不能省略。
- 单引号或双引号会把内部空格合并为一个元素:
domains=("www.inlanefreight.com ftp.inlanefreight.com vpn.inlanefreight.com" www2.inlanefreight.com)
echo ${domains[0]}
此时第一项是完整带空格的字符串。
IFS 提醒
特殊变量依赖 IFS(Internal Field Separator)识别参数/字段边界;课程只提示 IFS 决定参数何时结束、下一个何时开始,没有在本节展开配置细节。
4. Comparison Operators:比较运算符
四类运算符
比较运算符决定两个值如何比较。课程按用途分为字符串运算符、整数运算符、文件运算符、布尔与逻辑运算符四类。
字符串运算符
运算符 含义 == 相等 != 不相等 < ASCII 字母序较小(仅 [[ ]] 内可用) > ASCII 字母序较大(仅 [[ ]] 内可用) -z 字符串为空(null) -n 字符串不为空
关键点:
- 变量最好加双引号,例如 "$1",让 Bash 把内容当字符串处理。
- </> 不能直接在单方括号 [ ] 中使用,需要 [[ ]]。
- 查看 ASCII 表可以执行 man ascii。
示例:要求参数必须等于 HackTheBox,且参数不多于 1 个:
#!/bin/bash
# Check the given argument
if [ "$1" != "HackTheBox" ]
then
echo -e "You need to give 'HackTheBox' as argument."
exit 1
elif [ $# -gt 1 ]
then
echo -e "Too many arguments given."
exit 1
else
domain=$1
echo -e "Success!"
fi
ASCII 概要
- ASCII(American Standard Code for Information Interchange)是 7 位字符编码,共 128 个码点(十进制 0–127、十六进制 00–7F)。
- 前 32 个是控制字符,例如 0(NUL)表示字符串结束;65–68 对应 A–D;127(DEL)是删除。
- 需要完整表时在终端执行 man ascii。
整数运算符
运算符 含义 -eq 等于 -ne 不等于 -lt 小于 -le 小于或等于 -gt 大于 -ge 大于或等于
示例:按参数个数分流:
#!/bin/bash
# Check the given argument
if [ $# -lt 1 ]
then
echo -e "Number of given arguments is less than 1"
exit 1
elif [ $# -gt 1 ]
then
echo -e "Number of given arguments is greater than 1"
exit 1
else
domain=$1
echo -e "Number of given arguments equals 1"
fi
文件运算符
运算符 含义 -e 文件存在 -f 是普通文件 -d 是目录 -L 是符号链接 -N 自上次读取后被修改 -O 当前用户拥有该文件 -G 文件组 ID 与当前用户组匹配 -s 文件大小大于 0 -r / -w / -x 有读 / 写 / 执行权限
示例:先判断指定文件是否存在,存在返回退出码 0,不存在返回 2:
#!/bin/bash
# Check if the specified file exists
if [ -e "$1" ]
then
echo -e "The file exists."
exit 0
else
echo -e "The file does not exist."
exit 2
fi
布尔与逻辑运算符
- 用 [[ 条件 ]] 可以得到 true/false,例如 [[ -z $1 ]] 检查第一个参数是否为空。
- 逻辑运算符:
运算符 含义 ! 逻辑非 NOT && 逻辑与 AND `
示例:文件存在且可读 → 成功;不存在 → 退出码 2;存在但不可读 → 退出码 1:
#!/bin/bash
# Check if the specified file exists and if we have read permissions
if [[ -e "$1" && -r "$1" ]]
then
echo -e "We can read the file that has been specified."
exit 0
elif [[ ! -e "$1" ]]
then
echo -e "The specified file does not exist."
exit 2
elif [[ -e "$1" && ! -r "$1" ]]
then
echo -e "We don't have read permission for this file."
exit 1
else
echo -e "Error occured."
exit 5
fi
5. Arithmetic:算术运算
算术运算符
运算符 含义 + 加 - 减 * 乘 / 除 % 取模 变量++ 变量值加 1 变量-- 变量值减 1
课程用 Arithmetic.sh 汇总演示:
#!/bin/bash
increase=1
decrease=1
echo "Addition: 10 + 10 = $((10 + 10))"
echo "Subtraction: 10 - 10 = $((10 - 10))"
echo "Multiplication: 10 * 10 = $((10 * 10))"
echo "Division: 10 / 10 = $((10 / 10))"
echo "Modulus: 10 % 4 = $((10 % 4))"
((increase++))
echo "Increase Variable: $increase"
((decrease--))
echo "Decrease Variable: $decrease"
输出要点:
Addition: 10 + 10 = 20
Subtraction: 10 - 10 = 0
Multiplication: 10 * 10 = 100
Division: 10 / 10 = 1
Modulus: 10 % 4 = 2
Increase Variable: 2
Decrease Variable: 0
- 算术表达式写在 $(( ... )) 中。
- ((var++)) / ((var--)) 直接修改变量值。
变量长度
${#variable} 返回变量的字符数。例如:
#!/bin/bash
htb="HackTheBox"
echo ${#htb}
输出 10。
回到 CIDR.sh
CIDR.sh 的 ping_host 函数中多次使用 ((stat--))、((hosts_up++))、((hosts_total++)):
- stat 初值为 1,ping 结束后 stat-- 使 while 条件变假,结束当前主机的探测循环。
- 无论主机 up 还是 down,hosts_total++ 都计数;只有 up 时 hosts_up++。
- 最后用两个计数输出 "N out of M hosts are up"。
6. Input and Output Control:输入与输出控制
交互输入:read
有些流程需要人工决定下一步,例如是否继续执行可能受限的扫描活动;脚本需要能停下来等待用户输入。
CIDR.sh 的菜单使用 read -p:
echo -e "Additional options available:"
echo -e "\t1) Identify the corresponding network range of target domain."
echo -e "\t2) Ping discovered hosts."
echo -e "\t3) All checks."
echo -e "\t*) Exit.\n"
read -p "Select your option: " opt
case $opt in
"1") network_range ;;
"2") ping_host ;;
"3") network_range && ping_host ;;
"*") exit 0 ;;
esac
- echo 先打印菜单;read -p "Select your option: " opt 显示提示并把输入存入 opt。
- -p 让提示与输入留在同一行。
- 后续 case 按 opt 的值执行对应函数,具体见第 8 节。
输出控制:tee
- 直接重定向到文件时看不到命令输出;脚本变复杂、执行时间变长后,等待过程会很不方便。
- tee 把结果同时写到终端和文件:命令 | tee 文件。
- -a / --append 表示追加而不是覆盖,适合把多次结果累积到同一文件。
CIDR.sh 中的两处用法:
netrange=$(whois $ip | grep "NetRange\|CIDR" | tee -a CIDR.txt)
网段信息立即显示并追加保存到 CIDR.txt。
hosts=$(host $domain | grep "has address" | cut -d" " -f4 | tee discovered_hosts.txt)
解析出的 IP 同时写入 discovered_hosts.txt。
查看两个文件可以看到累积结果:
cat discovered_hosts.txt CIDR.txt
课程预期的文件内容要点:
165. 22.119.202
NetRange: 165.22.0.0 - 165.22.255.255
CIDR: 165.22.0.0/16
7. Flow Control - Loops:循环
控制结构概览
- 分支:if-else 条件、case 语句。
- 循环:for、while、until。
- 控制结构通常由布尔值/逻辑表达式控制执行。
for 循环
for 每轮从列表、增量或数据源取一个值,列表耗尽即结束;适合遍历数组、主机、端口,也适合对已知服务和端口批量执行命令,加快枚举。
for variable in 1 2 3 4
do
echo $variable
done
for ip in 10.10.10.170 10.10.10.174 10.10.10.175
do
ping -c 1 $ip
done
也可以写成单行:
for ip in 10.10.10.170 10.10.10.174;do ping -c 1 $ip;done
CIDR.sh 的 network_range 用 for ip in $ipaddr 遍历解析出的每个 IP,对每个 IP 执行 whois、过滤 NetRange|CIDR、用 tee 保存;再用 prips 把 CDIR 展开成地址清单,供后续查找更多主机(在客户授权范围内)或继续存活检测。
while 循环
条件为真时反复执行,条件为假时结束;必须靠计数器、布尔变量或 break 退出,否则会死循环。
课程先用 WhileBreaker.sh 演示 break 与 continue:
#!/bin/bash
counter=0
while [ $counter -lt 10 ]
do
# Increase $counter by 1
((counter++))
echo "Counter: $counter"
if [ $counter == 2 ]
then
continue
elif [ $counter == 4 ]
then
break
fi
done
输出到 Counter: 4 为止:到 2 时 continue 跳过本轮剩余,到 4 时 break 终止循环。
CIDR.sh 中的典型用法:
stat=1
while [ $stat -eq 1 ]
do
ping -c 2 $host > /dev/null 2>&1
if [ $? -eq 0 ]
then
echo "$host is up."
((stat--))
((hosts_up++))
((hosts_total++))
else
echo "$host is down."
((stat--))
((hosts_total++))
fi
done
- 条件写法与 if-else 相同,使用 [ 条件 ]。
- stat 每次探测后减 1,循环只跑一次,等价于"等这次 ping 有结果后再继续";课程中这套写法用于统一处理 up/down 计数。
until 循环
与 while 相反:条件为假时反复执行,直到条件变真;适合"跑到目标值"。
课程用 Until.sh 演示从 1 数到 10:
#!/bin/bash
counter=0
until [ $counter -eq 10 ]
do
# Increase $counter by 1
((counter++))
echo "Counter: $counter"
done
输出 Counter: 1 到 Counter: 10 后退出。
嵌套循环注意事项
嵌套循环可以把多个处理步骤合并,但过深嵌套会降低可读性、错误难追踪;课程提醒脚本应保持清晰,必要时把逻辑拆成函数。
8. Flow Control - Branches:分支(case)
case 的作用
case 语句在其他语言中对应 switch-case。它与 if-else 的主要区别:
- if-else 可以判断任意布尔表达式;
- case 只把同一个表达式与精确值/模式比较,不能写 >、< 这类范围条件。
语法
case <expression> in
pattern_1 ) statements ;;
pattern_2 ) statements ;;
pattern_3 ) statements ;;
esac
- 以 case 开始、esac 结束。
- 每个分支是 模式 ) 语句 ;;,双分号 ;; 表示该分支结束。
- 表达式命中哪个模式,就执行对应语句。
CIDR.sh 中的菜单分支
case $opt in
"1") network_range ;;
"2") ping_host ;;
"3") network_range && ping_host ;;
"*") exit 0 ;;
esac
- 1 → 只查询网段;
- 2 → 只 ping 主机;
- 3 → 先查询网段再 ping 主机;
- 其他输入(*)→ 退出脚本。
9. Functions:函数
为什么用函数
脚本越大越容易混乱;同一段逻辑反复出现时,函数可以把多条命令包成一个块,用名字调用,减少重复并提升可读性。函数必须先定义再调用(脚本自上而下执行),所以定义习惯放在脚本开头。
两种定义方式
function name {
<commands>
}
name() {
<commands>
}
两种都可以;CIDR.sh 使用带 function 关键字的第一种,可读性更好。
调用与参数
- 直接写函数名即可调用,例如 network_range、ping_host。
- 函数参数与脚本参数规则相同:9(或 ${n}),也可无参数调用。
- 每个函数拥有独立的参数集,不会与其他函数或脚本参数冲突。
#!/bin/bash
function print_pars {
echo $1 $2 $3
}
one="First parameter"
two="Second parameter"
three="Third parameter"
print_pars "$one" "$two" "$three"
输出:
First parameter Second parameter Third parameter
全局变量与 local
Bash 中默认所有变量都是全局的:函数里第一次定义的变量,脚本外部也能访问;需要隔离时才用 local 显式声明。
返回码
子进程或函数结束时会把返回码交回父进程(shell),用于判断执行是否成功。$? 可读取上一条命令或函数的返回码。
课程列出的常见返回码:
返回码 含义 1 一般错误 2 shell 内建使用不当 126 命令无法执行 127 命令未找到 128 传给 exit 的参数无效 128+n 收到致命信号 n 130 脚本被 Ctrl-C 终止 255* 退出状态超出范围
返回值三种取法
- return:设置函数返回码,配合 $? 读取。
- echo:把结果输出到 stdout。
- 命令替换:用 $(...) 捕获函数输出并存入变量。
示例 Return.sh:
#!/bin/bash
function given_args {
if [ $# -lt 1 ]
then
echo -e "Number of arguments: $#"
return 1
else
echo -e "Number of arguments: $#"
return 0
fi
}
# No arguments given
given_args
echo -e "Function status code: $?\n"
# One argument given
given_args "argument"
echo -e "Function status code: $?\n"
# Pass the results of the funtion into a variable
content=$(given_args "argument")
echo -e "Content of the variable: \n\t$content"
输出要点:
Number of arguments: 0
Function status code: 1
Number of arguments: 1
Function status code: 0
Content of the variable:
Number of arguments: 1
10. Debugging:脚本调试
调试的意义
调试是发现、追踪和修复代码错误(bug)的过程,既可用于检查拼写错误,也可用于分析某类输入为什么导致特定错误。课程同时说明,安全研究中也常通过不同输入触发错误、观察程序内部对错误的处理,进而研究是否能把控执行流;这部分内容由其他模块深入展开。
bash -x(xtrace)
bash -x 会逐条显示实际执行的命令和展开后的值,便于核对条件与变量:
bash -x CIDR.sh
课程输出要点:
+ '[' 0 -eq 0 ']'
+ echo -e 'You need to specify the target domain.\n'
You need to specify the target domain.
+ echo -e Usage:
Usage:
+ echo -e '\tCIDR.sh <domain>'
CIDR.sh <domain>
+ exit 1
- 每行以 + 开头,表示这是一条已展开的实际执行步骤。
- 从中可以直接确认:无参数时 [ 0 -eq 0 ] 成立,脚本打印用法并以状态 1 退出。
bash -v(verbose)
bash -x -v 在 xtrace 基础上同时显示已经处理到的整段源码,便于对照源代码位置和实际执行步骤:
bash -x -v CIDR.sh
输出先显示脚本源码,再附带 + 开头的实际执行步骤;相比只用 -x,能看到"整段代码处理过程 + 每一步命令"。
验证方法
调试后重点核对:条件是否按预期进入分支、变量是否展开成预期值、命令是否真的执行、退出码是否符合设计。
模块小结(归纳)
- 本模块的所有语法点都围绕 CIDR.sh 展开:参数检查 → 域名解析 → whois 网段查询 → ping 存活 → 菜单分支。
- 一句话主线:看懂脚本 = 看懂参数/变量 → 条件与比较 → 循环与分支 → 函数 → 用调试确认每一步。
- 课程提供的命令组合(管道、tee、$(...)、case)可以在授权范围内直接复用于目标枚举、批量检测和结果归档。
- 本模块正文全部为文字、命令与表格,没有需要下载保留的教学图片;各节的图片盘点记录见同目录 .audit.json。
