LINUX.ORG.RU

[Bash] Поиск строк с общими полями и подсчет количества совпадений

 , , ,


1

2

Доброго времени суток!

Нуждаюсь в помощи опытных коллег.

Есть файл такого содержания

cat input.txt

  • Иван:Петров
  • Сергей:Петров
  • Виктор:Петров
  • Вадим:Петров
  • Алексей:Петров
  • Станислав:Сидоров
  • Вячеслав:Сидоров
  • Геннадий:Сидоров
  • Владимир:Сидоров
  • Арсений:Сидоров
  • Константин:Иванов
  • Дмитрий:Иванов
  • Игорь:Иванов

Мне нужно подсчитать кол-во совпадений второго поля и найти строки в которых кол-во совпадений n меньше заданного N в скрипте (в данном случае 5) и использовать второе поле из этих строк в качестве переменной $var_surname для дальнейших действий.

на выходе нужно получить

  • n=
  • var_surname=

Буду благодарен любым подсказкам.



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

1. идешь while-ом по строке 2. пишешь функцию, подсчета совпадений 3. на каждой итерации, бери фамилию. разделитель двоеточие передавай в функцию поиска из пункта 2 (awk -F ':' '{print $2}') 3. функция поиска делает поиск (cat input | grep -i -c surname) и делает append в массив, хеш-мап нет, поэтому добавляй, в одну строку, потом при сортировке распарсишь, вида: surname:count 4. пишешь функцию сортировку 5. после идешь for-ом по массиву surname:count, передаешь на каждой итерации строку в функцию сортировки 6. в функции сортировки: сплит по ':', и сравниваешь count по условию, если удовлетворяет, принтуешь или что тебе там нужно.

garik_keghen ★★★★★
()

Вырезаешь второе поле man cut или man grep. Сортируешь man sort. Считаешь каждое входение man uniq. Получаешь список «кол-во фамилия». Дальше сам

anonymous
()
Ответ на: комментарий от garik_keghen

Как говорит тут один аноним, сразу видно пятизвёздочного. Ну накой awk -F ':' '{print $2}' если уже есть while? Это делается while IFS=: read name surname; С чего это нету map-а? Тут явно напрашивается ассоциативный массив. Ну и так далее.

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

Спасибо, очень помог) Простое и доступное для меня решение.

Сделал так:

cat unionfile | cut -d":" -f2 | sort | uniq -c > output

sed -i 's/^ *//' output

awk -F '[()]' '$1 < $N' output

и потом чтение строки в переменные

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

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

Раз тут идет звездная война, а ТС, как я понимаю, ничего не понимает, то если дать почти готовое решение, это ничего не изменить в балансе темных и светлых сил. ``` cut -d: -f2 | sort | uniq -c ``` И вообще, причем тут баш, сед, авк и другие. Задача именно на понимание.

anonymous
()
Ответ на: комментарий от debihard

Ну ты понял, что я ошибся на счет теба. Успехов.

anonymous
()
Ответ на: комментарий от debihard

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

Это точно будет попроизводительнее перелопачивания на баш.

anonymous
()
Ответ на: комментарий от debihard

sed -i ‘s/^ *//’ output

awk -F ‘[()]’ ‘$1 < $N’ output

и потом чтение строки в переменные

Что ты хочешь здесь сделать?

Ты же пишешь

найти строки в которых кол-во совпадений n меньше заданного N в скрипте

отсортируй дальше по количеству вхождений (по первому полю), и отбрось начало (или конец) до нужного числа N (например sed’ом). Будет тебе список фамилий удовлетворяющих условию, практически без использования баша.

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

Хорошо, если так можно сделать в bash, сходу и без лишних телодвижений. Я не в курсе и выдал сходу, по памяти.

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

Здесь sed-ом сначала удаляю пробелы вначале строки, потом с помощью awk по оставляю только строки где количество совпадений меньше заданного (значение в первом поле < N).

По идее можно и без седа, только я не знаю как понять какой номер поля там получается. Там идет 6 или 7 пробелов сначала.

Потом считываю из строк(и) переменные n и var_surname

Мне количество совпадений важно обязательно считать в переменную т.к. я потом произвожу с ней математическое действие.

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

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

