如何在哈希文件中精准匹配行尾指定文件名并提取对应哈希值
我有两个文件:
$hashfile:每行存储哈希值和./相对路径文件名,二者以两个空格分隔$badfiles:存储待在$hashfile中查询对应哈希值的./相对路径文件名
$hashfile示例片段如下:
c2c99b59f3303cafac85c2c6df6653cc ./vm-mount.sh 058a8fb0b9366f248be32b7390e94595 ./Jerusalem_Canon EOS R5_20210601_031.jpg~ 23eba1c54846de5244312047e2709f9a ./rsync-back.sh ff3f08f7bf45f8e9ef8b33192db3ce9a ./vm-backup.sh 11e0d980f3b2219f65da97a0318e7dce ./Jerusalem_Canon EOS R5_20210601_031.jpg 49fb1fb660dce09acd87861a228c899d ./vm-test.sh
$badfiles存储的搜索模式示例如下:
./Jerusalem_Canon EOS R5_20210601_031.jpg ./file.txt
我的需求是在$hashfile中搜索$badfiles内的模式,将包含对应哈希值的匹配行写入第三个文件$new。
遇到的问题
我最初使用的命令如下:
grep -Ff "$badfiles" "$hashfile" > "$new"
但该命令会同时匹配到后缀带~的多余行,不符合要求:
058a8fb0b9366f248be32b7390e94595 ./Jerusalem_Canon EOS R5_20210601_031.jpg~ 11e0d980f3b2219f65da97a0318e7dce ./Jerusalem_Canon EOS R5_20210601_031.jpg
之后我给$badfiles的每行末尾添加了$,并将命令修改为:
grep -f "$badfiles" "$hashfile" > "$new"
该方案在小型测试场景下可用,但我有30万以上的文件名和哈希记录,且部分文件名包含"':,;<>()[]?等ext4、NTFS文件系统支持的特殊字符,正则匹配存在性能下降和匹配错误的风险,需要更稳妥的解决方案。
grep没有提供在固定字符串搜索中加入行尾匹配的简单方案,最优解是使用awk实现:
awk 'NR == FNR {a[$0]; next} {b=$0; sub(/^\S+\s+/, "", b)} b in a' "$badfiles" "$hashfile" > "$new"
注意:$badfiles、$hashfile和$new均为存储文件名的变量。
命令说明
NR存储所有文件已读取的总行数,FNR存储当前文件已读取的行数。当awk读取$badfiles时,NR == FNR成立,将$badfiles的每一行内容存入数组a,next会阻止后续逻辑执行,直到$badfiles全部读取完成。- 读取
$hashfile时,将当前行$0赋值给变量b,sub(/^\S+\s+/, "", b)会把变量b中开头的一个或多个非空白字符(哈希值)加一个或多个空白字符(分隔符)替换为空,此时变量b仅保留./路径/文件名。 - 最后判断变量b是否存在于数组a中,若存在则将
$hashfile的当前行写入$new文件。如果$badfiles的所有行都能在$hashfile中找到匹配项,$new将存储所有对应带哈希值的行。
简化版本
由于MD5哈希值长度固定为32位,加之后面的两个分隔空格共34个字符,因此awk语句可简化为:
awk 'NR == FNR {a[$0]; next} {b=substr($0,35)} b in a' "$badfiles" "$hashfile" > "$new"
substr()语句会从输入行$0的第35位(awk计数从1开始)开始截取子串赋值给b,相当于移除前34位的哈希值和分隔空格,类似bash中的子串提取${mystring:34}(bash子串计数从0开始)。
变体用法:排除指定文件
如果需要生成新哈希文件,排除$deletedfiles中列出的文件,可以使用以下命令:
awk 'NR == FNR {a[$0]; next} {b=substr($0,35)} !(b in a)' "$deletedfiles" "$hashfile" > "$new"
该命令会将$hashfile中路径不在$deletedfiles内的行写入$new。需要特别注意:如果$deletedfiles为空文件,$new也会为空,不符合预期的和$hashfile一致的结果。
该方案性能优异速度快,即使单哈希文件内有20-30万条文件名记录也可正常运行。
内容的提问来源于stack exchange,提问作者powerhouse

