【问题标题】:Regex for strings nearby other strings?其他字符串附近的字符串的正则表达式?
【发布时间】:2018-09-10 18:11:02
【问题描述】:

我想为grep 编写一个灵活的正则表达式,它将返回在一定距离内找到的搜索词。

理想的行为类似于研究数据库;例如,您可以在其中搜索 capitalGDP 彼此相距在 15 个字以内的文章,其中包括字符串 capitalGDP 可能被五、六、七等分隔的文章.,未指定长度的字母数字字符串。正则表达式语句将包括标点符号(例如,逗号、句号、连字符),但也包括重音符号和变音符号。因此,chechèlavi 的结果相隔不超过五个字符串。

我想该声明将涉及前瞻,以及像{1,15} 这样的短语,或者可能将一个grep 传递到另一个grep,但这失去了GREP_OPTIONS='--color=auto' 的好处。构建它真的超出了我的技能范围。我有一组 .txt 文档,我想对其进行搜索,但是让正则表达式灵活地更改字符串之间的距离或截断术语对于在标准中有 fieldnotes 或阅读笔记等内容的其他人也很有用格式。

编辑

以下是从圣经中摘录的一段示例。

Ye shall buy meat of them for money, that ye may eat; and ye shall also buy water of them for money, that ye may drink. For the Lord thy God hath blessed thee in all the works of thy hand: he knoweth thy walking through this great wilderness: these forty years the Lord thy God hath been with thee; thou hast lacked nothing... Thou shalt sell me meat for money, that I may eat; and give me water for money, that I may drink: only I will pass through on my feet: (as the children of Esau which dwell in Seir, and the Moabites which dwell in Ar, did unto me:) until I shall pass over Jordan into the land which the Lord our God giveth us. But Sihon king of Heshbon would not let us pass by him: for the Lord thy God hardened his spirit, and made his heart obstinate, that he might deliver him into thy hand, as appeareth this day. And the Lord said unto me, Behold, I have begun to give Sihon and his land before thee: begin to possess, that thou mayest inherit his land. Then Sihon came out against us, he and all his people, to fight at Jahaz. And the Lord our God delivered him before us; and we smote him, and his sons, and all his people. And if the way be too long for thee, so that thou art not able to carry it; or if the place be too far from thee, which the Lord thy God shall choose to set his name there, when the Lord thy God hath blessed thee: then shalt thou turn it into money, and bind up the money in thine hand, and shalt go unto the place which the Lord thy God shall choose: and thou shalt bestow that money for whatsoever thy soul lusteth after, for oxen, or for sheep, or for wine, or for strong drink, or for whatsoever thy soul desireth: and thou shalt eat there before the Lord thy God, and thou shalt rejoice, thou, and thine household, and the Levite that is within thy gates; thou shalt not forsake him: for he hath no part nor inheritance with thee... Now it came to pass, that at what time the chest was brought unto the king’s office by the hand of the Levites, and when they saw that there was much money, the king’s scribe and the high priest’s officer came and emptied the chest, and took it, and carried it to his place again. Thus they did day by day, and gathered money in abundance. And when they had finished it, they brought the rest of the money before the king and Jehoiada, whereof were made vessels for the house of the Lord , even vessels to minister, and to offer withal, and spoons, and vessels of gold and silver. And they offered burnt offerings in the house of the Lord continually all the days of Jehoiada. Thou hast bought me no sweet cane with money, neither hast thou filled me with the fat of thy sacrifices; but thou hast made me to serve with thy sins, thou hast wearied me with thine iniquities... Howbeit there were not made for the house of the Lord bowls of silver, snuffers, basins, trumpets, any vessels of gold, or vessels of silver, of the money that was brought into the house of the Lord: but they gave that to the workmen, and repaired therewith the house of the Lord. Moreover they reckoned not with the men, into whose hand they delivered the money to be bestowed on workmen: for they dealt faithfully. The trespass money and sin money was not brought into the house of the Lord: it was the priests’.

如果我想查找 shaltmoney 在五个单词(包括标点符号)内同时出现的实例,我将如何编写该正则表达式?

我不确定如何给出预期的结果,因为grep --context=1 不仅仅包含中间有 0-5 个字符串的字符串,但我想结果会识别:

shalt sell me meat for money
shalt thou turn it into money
money in thine hand, and shalt
shalt bestow that money

但不会返回 shall buy meat of them for money,,因为 'money' 出现在第六个字符串中。