while read -r n surname; do
  if [ $n -lt $N ] # если $n < $N
  then
    process $n $surname
  fi
done < <( cut | sort | uniq )
anonymous
()
Ответ на: комментарий от anonymous

Мне количество совпадений важно обязательно считать в переменную т.к. я потом произвожу с ней математическое действие.

Где ты производишь мат. действия? Если в баше, то читай сразу в баш и фильтруй там.

anonymous
()
Ответ на: комментарий от debihard

Мне количество совпадений важно обязательно считать в переменную т.к. я потом произвожу с ней математическое действие.

#!/usr/bin/env bash

N=4

declare -A -i count

while IFS=: read name surname; do
	count[$surname]+=1
done

for s in "${!count[@]}"; do
	if [[ count[$s] -le N ]]; then
		echo $s=${count[$s]}
	fi
done

$ Иванов=3

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

Спасибо) Протестировал данное решение тоже, все работает.

Только вот выдает строки с совпадениями n не только те, которые меньше заданного значения, но и которые равны этому значению.

debihard
() автор топика

Для простых обработок текста есть специальный язык.

cat debihard.awk

#!/bin/awk -E

BEGIN {
  FS=":"
  N=5
}

{
  names[$2]++
}

END {
  nn=0
  var_surname=""
  for (i in names) {
    if (names[i]<N && names[i]>nn) {
      nn=names[i]
      var_surname=i
    }
#    print i,names[i];
  }
  printf "n=%d\nvar_surname=%s\n", nn, var_surname
}
# <input.txt  ./debihard.awk
n=3
var_surname=Иванов
legolegs ★★★★★
()
Ответ на: комментарий от debihard

Только вот выдает строки с совпадениями n не только те, …

Ясно, пациент полный ноль в баше.

Раз мы для тебя стараемся, сделай для нас что-нибудь. Например, какая самая популярная фамилия?

anonymous
()
Ответ на: комментарий от debihard

Только вот выдает строки с совпадениями n не только те, которые меньше заданного значения, но и которые равны этому значению.

Потому там и 4, а не 5. Но если вам для зачёта надо, то поменяйте le, на lt.

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

Спасибо за пример, познавательно.

Правда отдельной программой не очень удобно, т.к. этот кусочек обработки является частью более глобального скрипта и хочется все в одном файле уместить.

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

Не, я не сдаю зачеты, я для своих задач пишу и учусь)

Думаю разобрался бы, выше был пример с -t вместо -e и читал я ранее, что -e это equal, но в любом случае спасибо за поправку.

И вообще всем отписавшимся - спасибо большое за то что подсказали как можно решить задачу разными способами. Добра вам всем. Хорошо, что есть такие отзывчивые люди, которым не лень кому-то менее опытному подсказать)

p.s. ну не полный ноль, скрипты пишу какие-то для себя, повышаю сложность. Но вот с массивами очень плотно пока не работал, знакомлюсь как раз.

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

На будущее - прочитай Advanced Bash Scripting Guide. Он есть и на русском(правда устаревший перевод), но тебе хватит. Оттуда и будет понятна в чём разница между -lt и -le и где можно обойтись встроенными средствами bash(если переносимость на другие шеллы не планируется), а где надо подключать внешние программы.

Ну а потом - только практика. И чтение чужого кода, да.

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

Правда отдельной программой не очень удобно

хочется все в одном файле уместить.

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

....
cat file | grep | sed | blabla | awk '
BEGIN {
  FS=":"
  N=5
}
.......
' | cat | grep | gzip | blabla
legolegs ★★★★★
()
Последнее исправление: legolegs (всего исправлений: 1)
Ответ на: комментарий от legolegs

ну это так себе хотелка.

А вот не соглашусь. Сравните свой код и мой чуть выше. Есть разница? Казалось бы в принципе почти одно и тоже и даже может awk тут будет и быстрее. Но, это пока вам не понадобится потом работать с полученными переменными, особенно если надо будет обрабатывать чуть ли весь ассоциативный массив гораздо дальше, чем можно поместить в этот внедренный код в сложный скрипт.

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

Но, это пока вам не понадобится потом работать с полученными переменными

