在遍历文件时优化行的前缀检查

Optimize prefix checks of lines while iterating through file

我正在编写表单中的脚本

while read LINE 
do
    [[ $LINE =~ ^headertag1 ]] && function1 && continue
    [[ $LINE =~ ^headertag2 ]] && function2 && continue
    ...
done < filename

随着标签数量的增加,我将在每行中进行过多的检查。我可以尝试将常见标签排序得更高,但我认为它不能解决根本问题。我不是软件工程师。有编程concepts/methods可以改善这种情况吗?

是的,对于两个你可以先找到两者的最长公共前缀(这里人们想知道如何在 Bash Longest common prefix of two strings in bash 中做到这一点),然后首先检查行是否以它开头然后在从标签和行中剥离它之后检查行是否以它的其余部分开头。

如果超过两个,则需要创建一个 trie — 也称为前缀树 https://en.wikipedia.org/wiki/Trie .

那篇维基百科文章说

For the space-optimized presentation of prefix tree, see compact prefix tree.

并且拥有 最长 的通用前缀,这就是您要拥有的。

Bash doesn't have multidimensional associative arrays, you will have to either consider https://en.wikipedia.org/wiki/Trie#Implementation_strategies or embed some other scripting language, like Perl or Python — or GNU Awk (gawk), which, unlike to standard Awk, introduces multidimensional associative arrays.

使用 Bash 的关联数组实现的优化

正如 中所建议的那样,我们可以考虑只使用带有更简单正则表达式的标签,并将其用作关联数组的键,该关联数组在 Bash 中进行了一些优化(我们可以调查效果如何- 适合我们在来源中的需求:

如果我们知道它是由分隔的——比如,如果我们知道它后面总是紧跟:或其他东西 而不是包含它,并使用更简单的正则表达式,如:

[[ $LINE =~ ^(.*): ]] && "${DICTIONARY_OF_FUNCTIONS["${BASH_REMATCH[1]}"]}"

或者使用优化Bash的函数store

如果你所有的标签都像 /[a-z][a-z0-9]+/ 或者被 Bash 接受为函数名,并且像方法中那样用 Bash 的关联数组分隔,那么你可以使用上述方法插入函数名称,例如,

function the_function_for_tag_headertag1() {
    echo "hey it's the first one"
}
[[ $LINE =! ^(.*): ]] && {
    func_name="the_function_for_tag_${BASH_REMATCH[1]}"
    type "${func_name}" && "${func_name}"
}

您对每个标签执行的测试

    [[ $LINE =~ ^headertag1 ]] && function1 && continue

非常便宜(在内存正则表达式中。很可能,它只需要一小部分与读取 LINE(从文件或其他进程)相关的 IO 时间。除非您多次执行测试,这个实现是合理的。

样式注意事项: 如果所有模式都是前缀匹配(或其他简单构造),请考虑使用 bash case 语句

case "$LINE" in
   header1*) function1 ;;
   header2*) function2 ;;
   ...
esac

这将使代码更优雅,但不会改变性能——RE 和通配符都很简单。

这里不确定,但是如果你想整理你的代码并且对重复添加这些 if 守卫感到厌烦,那么这个想法也许会有所帮助:

#!/bin/bash

tags[tag1]="some regex1"
tags[tag2]="some regex2"
tags[tag3]="some regex3"

function action() {
  echo "perl -pe '${tags[$tag]} other-file.txt'"
}

while read LINE; do
  for tag in "${!tags[@]}"; do
    [[ $LINE =~ ^$tag ]] && action "${tags[$tag]}"
  done
done < filename

不确定 OP 是否在问这样的问题。