【问题讨论】:

  • 在预期输出中包含您的示例输入
  • @anubhava 添加了示例文本
  • 给出了预期的输出@EdMorton
  • wrt I want to write a flexible regex for grep - 这真的是您想要的,还是您只是想要一个明智的解决方案来解决您的问题?
  • @EdMorton 我确实想要一个明智的解决方案,但它应该足够灵活,可以轻松更改单词接近度或截断所需的更改,所以我觉得 grepping 是那个选项。我当然愿意接受更明智的选择。

标签: regex shell awk grep pcre


【解决方案1】:

好吧,这不是 grep,但这似乎可以满足您对使用 GNU awk 进行多字符 RS 和单词边界的要求:

$ cat tst.awk
BEGIN {
    RS="^$"
    split(words,word)
}
{
    gsub(/@/,"@A"); gsub(/{/,"@B"); gsub(/}/,"@C")
    gsub("\\<"word[1]"\\>","{")
    gsub("\\<"word[2]"\\>","}")
    while ( match($0,/{[^{}]+}|}[^{}]+{/) ) {
        tgt =  substr($0,RSTART,RLENGTH)
        gsub(/}/,word[2],tgt)
        gsub(/{/,word[1],tgt)
        gsub(/@C/,"}",tgt); gsub(/@B/,"{",tgt); gsub(/@A/,"@",tgt)
        if ( gsub(/[[:space:]]+/,"&",tgt) <= range ) {
            print tgt
        }
        $0 = substr($0,RSTART+length(word[1]))
    }
}

$ awk -v words='money shalt' -v range=5 -f tst.awk file
shalt sell me meat for money
shalt thou turn it into money
money in thine hand, and shalt
shalt bestow that money

$ awk -v words='and him' -v range=10 -f tst.awk file
him: for the Lord thy God hardened his spirit, and
and made his heart obstinate, that he might deliver him
him before us; and
and we smote him
him, and

请注意,即使输入像 shalt sell me meat for money in thine hand, and shalt 这样的输入,其中一个词 (money) 出现在另一个词 (shalt) 的第一次出现之后的 5 个词和第二次出现之前的 5 个词第一个词(再次,shalt):

$  echo 'shalt sell me meat for money in thine hand, and shalt' |
    awk -v words='shalt money' -v range=5 -f tst.awk
shalt sell me meat for money
money in thine hand, and shalt

对于颜色、文件名和行号:

执行此操作以查看终端中可用的颜色(每行将以不同的颜色输出):