Объявление запрошенных ТСом переменных в рамках этого скрипта, думаю, излишне (либо мало данных). Что будет, если попадется 2 фамилии с n<5?

В остальном - что в случае с awk'ом, что в случае с башем полученные пары можно обрабатывать одинаково; как было уже выше предложено:

$ awk -F: '{sn[$2]+=1} END {for (a in sn) if (sn[a]<5) print a, sn[a]}' file | while read surname n ; do ...

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

Что будет, если попадется 2 фамилии с n<5?

Так и я об этом. Что? Может тогда понадобятся имена? Номера строк в исходном файле? И главное, смысл тогда в awk, если результат из пайпа? Где удобство и скорость?

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

смысл тогда в awk
Где удобство и скорость?

По удобству - пусть каждый под задачу выбирает удобный для себя вариант. По скорости - awk будет быстрее на больших объемах данных.

Если у ТСа задача все равно подразумевает использование баша (некие математические операции с переменными - и да, а зачем тут баш?) - это не повод решать все исключительно средствами баша, благо подзадачи можно реализовать различными средствами.

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

По скорости - awk будет быстрее на больших объемах данных.

Да с чего бы это было б сильно заметно? Оба языка, что bash, что awk работают примерно одинаково - парсят текст, создают дерево разбора и его интерпретируют. Я знаю, что там внутри и даже правил и то и другое. У awk есть хорошее преимущество, когда надо обрабатывать входные строки через regex-ы. Когда же у нас задача разбить входные строки на два поля, то применение awk как правило только доказывает непрофессионализм пишущего.

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

Страдаю какой-то херней вместо приготовления ужина

Да с чего бы это было б сильно заметно?

Ну действительно:

rain@walkbook:~/to_net/test$ wc -l list
6000003 list
rain@walkbook:~/to_net/test$ ls -lh list
-rw-r----- 1 rain rain 121M июл 22 00:43 list

rain@walkbook:~/to_net/test$ time mawk -F: '{sn[$2]+=1} END {for (a in sn) if (sn[a]<5) print a, sn[a]}' list 

real    0m0,683s
user    0m0,670s
sys     0m0,012s

rain@walkbook:~/to_net/test$ time gawk -F: '{sn[$2]+=1} END {for (a in sn) if (sn[a]<5) print a, sn[a]}' list

real    0m2,979s
user    0m2,974s
sys     0m0,005s
rain@walkbook:~/to_net/test$ time (declare -A -i count; cat list | while IFS=: read name surname; do count[$surname]+=1 ; done)

real    2m43,414s
user    2m2,081s
sys     0m41,637s

YAR ★★★★★
()

Страдаю какой-то херней вместо приготовления ужина

Дык. Аж целых 2 минуты жевало, а с первого коммента - уже более 2 часов. Если вам жалко 2 минут на 121Мб, то перепишите на C. Не удивлюсь, что bash тормозит на генерации ассоциативного индекса.

На всякий случай, напоминаю, что я никогда не говорил, что bash на этой задаче может быть быстрее. Я говорил, что если вам понадобится потом эти данные, то встраивание другого интерпретатора в скрипт неудобно и будет скорее всего медленнее. Ну в самом деле, вот пусть у вас 100M будет по 4 имен у фамилий и только одно 5. Ну прогоните через пайп и while read.

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

Да с чего бы это было б сильно заметно?
я никогда не говорил, что bash на этой задаче может быть быстрее.

Это не «не сильно заметно». Это не «чуть быстрее». Это разница в 200 раз.

и будет скорее всего медленнее

Чем что? 2 последовательных цикла на bash по 3 минуты будут быстрее, чем 0.7 секунды на mawk + 3 минуты на bash? Ну ок.

Не удивлюсь, что bash тормозит

Дальше можно было не писать:

rain@walkbook:~/to_net$ time (cat list | while IFS=: read name surname; do true ; done)

real    2m33,704s
user    1m52,898s
sys     0m41,033s

rain@walkbook:~/to_net$ time (cat list | while read string; do true ; done)

real    1m20,660s
user    0m40,290s
sys     0m40,668s

rain@walkbook:~/to_net$ time (while read string; do true ; done < list)

