LINUX.ORG.RU

grep абзацев

 , ,


1

1

Есть такая утилита grep, позволяющая отфильтровать строки по маске. Тут всё хорошо.

Но, предположим, имеется текстовый файл с регулярной структурой, например такой:

...
foo1 #начало абзаца
....
key=bar1 #искомый признак
...
foo2 #начало нового абзаца - конец предыдущего
...

как занимаясь поиском строки вывести весь абзац?

То есть условно cat myfile | grep bar1 выводило весь абзац от foo1 до foo2?

★★★★★

С произвольными лексическими единицами хорошо умеет работать sam.

cat file | ssam -e 'x/(.+\n)*/ v/expr/ d'

x запускает цикл, (.+\n)* — определение параграфа, v — if not по текущему срезу, d — удаляет текущий срез.

kaldeon
()
Последнее исправление: kaldeon (всего исправлений: 1)
Ответ на: комментарий от etwrq

Нет. Тут структура текста. Ещё возможный пример:

node1
aaa=5
bbb=hello
ccc=2020-05-15
node2
aaa=44
bbb=world
ccc=2023-03-14
node3
aaa=579
bbb=buy
ccc=2024-01-11
ddd=¥

ищем bbb=world

выводим

node2
aaa=44
bbb=world
ccc=2023-03-14

тут node[0-9]+ признак начала абзаца и число параметров может быть разным

Psilocybe ★★★★★
() автор топика
Последнее исправление: Psilocybe (всего исправлений: 3)
Ответ на: комментарий от Psilocybe
#!/usr/bin/awk -f

/^node[0-9]+$/ {
	if (found) {
		for (i = 1; i <= nblock; i++)
			print block[i]
	}

	found = 0
	nblock = 0
	block[++nblock] = $0
}

/bbb=buy/ {
	found = 1
	block[++nblock] = $0
}

! /^node[0-9]+$/ && ! /bbb=buy/ {
	block[++nblock] = $0
}

END {
	for (i = 1; i <= nblock; i++)
		print block[i]
}
kaldeon
()
Ответ на: комментарий от Psilocybe

В sam это выглядело бы так:

c1 = '([^n]|\n)'
c2 = '([^o]|\n)'
c3 = '([^d]|\n)'
c4 = '([^e]|\n)'
c5 = '([^0-9]|\n)'
ssam -e 'x/^node[0-9]+\n('$c1'|n'$c2'|no'$c3'|nod'$c4'|node'$c5'|\n)*/ v/bbb=buy/ d'

Обработку усложняет символ перевода строки и отсутствие жадности.

Ещё можно вместо захвата всего блока обрабатывать построчно, но будет не сильно выразительнее:

ssam -e 'x g/^node[0-9]+$/ {
	+ g/^node[0-9]+$/ - d
	+ v/^node[0-9]+$/ ., y/^node[0-9]+\n(.|\n)*/ g/./ -,. v/bbb=buy/ d
}'
kaldeon
()
Последнее исправление: kaldeon (всего исправлений: 1)
Ответ на: комментарий от Psilocybe

.+[\r\n]+?bbb=world[\r\n]+.+

тут строку сверху и строка снизу

Если надо так чтобы первый node игнорировался, потому что он ещё абзац выше, то тут слишком мудрёно, алгоритм идёт по символьно, можно запретить символ, но не слова, так как .+? сожрёт всё включая очередной node. Можно попытаться повторение группы (node[0-9]+.+?)+ но у меня не сработало.

Вот вроде сработал группами, я сам сначала накосячил

(node[0-9]+.+?)+?\Knode[0-9]+.+?bbb=world.+?(?=node[0-9]+)

Если что я тестирую на этом

AZJIO
()
Последнее исправление: AZJIO (всего исправлений: 4)
Ответ на: комментарий от Psilocybe

Очевидно же, что нужно искать решение в облати многострочных регулярных выражений. Для данного примера можно так:

user> (->> "/tmp/nodes.txt" slurp 
           (re-matches #"(?s).*(node.*?bbb=world.*)node.*")
           last println)
node2
aaa=44
bbb=world
ccc=2023-03-14

nil

Модификатор ?s нужен для

Enables dotall mode.
In dotall mode, the expression . matches any character, including a line terminator. By default this expression does not match line terminators.

Dotall mode can also be enabled via the embedded flag expression (?s). (The s is a mnemonic for "single-line" mode, which is what this is called in Perl.)
ugoday ★★★★★
()

Может я что-то не понимаю но абзац это строка от начала строки до конца строки. Может тебе не абзац нужен а предусловие и постусловие? (?….) Вот это вот

ckotctvo
()

Я дома буду я накорябаю. Но хотелось бы знать точный синтаксис этих foo. В грепе есть режим перла и там есть precondition и postcondition которые матчатся но в ответ не идут

ckotctvo
()

Или используй pcregrep - там есть возможность выборки capturing groups. Выключи жадный greedy режим, первой группой пусти foo1, третьей foo2 а второй то что надо скушать и выведи вторую группу

ckotctvo
()

А можно и заморочиться и сделать вообще правильно с помощью спецификаций:

(ns nodes
  (:require
   [clojure.spec.alpha :as s]
   [clojure.string :as str]))

;; kv — непустая строка, включающая =
(s/def :line/kv (s/and
             string?
             #(not (str/blank? %))
             #(str/includes? % "=")))

;; title — непустая строка, без =
(s/def :line/title
  (s/and
   string?
   #(not (str/blank? %))
   #(not (str/includes? % "="))))

;; paragraph — title и не менее одной kv строки
(s/def :line/paragraph (s/cat :titel :line/title :content (s/+ :line/kv)))

;; cfg — не менее одного параграфа
(s/def :line/cfg (s/+ :line/paragraph))

(def data-file "/tmp/nodes.txt")

(def data
  (s/conform :line/cfg
             (-> data-file slurp (str/split #"\n"))))

После чего данные приобретают вид

[{:titel "node1", :content ["aaa=5" "bbb=hello" "ccc=2020-05-15"]}
 {:titel "node2", :content ["aaa=44" "bbb=world" "ccc=2023-03-14"]}
 {:titel "node3",
  :content ["aaa=579" "bbb=buy" "ccc=2024-01-11" "ddd=¥"]}]

И с ними можно сделать вообще всё, что душа пожелает. На сладкое можно так

nodes> (s/explain :line/cfg (-> data-file slurp (str/split #"\n")))
"" - failed: (not (blank? %)) in: [5] at: [:content] spec: :line/kv
nil

Здесь я испортил файл, добавив пустую строчку, которая изначальной спецификацией не допускалась. Сообщение об ошибке показывает нам на какой строке какое правило завалилось. Можно так оставить, можно сделать более понятное для пользователя пояснения.

ugoday ★★★★★
()
Ответ на: комментарий от Psilocybe

тебе уже 3 варианта с awk накидали, это именно то для чего его создавали. и будет работать даже под вендой если gawk.exe себе поставишь. я им спеки на ЗИП делал из разнообразных supportfile форматов от вендоров.

ну либо perl. тоже везде будет работать.

mumpster ★★★★★
()
Ответ на: комментарий от Psilocybe

Это, конечно, задачка для awk, но надо и bash добавить 😀

mapfile -t < "$1"
for ((i=0; i<${#MAPFILE[@]}; ++i)) {
    [[ ${MAPFILE[i]} =~ node[0-9]+ ]] && {
        index=$i
        continue
    }
    [[ ${MAPFILE[i]} == $2 ]] && {
        echo ${MAPFILE[index]}
        ((++index))
        break
    }
}
for ((i=$index; i<${#MAPFILE[@]}; ++i)) {
    [[ ${MAPFILE[i]} =~ node[0-9]+ ]] && break
    echo ${MAPFILE[i]}
}
$ # команда, файл, строка
$ my_grep nodes bbb=world
node2
aaa=44
bbb=world
ccc=2023-03-14
papin-aziat ★★★★★
()

Если у тебя начало абзаца как-то специально промаркировано (^$, ^\s\s\s\s, ^Абзац:\s и т.п.) то все \n заменяешь (напр. tr’ом) на какую-нибудь хрень, типа \r, \r перед маркером абзаца заменяешь на \n, например sed’ом, теперь каждый абзац - одна строка, грепом выводишь нужный абзац, заменяешь \r обратно на \n.

типа tr '\n' '\r' "твой-файл.txt" | sed 's/\r\s\s\s\s/\n\s\s\s\s/g' | grep 'паттерн для искомого абзаца' | tr '\r' '\n'

Ну это если очень нужно именно грепом это сделать. Так-то awk справится и в одиночку, а он обычно есть даже в укушенных системах типа OpenWrt.

Stanson ★★★★★
()