Виберіть рядки з текстового файлу, які мають ідентифікатори, перелічені в іншому файлі


13

Я використовую багато сорту grep awk у своїй оболонці unix для роботи із середніми розмірами (близько 10М-100М рядків) текстовими файлами стовпців, розділеними вкладками. У цьому відношенні оболонка unix - це моя таблиця.

Але у мене є одна величезна проблема - це вибір записів із переліком ідентифікаторів.

Маючи table.csvфайл із форматом id\tfoo\tbar...та ids.csvфайл із переліком ідентифікаторів, вибирайте лише записи table.csvіз наявного в ньому id ids.csv.

вид /programming/13732295/extract-all-lines-from-text-file-based-on-a-given-list-of-ids, але з оболонкою, а не perl.

grep -Fочевидно, створює помилкові позитиви, якщо ідентифікатори змінної ширини. joinце утиліта, яку я ніколи не міг зрозуміти. Перш за все, це вимагає алфавітного сортування (мої файли зазвичай сортуються за чисельністю), але навіть тоді я не можу змусити його працювати, не поскаржившись на неправильний порядок та пропустивши деякі записи. Тож мені це не подобається. grep -f проти файлу з ^id\t-s відбувається дуже повільно, коли кількість ідентифікаторів велика. awkгроміздкий.

Чи є для цього хороші рішення? Якісь інструменти для файлів, розділених вкладками? Додаткова функціональність теж буде вітатися.

UPD: виправлено sort->join


Якщо grep -fце занадто повільно, підтримка цієї стратегії звучить як більше проблем, ніж варто - зміни, швидше за все, стануть здобиччю до тих самих проблем роботи O (N * M). Можливо, ваш час було б краще витратити на вивчення нормалізованого SQL БД ...
goldilocks

1
Чому б не використати скрипт Perl з питання, яке ви зв'язали? Крім того, у ньому має бути можливість написати подібний сценарій awk.
cjm

Bash 4 має асоціативні масиви, для чого вам потрібно обійти вкладені петлі a la perl.
золотинок

1
sortможе робити всі види сортування, числові, алфавітні та інші. Див man sort.
terdon

У мене тут запит, як нам зробити те ж саме, якщо вихідний файл, з якого ми хочемо витягти дані, - це не обмежений файл

Відповіді:


19

Я думаю, ти мав на увазі це grep -fне так, grep -Fале насправді потрібна комбінація обох і -w:

grep -Fwf ids.csv table.csv

Причина, по якій ви отримували помилкові позитиви, - це, мабуть, ви не пояснили, оскільки якщо ідентифікатор може міститися в іншому, то обидва будуть надруковані. -wусуває цю проблему та -Fгарантує, що шаблони трактуються як рядки, а не регулярні вирази. Від man grep:

   -F, --fixed-strings
          Interpret PATTERN as a  list  of  fixed  strings,  separated  by
          newlines,  any  of  which is to be matched.  (-F is specified by
          POSIX.)
   -w, --word-regexp
          Select  only  those  lines  containing  matches  that form whole
          words.  The test is that the matching substring must  either  be
          at  the  beginning  of  the  line,  or  preceded  by  a non-word
          constituent character.  Similarly, it must be either at the  end
          of  the  line  or  followed by a non-word constituent character.
          Word-constituent  characters  are  letters,  digits,   and   the
          underscore.

   -f FILE, --file=FILE
          Obtain  patterns  from  FILE,  one  per  line.   The  empty file
          contains zero patterns, and therefore matches nothing.   (-f  is
          specified by POSIX.)

Якщо ваші помилкові позитиви пов’язані з тим, що ідентифікатор може бути присутнім у полі без ідентифікації, перейдіть натомість через ваш файл:

while read pat; do grep -w "^$pat" table.csv; done < ids.csv

або, швидше:

xargs -I {} grep "^{}" table.csv < ids.csv

Особисто я би це зробив, perlхоча:

perl -lane 'BEGIN{open(A,"ids.csv"); while(<A>){chomp; $k{$_}++}} 
            print $_ if defined($k{$F[0]}); ' table.csv

1
+1 Але: Що робити, якщо є потенційні помилкові позитиви, які точно відповідають слову id, тільки не в стовпці id? Якщо ви не можете використовувати ^з -F, ви не можете орієнтуватися на перший стовпець спеціально.
золотинки

@goldilocks якщо вони точно відповідають, вони не є хибними. Я розумію, що ви маєте на увазі, але в цьому випадку ОП повинен показати свої вхідні файли.
тердон

^id\tБіт з OP означає , idможе статися в іншій колонці. Якщо ні, це не має значення.
золотинки

Справедливий пункт @goldilocks, відповідь відредагована.
тердон

Ми використовували це для створення тимчасових файлів (використовуючи awk або sed), які додавали унікальний символ (скажімо, control-A), що обмежує поле, яке ми хотіли шукати, а потім використовуємо grep -F -f temppatternfile temptargetfile | tr -d '\ 001'
Марк Плотнік

7

joinУтиліта, що ви хочете. Для цього потрібні лексичні сортування вхідних файлів.

Припустимо, що ваша оболонка - bash або ksh:

join -t $'\t' <(sort ids.csv) <(sort table.csv)

Не потребуючи сортування, звичайне рішення awk

awk -F '\t' 'NR==FNR {id[$1]; next} $1 in id' ids.csv table.csv

Як я намагався, але в кінцевому підсумку не зміг передати, приєднання - це хитрість. Це не так добре працює для мене.
аламар

1
joinце не хитрість: ваші слова, якби ви не змогли це зрозуміти. Відкрий свій розум і вчися. Який результат ви отримали, і чим він відрізняється від очікуваного?
glenn jackman

+1, це робота для join.
don_crissti

Тут awkрішення дуже швидке та ефективне для моїх цілей (я витягую підмножини в декілька сотень з файлів ~ 100М рядків)
Лука

2

Відповіді на це питання допомогли мені обійти ніггери з приєднанням. По суті, сортуючи файл під час підготовки до надсилання до нього, вам потрібно переконатися, що ви сортуєте на основі стовпця, до якого ви приєднуєтесь. Отже, якщо це перше, вам потрібно сказати йому, що є символом роздільника у файлі, і що ви хочете його сортувати за першим полем (і лише першим полем). Інакше, якщо перше поле має змінну ширину (наприклад), ваші роздільники та, можливо, інші поля можуть почати впливати на порядок сортування.

Отже, використовуйте параметр -t сортування, щоб вказати свій розділовий символ, і використовуйте параметр -k, щоб вказати поле (пам’ятаючи, що вам потрібно поле початку та кінця - навіть якщо воно те саме - або він буде сортувати з цього символу до кінця рядка).

Отже, для файлу, розділеного на вкладки, як у цьому питанні, має працювати наступне (завдяки відповіді Glenn за структуру):

join -t$'\t' <(sort -d ids.csv) <(sort -d -t$'\t' -k1,1 table.csv) > output.csv

(Для довідки, -d прапор означає сортування словника. Ви також можете використовувати прапор -b для ігнорування провідних пробілів, див. man sortТа man join).

В якості більш загального прикладу, припустимо, ви приєднуєте два файли, розділені комами - input1.csvна третьому стовпці та input2.csvна четвертому. Ви можете використовувати

join -t, -1 3 -2 4 <(sort -d -t, -k3,3 input2.csv) <(sort -d -t, -k4,4 input2.csv) > output.csv

Тут -1і -2параметри вказують, до яких полів приєднатись у першому та другому вхідних файлах відповідно.


0

Ви також можете використовувати рубін, щоб зробити щось подібне:

ruby -pe 'File.open("id.csv").each { |i| puts i if i =~ /\$\_/ }' table.csv
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.