real    0m34,068s
user    0m28,237s
sys     0m5,816s

rain@walkbook:~/to_net$ time (while IFS=: read name surname; do true ; done < list)

real    1m46,313s
user    1m39,405s
sys     0m6,876s


Когда же у нас задача разбить входные строки на два поля, то применение awk как правило только доказывает непрофессионализм пишущего

mawk
real    0m0,683s

while read string
real    1m20,660s

while IFS=: read name surname
real    2m33,704s


Ну ок.

// Ушел спать.

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

Это не «не сильно заметно». Это не «чуть быстрее». Это разница в 200 раз.

Как вы предсказуемы. На реальных данных, когда вообще имеет смысл пользоваться скриптами, разница в долях секунд не заметна.

Ну ок.

А вот это уже подлог. Я вам предлагал обработать задачу awk, а потом закачать весь массив через read. Собственно данных уже достаточно, чтобы увидеть, что будет медленнее.

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

Добрый день. Вы оказались правы, когда продвинулся дальше, то оказалось, что был один неучтенный момент в логике скрипта и понадобилось помимо «фамилии» и кол-ва совпадений извлекать еще и полностью строки удовлетворяющие данному условию.

А вот как это сделать в контексте вашего примера никак не разберусь. Подключать еще один массив?

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

Вы оказались правы,

Справедливости ради, обсуждалось абстрактное условие: «если вдруг...» :)

Подключать еще один массив?

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

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

Ты занимаешься не тем делом. Ты пытаешься работать с базой данных на скриптовом языке, который задуман как запускалка других программ. У тебя ничего хорошего не получится, как хорошо бы ты не знал этот скриптовый язык для запуска программ. И при этом ты не знаешь этот скриптовый язык.

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

Это лишь кусочек большого скрипта с ветвлениями и циклами. Мне нужно запускать один файл и чтобы он выполнял всю работу на серверах, принимал разные решения в зависимости от вводных данных, ошибок, которые меняются после каждого последующего запуска. Поэтому я решил, что будет удобнее записывать данные в простые файлы. И при повторном запуске считывать данные с них и выполнять те или иные действия.

Если подключать БД, то вероятно одним скриптом я уже не справлюсь?

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

Примерно в этом ключе и думал.

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

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

Развивая тему.

Не надо строить массивы в баше при обработке реальных данных, ты столкнешься с тем, что у баш нет эффективных по скорости и по потреблению памяти структур данных для работы с такими объемами данными.

Поэтому используй баш как запускалку подходящих для этого программ. Не надо считать математику в баш.

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

cut -f2 "исходный_список.txt" | sort | uniq > "несортированный_список_частот_фамилий.txt"

sort -f1 "несортированный_список_частот_фамилий.txt" > "сортированный_по_частоте_фамилий.txt"

sort -f2 "несортированный_список_частот_фамилий.txt" > "сортированный_по_фамилиям.txt"

man column # чтобы подформатировать текстовые таблицы (убрать начальные пробелы)

# awk я не знаю, авк-скрипт скорее всего неправильный
# печатать все строки пока не обломится на условии, чтобы не проходить весь файл (оптимизация)
# надеюсь, понимаешь зачем здесь сортированный по частоте список, и как сортированный - по возрастанию и по убыванию
awk "{ print } if (\$1 < $N) { exit }" "сортированный_по_частоте_фамилий.txt" > "список_фамилий_удовлетворяющих_условию.txt"

# дальше тебе нужно закатить солнце вручную
# сделать sql-join "исходный_список.txt" join "список_фамилий_удовлетворяющих_условию.txt"
# where t1.фамилия=t2.фамилия
# sql тоже не знаю
man join
man comm
#вроде должны справится

sort -по_полю_фамилия "исходный_список.txt" > "сортированный_по_фамилиям.txt"
comm ...
 # или
join "сортированный_по_фамилиям.txt" "список_фамилий_удовлетворяющих_условию.txt" > "список_на_котором_применим_математику.txt"

Все промежуточные файлы можно скоратить направив через пайпы следующей команде.

Чувствствуешь, что во всем этом практически не используется баш, тупое использование спец-инструментов, и все это есть закат солнца вручную - имитация субд.

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