如何使用 Grep 分类重新定位记录

如何使用 Grep 分类重新定位记录

我需要解析一个包含以关键字开头的记录的文件,并有选择地将这些记录写入单独的类别文件。完成此操作后,需要从主文件中删除提取的记录。使用 bash,grepping 是最好的方法吗?

下面的函数和循环执行提取部分:

declare -a keywords
declare -f extract_records

keywords=(alpha bravo gamma delta)

extract_records() {
    grep -E "^($1 )" main_file >> category_file.$1
}

for i in "${keywords[@]}"; do
    extract_records "$i"
done

然后重新压缩主文件:

grep -E -v \
  -e "^alpha " \
  -e "^bravo " \
  -e "^gamma " \
  -e "^delta " \
main_file >> main_file.$$

main_file.$$可选排序的,替换原始的。这里,关键字列表指定了两次。最好再次使用数组进行重新压缩部分,这样只需要一个关键字列表,例如:

grep -E -v "^(${keywords[@]})" main_file >> main_file.$$

但这不起作用,因为每个关键字都需要一个模式说明符。有没有更简单的方法?是否可以在提取记录时将其删除,而不是采用这种两部分方法?关键字列表可能有数百个,可以从文件加载并读入数组(此处未显示)。管理两组关键字容易导致不匹配和数据丢失。除了 bash 之外,还有 Python 或其他解决方案吗?

编辑:2023 年 2 月 20 日星期一 22:52:53 EST

以下是一个最小代表性示例...

原文main_file包含10条数据记录:

alpha 1
bravo 1
gamma 1
delta 1
omicron 1
sigma 1
alpha 2
bravo 2
gamma 2
delta 2

提取创建的类别文件... main_file.alpha

alpha 1
alpha 2

main_file.bravo

bravo 1
bravo 2

main_file.gamma

gamma 1
gamma 2

main_file.delta

delta 1
delta 2

压缩后的结果main_file将保留未提取的内容:

omicron 1
sigma 1

根据 steeldriver 的建议,从数组创建一个exclusion_args数组确实keywords可以为反转 grep 编译正确的模式,并且确实解决了我的“两个独立列表”问题:

declare -a exclusion_args
for k in "${keywords[@]}"; do exclusion_args+=( -e "\"^$k \"" ); done
printf "%s " "${exclusion_args[@]}"

-e "^alpha " -e "^bravo " -e "^gamma " -e "^delta "

就反转模式而言,上述字符串重现了使用“grep -E -v”首次发布的内容。正确,不需要分组括号。现在,上述字符串可以以何种方式用作以下 grep 模型的附加参数:

grep -E -v $(printf " %s " "${exclusion_args[@]}") main_file

如果正确,这应该只产生omicronsigma记录。

echo上面的内容意外地删除了此处看到的printf前导:-e

"^alpha " -e "^bravo " -e "^gamma " -e "^delta "

当然,这会破坏 grep 的反转模式。也许这就是为什么反转 grep 返回所有 10 条记录而不排除任何记录的原因。

并且可能有比这种设计更好的方法将输入转化为输出。

答案1

虽然你可以从关键字数组中构造一个 grep 参数数组,例如

args=( -v -w )
for k in "${keywords[@]}"; do 
  args+=( -e "^$k" )
done 

例如(我认为不需要 -E 或分组括号,并且 -w 可能比添加显式尾随空格字符更强大),在我看来,使用功能更齐全的文本处理语言会更自然。例如在 GNU awk 中,您可以将关键字读入内部数组,然后main_file一次性处理整个数组:

keywords=(alpha bravo gamma delta)

printf '%s\n' "${keywords[@]}" | gawk -i inplace '
  BEGIN{inplace::enable=0} 
  NR==FNR {keywords[$0]; next} 
  ($1 in keywords) {print > (FILENAME "." $1); next} 
  {print}
' - inplace::enable=1 main_file

导致

$ head main_file*
==> main_file <==
omicron 1
sigma 1

==> main_file.alpha <==
alpha 1
alpha 2

==> main_file.bravo <==
bravo 1
bravo 2

==> main_file.delta <==
delta 1
delta 2

==> main_file.gamma <==
gamma 1
gamma 2

(打开inplace::enable和关闭并不是绝对必要的 - 它只是抑制有关 的警告in-place editing for invalid FILENAME '-')。


如果你决定使用 shell 循环,那么sed有一个比 - 更合适的正则表达式工具,grep它仅用于搜索,而不是搜索和替换。例如

for k in "${keywords[@]}"; do 
  sed -i -e "/^$k/{w main_file.$k" -e "d}" main_file
done

然而我建议,即使有sed,更好的方法是将关键字数组转换为 sed 脚本,然后将其作为单个指令执行:

printf '%s\n' "${keywords[@]}" | sed 's:.*:/&/{w main_file.&\nd}:' | 
  sed -i.bak -f - main_file

事实上,如果您不介意对 basename 进行硬编码,我会选择后者而不是 gawk 解决方案main_file

相关内容