LINUX.ORG.RU

Как из файла выбрать строки, значения в поле N которых НЕ содержатся в поле M другого файла?

 , ,


0

2

Здравствуйте.

Есть два файла с разделителем полей |:

main.csv

Значение 11 | Значение 12 | Значение 13 | Значение 14 | Значение 15
Значение 21 | Значение 22 | Значение 23 | Значение 24 | Значение 25
Значение 31 | Значение 32 | Значение 33 | Значение 34 | Значение 35
Значение 41 | Значение 42 | Значение 43 | Значение 44 | Значение 45

input.csv

Значение 33 | Значение |
Значение ХХ | Значение |
....
Значение 13 | Значение |
Значение ХХ | Значение |

Как из main.csv получить файл, в поле 3 которого указаны значения, не встречающиеся в поле 1 файла input.csv, т.е.:

main_out.csv

Значение 21 | Значение 22 | Значение 23 | Значение 24 | Значение 25
Значение 41 | Значение 42 | Значение 43 | Значение 44 | Значение 45

Что-то не могу ничего придумать. Обычный sed или join вроде не подходят, на awk не знаю как реализовать.

Ответ на: комментарий от Yorween

А можно первую часть конструкции в awk'е пояснить? Т.е., про создание массива с индексом - значением поля я понял; интересует условие и переход.

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

Оригинальная конструкция. Сам придумал или подсмотрел у кого-то?

Я банально в блоке BEGIN читаю один файл в хеш и затем стандартно обрабатываю остальные файлы.

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

давай, рассказывай уже

NR==FNR

как он отличает файлы, передаваемые, через stdin?

a[$1];

получается, что массив заполняется из обоих файлов, и если

!($3 in a)

то он должен вывести хотя бы одну строку и из файла input.csv

или как?

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

Просто и элегантно!

Да ну нафиг. Юзать два sed-а, когда уже вызывается awk — охренеть элегантность.

awk -F'[ ][|][ ]?' '
        FNR==1 { fn++; }
        { if(fn==1) a[$1]; else if(!($3 in a)) print }
        ' input.csv main_out.csv

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

но ведь 80-е давно закончились. Почему бы не использовать простой и удобный инструмент, специально заточенный для работы с таблицами — sqlite. Разве не в этом состоит этот ваш unix-way? Один инструмент для одной задачи.

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

Почему бы не использовать простой и удобный инструмент

Может потому, что пока вы будете туда засовывать, а потом извлекать — проходит время, sql-подобное хорошо, когда надо извлекать несколько раз, а задачи бывают однократные и с небольшими данными, которые решаются просто и реально элегантно и быстрее даже на кофеварке.

vodz ★★★★★
()
Ответ на: комментарий от YAR
!($3 in a){print}

Для каждой строки из столбца 3 файла 2, если содержание отсутствует в массиве «а», напечатать содержание всей строки, содержащей столбец 3 из файла 2.

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

Да, согласен, можно было только авком. Мне лень было читать про регекспы при объявлении сепаратора, а решение в лоб -F" | " не работало, поэтому пихнул сэд.

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

anarquista ★★★★★ (14.03.2019 11:32:04)

но ведь 80-е давно закончились. Почему бы не использовать простой и удобный инструмент, специально заточенный для работы с таблицами — sqlite.

Тот под тысячу строк скрипт, частью которого является эта конструкция медленно и упорно переползает в postgres (ибо только хардкор, полумеры не наши методы). Ну а работать здесь и сейчас надо.

Yorween 13.03.2019 23:05:17

Оригинальная конструкция. Сам придумал или подсмотрел у кого-то?

Я банально в блоке BEGIN читаю один файл в хеш и затем стандартно обрабатываю остальные файлы.

Я пока сам ковырял, тоже соорудил считывание в awk в BEGIN хеша файла, добавление двух колонок с совпадающими значениями и потом грепание из результата в один файл строк с пустыми двумя колонками (не совпадает), в другой - с заполненными (совпадает).

А теперь пользую эту конструкцию, однозначно удобнее. Хотя и два sed'а, как выше отметили.

Но понять как она работает, мозгов не хватает.

Во-первых, если ее записать не в одну, а в несколько строк, работать перестает.

Во-вторых, если вместо

 <(sed 's/ | /|/g' input.csv) <(sed 's/ | /|/g' main.csv)
записать (у меня нет пробелов вокруг разделителя, поэтому убирать их нет необходимости, это я сдуру визуально его в примере выделил):
 <input.csv <main.csv
то работать перестает тоже. Оставил в скрипте в первозданном виде с sed'ами и в строку, обозначив для себя «необъяснимое колдунство».

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

Конструкция совершенно обычная. В мане awk и в книге awk programming поясняется, как сокращать длинные команды awk до более коротких следуя логике работы программы.

Yorween
()
Ответ на: комментарий от Neuro75
[/tmp] $ cat input.csv
Значение 33|Значение|
Значение ХХ|Значение|
Значение 13|Значение|
Значение ХХ|Значение|
[/tmp] $ cat main.csv
Значение 11|Значение 12|Значение 13|Значение 14|Значение 15
Значение 21|Значение 22|Значение 23|Значение 24|Значение 25
Значение 31|Значение 32|Значение 33|Значение 34|Значение 35
Значение 41|Значение 42|Значение 43|Значение 44|Значение 45
[/tmp] $ awk -F'|' 'NR==FNR {a[$1]; next}!($3 in a){print}' input.csv main.csv
Значение 21|Значение 22|Значение 23|Значение 24|Значение 25
Значение 41|Значение 42|Значение 43|Значение 44|Значение 45

GNU Awk 4.2.1

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

Да ну нафиг.

awk -F'[ ][|][ ]?' '
        FNR==1 { fn++; }
        { if(fn==1) a[$1]; else if(!($3 in a)) print }
        ' input.csv main_out.csv

а где входящий main.csv указать? Так тоже попробую.

Neuro75
() автор топика
Ответ на: комментарий от Yorween
awk -F'|' 'NR==FNR {a[$1]; next}!($3 in a){print}' input.csv main.csv

Спасибо

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

а где входящий main.csv указать?

Да там надо без «_out» в ваших именах.

Так тоже попробую.

Зачем? Это в точности тот же скрипт без sed-ов.

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

а решение в лоб -F" | " не работало,

Но документацию прочитать на awk не помешает. Когда FS начинается с пробела, то интепретируется как любой из символов, потому надо другой символ для включения режима регекcа.

vodz ★★★★★
()
Ответ на: комментарий от YAR
NR==FNR {a[$1]; next}

NR - количество записей (строк) во всех входящих файлах, FNR - количество записей (строк) в текущем обрабатываемом файле. Условие помогает разделить входящие файлы, чтобы обрабатывать их отдельно, т.е. если общее кол-во = текущему кол-ву, то все ок, мы еще в первом файле и можно обрабатывать дальше.

a[$1] - создается массив, где индексами служат значения из поля 1 первого входящего файла, т.к. это выражение относится к первому файлу.

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

Все, что идет дальше, начиная с «!» это условие и выражение для второго файла.

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

Спасибо. Еще немного сам поэтапно поразбирался, стала ясна механика всего этого. Но до написания подобного пока далеко, хотя awk более-менее активно использую :(

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