【问题标题】:POSIX regex: match only within comma-delimited itemsPOSIX 正则表达式:仅在逗号分隔的项目内匹配
【发布时间】:2016-04-27 20:41:30
【问题描述】:

上下文:我正在编写一个 shell 脚本来帮助管理一个简单的数据库,该数据库以人类可读的方式存储在文本文件中,并使用普通文本编辑器进行编辑。 (每个条目都是一个文本文件,其名称是一个 ID 号,所有文件都存储在一个目录中。)

我目前的问题是搜索。有一些 headers 基本上是文件顶部的数据字段。例如,让我们以标签字段为例,它以 Tags:\t (其中\t 是文字制表符)开始新行,然后有一个逗号分隔的标签列表。我希望能够将用户提供的正则表达式插入到对grep 的更大调用中,并使用户的正则表达式匹配仅在每个逗号分隔的项目中

以下是我的文档中的一些描述我希望发生的事情:


hregexes 是仅在逗号分隔的项目中匹配的 ERE。例如,带有标题Tags: foo, bar baz:

REGEX     :: MATCHES?
foo       :: yes
bar       :: yes
baz       :: yes
az        :: yes
.*baz     :: yes
ba.*az    :: yes
o, ba     :: no
foo.*baz  :: no

理想情况下,这将完全与 POSIX 扩展正则表达式一起工作,以与系统的其余部分保持一致;我有一个在 Python 中工作的简化版搜索,但我决定重写该部分,这样系统就不会有一些搜索采用 POSIX 正则表达式和一些 Python 正则表达式。

我确实尝试过想出一个模式,但我对正则表达式还不够好,无法完成如此复杂的事情。在接下来的尝试中,$2 是我们要查找的标头,$3 是要在该标头中匹配的模式。

grep -El "$2:   (|.*,|.*, )[^,]*$3[^,]*(,|\b)" *.dre

这不会错过它应该捕获的任何结果,但它存在o, bafoo.*baz 在不应该匹配时都匹配的问题;在这一点上我还不如搜索$2: .*$3

如果单个 ERE 无法做到这一点,那么在 Bash 中是否有另一种好方法可以做到这一点?我的数据库已经有超过一千个文件,并且很容易增长到很多倍,所以我不希望循环遍历每个文件,然后遍历逗号分隔列表中的每个项目,并且每次都会产生 shell 开销。

【问题讨论】:

    标签: regex bash grep


    【解决方案1】:

    诀窍是将逗号更改为在 grep 中作为分隔符效果更好的东西,即换行符。

    head -1 $DATA_FILE | sed -E 's/,/\'$'\n/g' | grep -qE "$SEARCH"
    
    if [ $? == 0 ]
    then
        echo "Pattern found: $DATA_FILE"
    else
        echo "Pattern not found: $DATA_FILE"
    fi
    

    $DATA_FILE 是包含标签的文件。 $SEARCH 是正在寻找的正则表达式。

    if 语句显然会替换为适合您的应用程序的逻辑。

    head 命令从文件中提取第一行(“Tag:”行)。 sed 命令将该行上的任何逗号替换为换行符(此时删除“标签:”也是明智的做法,以避免误报)。 然后grep 只需在每组结果行中搜索输入正则表达式并返回一个状态,指示是否找到它。

    搜索每个数据文件的最小数量。

    【讨论】:

    • 这行得通(除了Tags 不一定是第一行,所以我改为使用grep -hm 1 'Tags:'),但它需要遍历 Bash 目录中的每个文件,这减慢了搜索 60 倍,从用户的角度来看几乎是瞬间到几秒钟。
    • 我想我找到了一个可行的解决方案,我已将其发布为答案;我很想听听您对此的看法。
    【解决方案2】:

    以下解决方案,基于 Perry 更换分离器的想法,并非万无一失,但保留了理想的运行时间,同时很难搞砸。

    首先,我们选择一个分隔符来替换逗号;我选择了@@@@@,理由是这不会出现在任何格式正确的标签中。 (标签通常是纯字母数字。)

    然后我们修改用户的正则表达式以将. 替换为[^@],这样除非明确组合成,否则任何表达式都不会跨越@@@@@ 边界。我可能会错过一些其他比赛,比如[[:punct:]];我不太担心这些,但如果有人对其他可能有问题的特殊字符有想法,我想听听他们的意见。

    最后,我们创建一个包含所有Tags 行的流,编辑它以仅包含文件名和新的@-delimited 标签,将用户的模式匹配应用于此流,然后删除除匹配流中的文件名。

    最终代码:

    header="$2"
    pattern=$(echo "$3" | sed -e 's/\./[^@]/')
    grep -m 1 "$header: " *.dre | sed -e "s/$header:        //" | \
        sed -e 's/, /@@@@@/g' | grep -E "$pattern" | \
        sed -e 's/\([0-9]\{5\}\.dre\):.*/\1/'
    

    [0-9]\{5\}\.dre 是一个匹配所有合法文件名的表达式。)

    示例输出:

    00775.dre
    00787.dre
    00788.dre
    00883.dre
    00889.dre
    

    (显然,可以将匹配项保存到变量中以供进一步处理;这就是我在这里所做的。)

    【讨论】:

    • 我没有尝试过,但看起来你走在正确的轨道上。您还遇到了在纯 shell 中能够实现的限制,因此请记住,用户的下一个功能请求可能会将您推到使用较低级别语言编写一些帮助程序的边缘:)
    猜你喜欢
    • 2021-05-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-31
    • 1970-01-01
    • 2021-07-01
    相关资源
    最近更新 更多