LINUX.ORG.RU

Фильтрация по порядку шаблона

 , , ,


0

0

Как можно фильтровать по регулярке (1|a|c|2|3|b|v) с сохранением сортировки по шаблону, если текст:

2
a
v
3
b
f
o
?

Т.е. должно вывести:
a
2
3
b
v


Данный шаблон просто пример. Есть набор строк, нужно фильтровать по второму столбцу по шаблону, сохраняя последовательность как в шаблоне.
Вообще этот шаблон у меня изначально массив ( 1 a c 2 3 b v ), просто преобразовал в регулярку, если как-то сразу массивом получится — ещё лучше.
Через grep получается добиться подобия того, что мне нужно grep -f </file_with_filter_lines /text, но если в /file_with_filter_lines есть регулярки — почему то не работает.
Можно конечно циклом while read пробежаться, но хочется одной командой, while read с циклом сравнения всё-равно будет медленнее какого-нибудь sed, grep или awk

★★★★★

Последнее исправление: teod0r (всего исправлений: 4)

последнее время часто приходится сравнивать всякие списки, фильтравать один список по шаблону из другого искать дубликаты в двух списках, только если они из определённого списка... какими бы тулзами всё это делать?

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

awk, sed, grep.

По вопросу из ОП, элементарщина же. Задай счетчик, который при совпадении по каждому отдельному шаблону из регулярки будет увеличиваться. Печатай итог как счетчик - совпадение, а затем сортируй по счетчику. Если такое не осилишь, хотя чего там не осилить, то запускай на совпадение по одному шаблону за раз, потом собирай это воедино в один файл.

anonymous
()

Одними регулярками - никак. Сперва фильтруй, потом сортируй. У онанимуса выше много ошибок в слове perl.

pinus_nigra
()

Если я правильно понял это путанное описание задачи, вы хотите отсортировать вывод по порядку подшаблонов в регулярке, разделенных «ИЛИ»? Ну тогда возникает конфликт. Если во входных данных есть строки попадающие под всю регулярку, то непонятно, как их сортировать, если строк под один подшаблон больше одной. Скачать весь файл в память, и выводить вначале строки попадающие под первый подшаблон? Ну это не сложно же. Или выводить когда полностью наберется на один список подшаблонов, а далее формировать второй? Когда останавливаться то в порциях данных?

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

да. есть список вида:

yes package1 /dir/ v-uywv
yes package2 /dir2/ ver
no package3 /dir1/ ver-3r
в котором могут быть тысячи строк.
есть список (массив с точными именами — каждый элемент точное имя, которое точно присутствует в списке), по которому нужно отфильтровать (чтобы имя точно совпадало по второму столбцу списка), но нужно сохранить последовательность из шаблона-массива. в шаблоне может быть от 1 до сотен элементов. while read line с циклом проверки элементов массива я и сам могу написать, наверняка будет медленно, нужно что-то более шустрое. наверное, получится каким-нибудь sed или awk, но я плохо знаю синтаксис

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

регулярками или нет мне не важно. изначально шаблон у меня — массив, регулярками это я так, просто попробовал как пример, не очень удачный. лучше конечно не регулярками, с регулярками нужно будет ещё экранировать возможные спецсимволы в шаблоне.

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

Кажется понял, хотя конкретно на вопросы из прошлого коммента вы так и не ответили. Вы изобретаете ассоциативный массив. Если у вас действительно не шаблон с метасимволами, а конкретные имена, то это и будет индексом в ассоциативном массиве: A[имя пакета]=(номера индексов в загруженном простом массиве всего файла). Это будет работать даже на bash быстрее, чем на awk, так как в bash readarray/read -a - С-шная функция интерпретатора, а не построчная интерпретация while read

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

что такое "номера индексов в загруженном простом массиве всего файла"?
можно пример?

с метасимволами

вообще нужно универсальное решение, которое поддерживает любые символы, как часть имени (но не как регулярки)

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

что такое «номера индексов в загруженном простом массиве всего файла»?

Индексы через пробел из исходного массива.

#!/usr/bin/env bash

declare -a packages=(package1 package2 package3)
declare -A P
declare -a file words
declare -i i=0

