我需要解析一个包含以关键字开头的记录的文件,并有选择地将这些记录写入单独的类别文件。完成此操作后,需要从主文件中删除提取的记录。使用 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
如果正确,这应该只产生omicron
和sigma
记录。
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
。