匹配文件中行尾的字符串

Match string at end of line in file

我有两个文件:

  1. $hashfile:哈希和 ./relative/path/to/file/names,都在一行上,由 2 spaces

    分隔
  2. $badfiles: ./relative/path/to/file/names 我需要在 $hashfile 中找到它来得到相应的 hash

这是 $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 and/or Windows NTFS 文件系统将接受。

有什么想法吗?

编辑:解决方案

显然 grep 没有提供将换行符包含到固定字符串搜索中的简单解决方案。 @anubhava 提供了使用 awk 的最佳解决方案:

awk 'NR == FNR {a[[=15=]]; next}
{b=[=15=]; 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[[=22=]]; next}将$badfiles文件读入数组,; next阻止程序执行后续条件和动作,直到整个$badfiles被读取,即直到NR == FNR为false。

读取$hashfile时,[=25=](已读取的行)被分配给bb=[=27=])。 sub(/^\S+\s+/, "", b) 在行 (^) 的开头替换一个或多个非 space 字符 (\S+),后跟一个或多个 space 字符 ( \s+) 通过变量 b 中的 ""(空字符串)。然后只留下 ./path/to/file 内部变量 b.

最后一行b in a' "$badfiles" "$hashfile" > "$new" 查找是否在a 中找到变量b,如果是,则将$hashfile 中的行复制到文件$new。如果 $badfiles 中的所有行在 $hashfile 中都有匹配的条目,则将带有哈希值的相应 $hashfile 行复制到 $new.

由于文件名前的hash值是固定长度的,awk语句可以简化为:

awk 'NR == FNR {a[[=16=]]; next}
{b=substr([=16=],35)}
b in a' "$badfiles" "$hashfile" > "$new"

上面的substr()语句获取输入行[=25=]并去掉前34个字符,从1开始计算。子字符串b然后从位置35开始。这很多例如 bash 中的子字符串提取,例如 ${mystring:34}。请注意,bash 子字符串提取从 0 开始计数。

我现在使用该 awk 命令的变体来创建一个新的哈希文件,其中包含除 $deletedfiles:

中列出的所有文件哈希之外的所有文件哈希
awk 'NR == FNR {a[[=17=]]; next}
{b=substr([=17=],35)}
!(b in a)' "$deletedfiles" "$hashfile" > "$new"

使用上述命令,在 $deletedfiles 中未找到的每个字符串 b(来自 $hashfile)都将相应的行从 $hashfile 复制到 $new。必须特别注意一个空的 $deletedfiles 文件:如果 $deletedfiles 是一个空文件,那么 $new 文件也将是空的!预期结果是 $new file 与 $hashfile 相同。

即使在一个哈希文件中包含 200,000-300,000 个文件名,此解决方案也非常有效(而且速度很快)。

这个 awk 解决方案应该适合您:

awk 'FNR == NR {srch[[=10=]]; next} 
{s = [=10=]; sub(/^[^[:blank:]]+[[:blank:]]+/, "", s)}
s in srch' badfiles hashfile

11e0d980f3b2219f65da97a0318e7dce  ./Jerusalem_Canon EOS R5_20210601_031.jpg

此解决方案首先将 badfiles 中的所有行存储在数组 srch 中。然后从 hashfile 开始,它删除文本直到第一个空格,然后如果在 srch 数组中找到剩余部分,则打印同一文件中的每一行。