readarray -t file
set -f
for line in "${file[@]}"; do
  words=($line)
  file[i]="${words[0]} ${words[@]:2}"
  package=${words[1]}
  list=${P["$package"]}
  P["$package"]="$list $((i++))"
done

for p in "${packages[@]}"; do
        echo "$p:"
        list=${P["$p"]}
        for i in $list; do
                echo "${file[i]}"
        done
        echo
done
Исходный файл
yes package1 /dir/ v1
yes package2 /dir2/ v1
no package3 /dir1/ v1
no package1 /dir/ v2
no package2 /dir2/ v2
yes package3 /dir1/ v2
Результат — отсекаем ненужные пакеты и выводим в порядке следования элементов в массиве пакетов:
package1:
yes /dir/ v1
no /dir/ v2

package2:
yes /dir2/ v1
no /dir2/ v2

package3:
no /dir1/ v1
yes /dir1/ v2

вообще нужно универсальное решение, которое поддерживает любые символы, как часть имени (но не как регулярки)

Тут будет работать любые символы кроме пробелов и не распознаваться как шаблоны. С шаблонами на любом языке придётся делать в лоб, я не знаю языка, где в зависимости от номера в регулярке элемента по ИЛИ можно сразу поместить в разный массив.

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

любые символы

где ж вы раньше были. до этого думал, эемент ассоциативного массива не может содержать всякие там ;$ символы... как-то пробовал, а их просто оказывается экранировать надо.
с ассоциативными массивами дел практически не имел. что делает предпоследняя строка в первом цикле?
возможно, можно как-то упростить. набор строк отсортирован в алфавитном порядке по второму столбцу. в нём нет дубликатов. то что есть в массиве-фильтре 100% есть в списке и тоже не повторяется.

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

до этого думал, эемент ассоциативного массива не может содержать всякие там ;$ символы... как-то пробовал, а их просто оказывается экранировать надо.

Можно подумать, что тут есть какая-то разница. Вся разница, что индекс у ассоциативного массива не номер, а строка. А вот как эта строка сформируется определяется по стандартным правилам раскрытия спецсимволов и экранирования. А уж данные-элементы то и подавно могут содержать что угодно везде во всех языках и только специальное определение типов ускоряет и делает проверки.

что делает предпоследняя строка в первом цикле?

Добавляет к списку еще один текущий индекс.

возможно, можно как-то упростить. набор строк отсортирован в алфавитном порядке по второму столбцу.

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

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

есть массив, например PKG=(bash acl dcron). в нём может быть от 1 до сотен элементов
есть список, выданный утилитой пакетного менеджера prt-cache printf '%i\t%n $p $v-$r: d\n'. пример вывода:

yes acl /my/ 2.3.1-1: Access Control Lists library
yes bash /core/ 5.1.8-1: An sh-compatible command language interpreter
no  bash-completion /opt/ 2.11-1: Programmable completion functions for bash
yes dcron /core/ 4.5-3: Multi-user cron daemon
no  fcron /opt/ 3.3.0-2: Enhanced periodical command scheduler like cron
no  gdb /opt/ 11.1-1: The GNU Debugger (GDB)
yes gdbm /core/ 1.21-1: GNU database library for C

в нём может быть сколько угодно строк, одна, тысячи. первое поле может быть yes, no, diff. отсортировано по алфавиту по второму столбцу.
нужно вывести в данном случае:
yes bash /core/ 5.1.8-1: An sh-compatible command language interpreter
yes acl /my/ 2.3.1-1: Access Control Lists library
yes dcron /core/ 4.5-3: Multi-user cron daemon

т.е. получить только строки, совпадающие по второй колонке с ${PKG[@]}, сохранив последовательность из PKG.
сейчас я храню выданный ПМом список в переменной. можно и в массив переделать...
то что есть в PKG всегда присутствует в списке (хотя, не факт, что в будущем как-то не переделаю)

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

то что есть в PKG всегда присутствует в списке по алфавиту

Тогда я не понимаю, в чём проблема вообще и регекса с ИЛИ в частности, раз всё уже отсортировано, выбирай по регексу нужные пакеты и сразу выводи.

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