$ for ((c=0; c<$(tput colors); c++)); do tput setaf "$c"; tput setaf "$c" | cat -v; echo "=$c"; done; tput setaf 0
^[[30m=0
^[[31m=1
^[[32m=2
^[[33m=3
^[[34m=4
^[[35m=5
^[[36m=6
^[[37m=7

现在您可以看到这些转义序列和数字的含义,请将 awk 脚本更新为 (\033 = ^[ = Esc):

$ cat tst.awk
BEGIN {
    RS="^$"
    split(words,word)
    c["black"]  = "\033[30m"
    c["red"]    = "\033[31m"
    c["green"]  = "\033[32m"
    c["yellow"] = "\033[33m"
    c["blue"]   = "\033[34m"
    c["pink"]   = "\033[35m"
    c["teal"]   = "\033[36m"
    c["grey"]   = "\033[37m"
    for (color in c) {
        print c[color] color c["black"]
    }
}
{
    gsub(/@/,"@A"); gsub(/{/,"@B"); gsub(/}/,"@C")
    gsub("\\<"word[1]"\\>","{")
    gsub("\\<"word[2]"\\>","}")
    while ( match($0,/{[^{}]+}|}[^{}]+{/) ) {
        tgt =  substr($0,RSTART,RLENGTH)
        gsub(/}/,word[2],tgt)
        gsub(/{/,word[1],tgt)
        gsub(/@C/,"}",tgt); gsub(/@B/,"{",tgt); gsub(/@A/,"@",tgt)
        if ( gsub(/[[:space:]]+/,"&",tgt) <= range ) {
            print FILENAME, FNR, c["red"] tgt c["black"]
        }
        $0 = substr($0,RSTART+length(word[1]))
    }
}

当您运行它时,您会看到所有可用颜色的转储,并且对于您的每个目标文本,它的前面都将是该文件中的文件名和行号,并且文本将被着色为红色:

【讨论】:

  • 您的解决方案适用于我的简单示例!但我根本不知道awk - 是否可以在一组文件上进行迭代?
  • 是的。但我会尝试阅读更多手册以查找颜色选项并让 awk 打印搜索命中的文件名(和行号?)
  • 在 .rtf 文件夹上运行脚本效果很好,但它不能很好地打印变音符号和口音(也许这是我已经搞定的终端设置?)而且它似乎没有正确报告行号。
  • this sample 和正则表达式“砖块废墟”上使用您的脚本时,您能帮我修改一下我的期望吗?当我运行它时,结果显示sample.txt 1 bricks. Scattered about the vicinity are fragments of wòch ravèt and brick. Mozà says there are more ruins,我认为这意味着第 1 行,但在第 7 行可以找到这些术语。
  • WRT 变音符号和重音符号,仅在对 .rtf 文件而非 .txt 运行脚本时出现
【解决方案2】:

简短回答: grep 'shalt\W\+\(\w\+\W\+\)\{0,5\}money'

也许是双向的: grep 'shalt\W\+\(\w\+\W\+\)\{0,5\}money\|money\W\+\(\w\+\W\+\)\{0,5\}shalt'

https://www.gnu.org/software/grep/manual/grep.html:

‘\w’

匹配词成分,是'[_[:alnum:]]'的同义词。

‘\W’

匹配非单词成分,是‘[^_[:alnum:]]’的同义词。

动态构造 grep 的通用答案,在本例中使用 shell 函数:

find_adjacent() {
    dist="$1"; shift
    grep1="$1"; shift
    grep2="$1"; shift
    
    between='\W\+\(\w\+\W\+\)\{0,'"$dist"'\}'
    regex="$grep1$between$grep2\|$grep2$between$grep1"
    
    printf 'Using the regex: %s\n' "$regex" 1>&2
    grep "$regex" "$@"
}

示例用法:

echo 'shalt sell me meat for money
shalt thou turn it into money
money in thine hand, and shalt
shalt bestow that money
capital and GDP' | find_adjacent 3 shalt money -i --color=auto

或跨行匹配:

find_adjacent 5 shalt money -z file_with_the_bible_passages.txt

编辑

作为pointed out by EdMorton,这只会找到继续匹配的第一部分。它仍然会匹配正确的行,但颜色突出显示会有点。

要解决这个问题,正则表达式会变得更加复杂,因为它必须在 4 种情况下匹配任何继续的“salt ... money ... shalt”:

  • “应该……钱……应该”
  • “应该...钱...应该...钱”
  • “钱……应该……钱”
  • “钱...应该...钱...应该”

这可以通过将regex=... 行替换为:

regex1="$grep1\($between$grep2$between$grep1\)\+"
regex2="$grep1$between$grep2\($between$grep1$between$grep2\)*"
regex3="$grep2\($between$grep1$between$grep2\)\+"
regex4="$grep2$between$grep1\($between$grep2$between$grep1\)*"
regex="$regex1\|$regex2\|$regex3\|$regex4"

此外,它可能会像这样混淆:
“要xxx要xxx钱xxx钱”

之间的距离最多为 3 个单词,上面的正则表达式仍然只能找到:
“要xxx要xxx钱”

要处理这些情况,唯一可行的解​​决方案是,只匹配单词本身并使用前瞻/后瞻(需要更高级的正则表达式实现,例如 GNU grep 的 -P 用于 perl 正则表达式):

find_adjacent() {
    dist="$1"; shift
    word1="$1"; shift
    word2="$1"; shift
    
    ahead='\W+(\w+\W+){0,'"$dist"'}'
    behind='(\W+\w+){0,'"$dist"'}\W+'
    regex="$word1(?=$ahead$word2)|(?<=$word2)$behind\K$word1|$word2(?=$ahead$word1)|(?<=$word1)$behind\K$word2"
    
    printf 'Using the regex: %s\n' "$regex" 1>&2
    grep -P "$regex" "$@"
}

另一个示例用法(不区分大小写,显示文件名和行,突出显示找到的单词,搜索目录中的所有文件):

find_adjacent 15 capital GDP -i -Hn --color=auto -r folder_to_search

【讨论】:

  • 尝试使用shalt sell me meat for money in thine hand, and shalt 之类的输入,其中一个单词 (money) 在另一个单词 (shalt) 第一次出现后 5 个单词出现,并且在第二次出现之前 5 个单词出现第一个词(再次,shalt)。
  • 顺便说一句 - 如果您使用 grep -E,那么您不必转义正则表达式中的所有 ERE 元字符来激活它们。
  • @EdMorton:很好,这将打破-o 的例子,--color=auto 可能有点。但是对于 OP 的“...研究数据库;例如,您可以在其中搜索具有...的文章”,它应该可以工作
  • 不确定这意味着什么。哦,你还需要-z 来匹配跨行的字符串。
  • @EdMorton:-z 是可选的,我编辑了示例以反映该行为。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-07-26
  • 1970-01-01
  • 2021-03-05
  • 2019-11-09
  • 1970-01-01
  • 2016-02-09
相关资源
最近更新 更多