Regex для строк рядом с другими строками? - PullRequest
0 голосов
/ 10 сентября 2018

Я хочу написать гибкое регулярное выражение для grep, которое будет возвращать поисковые термины, найденные на определенном расстоянии друг от друга.

Идеальное поведение - это что-то вроде исследовательских баз данных;например, где вы можете искать статьи, которые имеют capital и GDP в пределах 15 слов друг от друга, в том числе статьи, в которых строки capital и GDP могут быть разделены на пять, шесть, семь и т. д.., буквенно-цифровые строки неопределенной длины.Оператор регулярного выражения будет включать знаки препинания (например, запятые, точки, дефисы), а также знаки ударения и диакритические знаки.Таким образом, результаты, в которых chechè и lavi находятся на расстоянии не более пяти строк.

Я полагаю, что в этом заявлении будут использоваться заглядывания и фразы типа {1,15}, или, возможно, передача одного grep через другой grep, но это теряет выгоду GREP_OPTIONS='--color=auto'.Построение этого действительно вне моего набора навыков.У меня есть набор документов .txt, по которым я хочу выполнить поиск, но сделать регулярное выражение гибким для изменения расстояния между строками или усечения терминов также было бы полезно для других, у которых есть такие вещи, как полевые заметки или чтение заметок в стандарте.формат.

РЕДАКТИРОВАТЬ

Ниже приведен пример отрывков, взятых из Библии.

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’.

Если бы я хотел найти примеры, гдеshalt и money присутствуют в пяти словах (включая знаки препинания), как бы я написал это регулярное выражение?

Я не уверен, как получить ожидаемые результаты, поскольку 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,, поскольку в качестве шестой строки отображается «деньги».

Ответы [ 2 ]

0 голосов
/ 11 сентября 2018

Краткий ответ: 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:

«\ ш»

Соответствует составному слову, это синоним ‘[_ [: alnum:]]’.

«\ W»

Соответствует несловесной составляющей, это синоним ‘[^ _ [: alnum:]]’.

Общий ответ для динамического построения grep, в данном случае с функцией оболочки:

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

Редактировать

Как указывает Эдмортон , он находит только первую часть продолжения матча. Это все равно будет соответствовать правой линии, но цветовая подсветка будет немного.

Чтобы исправить это, регулярное выражение станет более сложным, потому что оно должно соответствовать любому продолжению "shalt ... money ... shalt" в 4 случаях:

  • "будет ... деньги ... будет"
  • "будет ... деньги ... будет ... деньги"
  • "деньги ... будут ... деньги"
  • "деньги ... будут ... деньги ... будут"

Это можно сделать, заменив строку 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"

Кроме того, это может быть перепутано так:
"Shalt XXX Shalt XXX деньги XXX деньги"

Если расстояние между ними не превышает 3 слова, приведенное выше регулярное выражение все равно будет находить:
"Shalt XXX Shalt XXX деньги"

Для этих случаев единственное жизнеспособное решение состоит в том, чтобы сопоставлять только слова и использовать упреждающие взгляды / упущения (требуется более продвинутая реализация регулярного выражения, например, GNU grep's -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
0 голосов
/ 11 сентября 2018

Ну, это не 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) появляется через 5 слов после первого появления другого слова (shalt) И за 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

Теперь, когда вы можете увидеть, что означают эти escape-последовательности и числа, обновите скрипт 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]))
    }
}

и при его запускевы увидите дамп всех доступных цветов, и для каждого целевого текста ему будет предшествовать имя файла и номер строки в этом файле, а текст будет окрашен в красный цвет:

enter image description here

...