по регэкспу оно сохранит порядок списка

Как показала многочисленная практика разбора местных вопросов, проблемы у задающих вовсе не в том, что они не знают как реализовать свой алгоритм на том языке, который они почему-то выбрали (зачастую не по задаче), а в самом умении составлять алгоритм и программировании как области знания вообще. И это только во-первых. Если сортировка исходного текста не совпадает с сортировкой выходного, то это просто означает, что сортировка отсутствует, она никак не помогает и не должна рассматриваться вообще. Конечно можно прикопаться к частностям, типа - первая буква у всех слов для поиска разная и так далее, но всё это частности и в общем это только мешает, если в вопросе стоит как правильно и универсально, а не один конкретный разовый пример. А во-вторых, иногда даже дело до алгоритма не доходит. Я вот какой раз от вас бьюсь, пытаясь вам и так и этак сказать, что у вас сплошное противоречие в условии, вам надо одновременно задать как в порядке перечисления строк поиска и одновременно в порядке как в исходных данных. Ну не бывает так! Вы, похоже так и не сделали нормальный пример, на котором можно увидеть, что алгоритм работает как надо, то есть - взять конкретные данные, дать конкретный вывод и примеры как не надо, если алгоритм сортирует не так. Ибо если как в исходных данных, то порядок в списке поиска не важен и достаточно просто один регексп, либо порядок в исходном тексте не рассматривается. Третьего не дано.

Безусловно, для ускорения можно сравнивать следующую строку после успешного найденного начиная с того же самого шаблона, но это следующий этап, алгоритм оптимизации. Но для этого ещё надо дойти, а не начинать с этого.

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

я не говорил, что мне надо одновременно порядок перечисления строк поиска и порядок как в исходных данных.
мне нужен порядок ТОЛЬКО из шаблона-массива.
пример дан вполне рабочий, только список строк дан не весь, несколько строк, весь список — тысячи строк.
то что я написал, что там по алфавиту, это я к тому, что, возможно, удастся упростить ваш алгоритм. возможно, он и сейчас рабочий, я его ещё не проверял, руки не дошли пока. в нём мне непонятны 2 последние строки в теле первого цикла: list=${P["$package"]}; P["$package"]="$list $((i++))". для чего этот "$list $((i++))"?

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

пример дан вполне рабочий, только список строк дан не весь, несколько строк, весь список — тысячи строк.

Причём тут тысячи строк, если вопрос был как группировать и надо ли.

то что я написал, что там по алфавиту, это я к тому, что, возможно, удастся упростить ваш алгоритм

Нет, не удастся. Потому что регексы не отсортированы по алфавиту, bash > acl, а насчёт будут ли одинаковые строки по второму полю я так и не понял.

я его ещё не проверял, руки не дошли пока.

Ну охренеть. 10 дней прошло. Я сам скрипт с отладкой писал 5 минут.

для чего этот «$list $((i++))»?

Я же ответил. 10 дней назад

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

как группировать и надо ли.

что группировать?

будут ли одинаковые строки по второму полю

нет

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

будут ли одинаковые строки по второму полю

нет

Ну так это всё упрощает, не надо запоминать в группы и упрощается не понятные для вас строки запоминания нескольких индексов для одного совпадения, там будет просто $((i++)) без манипуляции с $list. И далее будет простой, а не вложенный цикл. И всё это по причине, что только через две недели стало понятно, что у вас за входные данные!

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

Ради ускорения решил убрать описание из того, что выводит программа в список. Т.е. количество полей в каждой строке одинаковое.
Вот часть списка:

yes	acl /my/ 2.3.1-1
yes	bash /core/ 5.1.8-1
no	bash-completion /opt/ 2.11-1
no	crux-bashcompletion /opt/ 20031028-1
yes	dcron /core/ 4.5-3
no	fcron /opt/ 3.3.0-2
no	gdb /opt/ 11.1-1
yes	gdbm /core/ 1.21-1
no	pkg-get-bashcompletion /opt/ 0.4.5-2
no	prt-get-bashcompletion /opt/ 5.19.3-1
no	xorg-font-cronyx-cyrillic /xorg/ 1.0.3-1

