【发布时间】:2021-09-29 20:16:39
【问题描述】:
我有两个文件:
-
$hashfile:哈希和 ./relative/path/to/file/names,都在一行,用 2 个空格隔开
-
$badfiles: ./relative/path/to/file/names 我需要在 $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 命令更改为:
grep -f "$badfiles" "$hashfile" > "$new"
这适用于一个小型测试文件夹,但我担心不会被解释为固定字符串的模式搜索可能会对大型文件系统造成严重破坏。我有大约 300,000 多个文件名和哈希,其中一些使用特殊字符,例如 "':,;()[]? - 简而言之,Linux ext4 和/或 Windows NTFS 文件系统可以接受的任何字符。
有什么想法吗?
编辑:解决方案
显然 grep 没有提供将换行符包含到固定字符串搜索中的简单解决方案。 @anubhava 提供了使用 awk 的最佳解决方案:
awk 'NR == FNR {a[$0]; next}
{b=$0; sub(/^\S+\s+/, "", b)}
b in a' "$badfiles" "$hashfile" > "$new"
注意:$badfiles、$hashfiles 和 $new 是保存文件名的变量。
上面的语法最好描述here under "Two-file processing"。 NR 保存到目前为止从所有文件读取的行号,而FNR 保存从当前文件读取的行号。因此,当 awk 完成读取 $badfiles 并读取 $hashfile 的第一行时,NR 保存到目前为止读取的所有行的总和,FNR 等于 1,因为这是新文件的第一行。 {a[$0]; next} 将 $badfiles 文件读入一个数组,; next 阻止程序执行后续的条件和动作,直到整个 $badfiles 被读取,即直到 NR == FNR 为 false。
读取 $hashfile 时,$0(已读取的行)被分配给 b(b=$0)。 sub(/^\S+\s+/, "", b) 在行首 (^) 替换一个或多个非空格字符 (\S+),然后在变量中由 "" (空字符串) 替换一个或多个空格字符 (\s+) b。然后只留下变量b中的./path/to/file。
最后一行 b in a' "$badfiles" "$hashfile" > "$new" 查看变量 b 是否在 a 中找到,如果是,则将 $hashfile 中的行复制到文件 $new。如果 $badfiles 中的所有行在 $hashfile 中都有匹配的条目,则将带有哈希值的相应 $hashfile 行复制到 $new。
由于文件名前的hash值是固定长度的,awk语句可以简化为:
awk 'NR == FNR {a[$0]; next}
{b=substr($0,35)}
b in a' "$badfiles" "$hashfile" > "$new"
上面的substr() 语句采用输入行$0 并去掉前34 个字符,从1 开始计数。子字符串b 然后从位置35 开始。这很像bash 中的子字符串提取,例如${mystring:34}。请注意,bash 子字符串提取从 0 开始计数。
我现在使用该 awk 命令的变体来创建一个新的哈希文件,其中包含除$deletedfiles 中列出的文件之外的所有文件哈希:
awk 'NR == FNR {a[$0]; next}
{b=substr($0,35)}
!(b in a)' "$deletedfiles" "$hashfile" > "$new"
使用上述命令,$deletedfiles 中未找到的每个字符串 b(来自 $hashfile)将相应的行从 $hashfile 复制到 $new。必须特别注意一个空的 $deletedfiles 文件:如果 $deletedfiles 是一个空文件,那么 $new 文件也将是空的!预期结果是 $new 文件与 $hashfile 相同。
即使在一个哈希文件中包含 200,000-300,000 个文件名,此解决方案也非常有效(而且速度很快)。
【问题讨论】:
-
也许
sed你的模式文件将所有奇怪的字符转换为点?不是一个很好的解决方案,但可能会使问题更简单。
标签: bash awk grep anchor newline