Целый список сейчас ~1600 строк.
Вот мой первый вариант:
declare -A P
declare -a line list PKG
declare -i i
PKG=(bash acl dcron)
readarray -td$'\n' list < <(prt-cache printf '%i\t%n %p/ %v-%r\n')

for (( i=0; i<${#list[*]}; i++ )) {
	line=(${list[i]})
	P["${line[1]}"]="${list[i]}"
}

for (( i=0; i<${#PKG[*]}; i++ )) {
	printf %s\\n "${P[${PKG[i]}]}"
}

Потом подумал, плохо, что сначала дожидаешься, пока отработает prt-cache в массив, потом тока опять цикл для каждой строки, и для каждой строки создавать массив всей этой строки.
Второй вариант:
declare -A P
declare -a PKG
declare -i i
PKG=(bash acl dcron)

while read -r s name dir ver; do
	P["$name"]="$s $name $dir $ver"
done < <(prt-cache printf '%i\t%n %p/ %v-%r\n')

for (( i=0; i<${#PKG[*]}; i++ )) {
	printf %s\\n "${P[${PKG[i]}]}"
}

Первый вариант всё-равно быстрее.
Можно ли заменить while read во втором варианте на более быстрый readarray?
Нужно ли менять line=(${list[i]}) в первом варианте на readaaray? Пробовал, так и не понял, как через readaarray создать массив из другой переменной.
Как-то, может, объединить эти два варианта?

teod0r ★★★★★
() автор топика
Последнее исправление: teod0r (всего исправлений: 2)
Ответ на: комментарий от teod0r

Можно ли заменить while read во втором варианте на более быстрый readarray?

В readarray есть колбэк, но почему-то не умеет разбивать на слова, я бы разрешил список переменных.

declare -a packages=(package1 package2 package3)

declare -A P
declare -a words

for p in "${packages[@]}"; do
        P["$p"]=1
done

read_cb() {
  words=($2)
  p=${words[1]}
  [[ ${P["$p"]} != 1 ]] && return 0
  P["$p"]="$2"
}

set -f
readarray -t -c 1 -C read_cb

for p in "${packages[@]}"; do
        p=${P["$p"]}
        [[ $p != 1 ]] && echo "$p"
done

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

не совсем понятно. данный пример это просто набросок? где вводится список строк? что значит колбэк, колбэк просто должен вызывать функцию? а откуда читает данный readarray? почему read_cb назначает words из $2, что вообще идёт на ввод этой функции?
или это просто, что можно было бы сделать, если бы...?

teod0r ★★★★★
() автор топика
#!/usr/bin/env python3
import sys

if len(sys.argv) == 1:
    print("Usage: get_shit | yoba.py a c 2 3 b v")

pkgs = sys.argv[1:]

res = {pkg: [] for pkg in pkgs}

for line in sys.stdin:
    words = line.split()
    if len(words) > 1:
        pkg = words[1]
        if pkg in pkgs:
            res[pkg].append(line)

for pkg in pkgs:
    for line in res[pkg]:
        sys.stdout.write(line)
fulmar_lor
()
Последнее исправление: fulmar_lor (всего исправлений: 1)
Ответ на: комментарий от teod0r

не совсем понятно. данный пример это просто набросок?

А слабо было запустить и увидеть, что это как и предыдущие скрипты полностью рабочие?

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

оно оказалось ещё медленнее.

искал что-то типо этого:

#!/bin/bash -f

declare -A P
declare -a PKG line
declare -i i
PKG=(bash acl dcron)

while read -ra line; do
	P["${line[1]}"]="${line[*]}"
done < <(prt-cache printf '%i\t%n %p/ %v-%r\n')

for (( i=0; i<${#PKG[*]}; i++ )) {
	printf %s\\n "${P[${PKG[i]}]}"
}

но всё-равно и это оказалось не самым быстрым.
(до этого неправильно мерял)
вот что оказалось самым быстрым:
#!/bin/bash -f

declare -A P
declare -a PKG
declare -i i
PKG=(bash acl dcron)

while read -r s name dir ver; do
	P["$name"]="$s $name $dir $ver"
done < <(prt-cache printf '%i\t%n %p/ %v-%r\n')

for (( i=0; i<${#PKG[*]}; i++ )) {
	printf %s\\n "${P[${PKG[i]}]}"
}

teod0r ★★★★★
() автор топика

bash

А зойчем?

В теории такое можно написать на awk, но гораздо проще взять perl или ruby.

wandrien ★★
()

одно в один файл, другое - в другой файл. дальше grep -f, не? :)

aol ★★★★★
()

Вот набросок кода на руби для примера из стартового поста. Выводит

a
2
3
b
v

как и было прошено.

patterns = %w{ 1 a c 2 3 b v}

matches = Hash.new {|h, k| h[k] = []}

STDIN.read.lines {|line|
  patterns.each {|pattern|
    if line.include? pattern then
      matches[pattern].push(line)
      break
    end
  }
}

patterns.each {|pattern|
  matches[pattern].each {|line|
    puts line
  }
}
wandrien ★★
()
Ответ на: комментарий от teod0r

вот что оказалось самым быстрым:

Не понятно, почему в printf нельзя поменять поля, чтобы было первое name, тогда можно было б вообще писать read name other, раз уж вам всё равно что там.

Меня другое интересует, почему вы в упор не хотите использовать правильную конструкцию последнего цикла, а переписываете его в C-style, что явно медленнее.

vodz ★★★★★
()
1 января 2022 г.
Ответ на: комментарий от vodz

сделал как ты сказал, вначале name всё-равно не понятно, почему printf %s\\n "${PKG[@]}" выводит именованный массив не в правильном порядке, не в том, в каком он назначался, а в каком-то непонятном отсортированном порядке. как выводить его сразу в правильном порядке без sort? и можно ли делать как-то типа grep ... <<<"${PKG[@]}" чтобы передавалось грепу построчно, а не как одна строка? без лишнего printf %s\\n "${PKG[@]}" | ... — не работает

teod0r ★★★★★
() автор топика

AWK пользуй. Там все просто.

Bootmen ☆☆☆
()
Ответ на: комментарий от teod0r

не понятно, почему printf %s\\n «${PKG[@]}» выводит именованный массив не в правильном порядке,

В каком-таком «правильном»? Сегодня правильный вам надо такой, завтра другой. Это ассоциативный массив, там внутренний порядок, какой-то хэш. Сортировка вообще вещь тяжелая, но вам она не нужна, так как индексы хешируются быстро и выборка будет намного лучше по ресурсам, чем пересортировка.

и можно ли делать как-то типа grep ...

Я ничего не понял. Вы хотите, чтобы grep вначале в файле нашел первый regex, потом снова прошелся по файлу и нашел второй? А не много ли вы хотите?

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

индексы

сейчас храню дополнительно индексы в обычном массиве для сохранения правильной послндовательности. есть способ лучше?

Вы хотите

хочу просто иметь возможность предавать массив как набор строк без лишнего printf, используя <<<, например while read a b ...; do ...; done <<<"${array[@]}". видимо, в будущем буду переходить на rc shell, если не окажется что в нём не хватает какого-то нужного функционала

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

сейчас храню дополнительно индексы в обычном массиве для сохранения правильной послндовательности. есть способ лучше?

Ещё раз, «правильная последовательность» это только для вашего текущего представления о конкретной задаче. Отсюда логически вытекает, что и придётся их хранить, ибо они и есть ваши правильные сейчас.

хочу просто иметь возможность предавать массив как набор строк без лишнего printf, используя <<<, например while read a b ...; do ...; done <<<«${array[@]}».

Ерунда какая-то. Если у вас уже массив, то зачем его вначале формировать в виде строки, а потом считывать снова через разбиение по IFS? printf у меня по назначению — для вывода результата.

видимо, в будущем буду переходить на rc shell, если не окажется что в нём не хватает какого-то нужного функционала

Вообще-то у вас нет решения вообще, а просто тупое нытьё, почему нет в языке волшебной операции сразу сделать вот такую задачу. Глупо и уже утомительно.

vodz ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.