Объединение нескольких полей в текстовых файлах в Unix - PullRequest
12 голосов
/ 12 апреля 2010

Как я могу это сделать?

Файл1 выглядит так:

foo 1 scaf 3 
bar 2 scaf 3.3

File2 выглядит так:

foo 1 scaf 4.5
foo 1 boo 2.3
bar 2 scaf 1.00

Я хочу найти строки, которые встречаются в File1 и File2 когда поля 1,2 и 3 совпадают.

Есть ли способ сделать это?

Ответы [ 10 ]

13 голосов
/ 15 октября 2014

Вот правильный ответ (с точки зрения использования стандартных инструментов GNU coreutils , а не написания пользовательского сценария на perl / awk , как вы его назвали).

$ join -j1 -o1.2,1.3,1.4,1.5,2.5 <(<file1 awk '{print $1"-"$2"-"$3" "$0}' | sort -k1,1) <(<file2 awk '{print $1"-"$2"-"$3" "$0}' | sort -k1,1)
bar 2 scaf 3.3 1.00
foo 1 scaf 3 4.5

ОК, как это работает:

  1. Прежде всего мы будем использовать отличный инструмент join, который может объединить две строки. join имеет два требования:

    • Мы можем присоединиться только к одному полю.
    • Оба файла должны быть отсортированы по ключевому столбцу!
  2. Нам нужно сгенерировать ключей во входных файлах, и для этого мы используем простой awk скрипт:

    $ cat file1
    foo 1 scaf 3
    bar 2 scaf 3.3    
    
    $ <file1 awk '{print $1"-"$2"-"$3" "$0}'
    foo-1-scaf foo 1 scaf 3
    bar-2-scaf bar 2 scaf 3.3
    

    Видите ли, мы добавили 1-й столбец с некоторым ключом, например " foo-1-scaf ". Мы делаем то же самое с file2 . КСТАТИ. <file awk, это просто причудливый способ написания awk file или cat file | awk.

    Мы также должны отсортировать наши файлы по ключу, в нашем случае это столбец 1, поэтому мы добавим до конца команды | sort -k1,1 ( сортировка по тексту из столбца 1 в столбец 1)

  3. На данный момент мы можем просто сгенерировать файлы file1.with.key и file2.with.key и присоединиться к ним, но предположим, что эти файлы огромны, мы не хотим копировать их в файловую систему. Вместо этого мы можем использовать нечто под названием bash подстановка процесса для генерации вывода в именованный канал (это позволит избежать любых создание ненужного промежуточного файла). Для получения дополнительной информации, пожалуйста, прочитайте предоставленную ссылку.

    Наш целевой синтаксис: join <( some command ) <(some other command)

  4. Последнее, что нужно объяснить причудливые аргументы соединения: -j1 -o1.2,1.3,1.4,1.5,2.5

    • -j1 - объединить по ключу в 1-м столбце (в обоих файлах)
    • -o - выводить только эти поля 1.2 (1-е поле файла2), 1.3 (1-й столбец файла 3) и т. Д.

      Таким образом мы объединяем строки, но join выводит только необходимые столбцы.

Уроки, извлеченные из этого поста, должны быть:

  • вы должны освоить пакет coreutils , эти инструменты очень эффективны при объединении, и вам почти никогда не нужно писать собственную программу для работы с такими случаями,
  • core utils также работают быстро и тщательно протестированы, поэтому они всегда лучший выбор.
11 голосов
/ 12 апреля 2010

Команда объединения сложна в использовании и объединяет только один столбец

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

Следовательно, любое решение требует объединения столбцов в один столбец. Стандартная команда соединения также требует, чтобы ее входные данные были в правильном порядке сортировки - в соединении GNU (info coreutils join) есть замечание, что не всегда требуются отсортированные данные:

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

Один из возможных способов сделать это с указанными файлами:

awk '{printf("%s:%s:%s %s %s %s %s\n", $1, $2, $3, $1, $2, $3, $4);}' file1 |
sort > sort1
awk '{printf("%s:%s:%s %s %s %s %s\n", $1, $2, $3, $1, $2, $3, $4);}' file2 |
sort > sort2
join -1 1 -2 1 -o 1.2,1.3,1.4,1.5,2.5 sort1 sort2

Это создает составное поле сортировки в начале, используя ':' для разделения подполей, а затем сортирует файл - для каждого из двух файлов. Команда объединения затем объединяет два составных поля, но выводит только несоставные (не объединяющие) поля.

Вывод:

bar 2 scaf 3.3 1.00
foo 1 scaf 3 4.5

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

объединить -1 1 -2 1 -1 2 -2 2 -1 3 -2 3 -о 1.1,1.2,1.3,1.4,2.4 file1 file2

В MacOS X 10.6.3 это дает:

$ cat file1
foo 1 scaf 3 
bar 2 scaf 3.3
$ cat file2
foo 1 scaf 4.5
foo 1 boo 2.3
bar 2 scaf 1.00
$ join -1 1 -2 1 -1 2 -2 2 -1 3 -2 3 -o 1.1,1.2,1.3,1.4,2.4 file1 file2
foo 1 scaf 3 4.5 
bar 2 scaf 3.3 4.5 
$

Это присоединение к полю 3 (только) - это не то, что нужно.

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

4 голосов
/ 12 апреля 2010

вы можете попробовать это

awk '{
 o1=$1;o2=$2;o3=$3
 $1=$2=$3="";gsub(" +","")
 _[o1 FS o2 FS o3]=_[o1 FS o2 FS o3] FS $0
}
END{ for(i in _) print i,_[i] }' file1 file2

выход

$ ./shell.sh
foo 1 scaf  3 4.5
bar 2 scaf  3.3 1.00
foo 1 boo  2.3

Если вы хотите пропустить необычные строки

awk 'FNR==NR{
 s=""
 for(i=4;i<=NF;i++){ s=s FS $i }
 _[$1$2$3] = s
 next
}
{
  printf $1 FS $2 FS $3 FS
  for(o=4;o<NF;o++){
   printf $i" "
  }
  printf $NF FS _[$1$2$3]"\n"
 } ' file2 file1

выход

$ ./shell.sh
foo 1 scaf 3  4.5
bar 2 scaf 3.3  1.00
4 голосов
/ 12 апреля 2010

Вероятно, проще всего объединить первые три поля с awk:

awk '{print $1 "_" $2 "_" $3 " " $4}' filename

Тогда вы можете использовать join обычно в «поле 1»

3 голосов
/ 12 апреля 2010

Как насчет:

cat file1 file2
    | awk '{print $1" "$2" "$3}'
    | sort
    | uniq -c
    | grep -v '^ *1 '
    | awk '{print $2" "$3" "$4}'

Предполагается, что вас не слишком беспокоит пустое пространство между полями (другими словами, три табуляции и пробел ничем не отличаются от пробела и 7 табуляций). Это обычно тот случай, когда вы говорите о полях в текстовом файле.

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

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

Этот не будет работать должным образом, если два идентичных ключа находятся только в одном файле, поскольку файлы объединяются на ранней стадии. Другими словами, если у вас есть дубликаты ключей в file1, но не в file2, это будет ложное срабатывание.

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


И для тех, кто любит немного садомазохизма, вот вышеупомянутое не слишком эффективное решение:

cat file1
    | sed
        -e 's/ [^ ]*$/ "/'
        -e 's/ /  */g'
        -e 's/^/grep "^/'
        -e 's/$/ file2 | awk "{print \\$1\\" \\"\\$2\\" \\"\\$3}"/'
    >xx99
bash xx99
rm xx99

Этот файл создает отдельный файл сценария для выполнения работы. Для каждой строки в file1 в скрипте создается строка для поиска в file2. Если вы хотите посмотреть, как это работает, просто посмотрите на xx99, прежде чем удалить его.

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

Это , а не , что я бы сделал для кода производственного качества, но это хорошо для разового, при условии, что вы уничтожите все свидетельства до того, как Daily WTF узнает о это: -)

2 голосов
/ 08 июля 2016

Простой метод (без awk , join , sed или perl ) с использованием программных инструментов cut, grep и sort:

cut -d ' ' -f1-3 File1 | grep -h -f - File1 File2 | sort -t ' ' -k 1,2g

Вывод (не печатает несопоставленные строки):

bar 2 scaf 1.00
bar 2 scaf 3.3
foo 1 scaf 3 
foo 1 scaf 4.5

Как это работает ...

  1. cut составляет список всех строк для поиска.
  2. grep '-f - вводит строки из cut и ищет File1 и File2 для них.
  3. sort не требуется, но облегчает чтение данных.

Сокращенные результаты с datamash:

cut -d ' ' -f1-3 File1 | grep -h -f - File1 File2 | \
datamash -t ' ' -s -g1,2,3 collapse 4

Выход:

bar 2 scaf 3.3,1.00
foo 1 scaf 3,4.5

Если File1 огромен и несколько избыточен, добавление sort -u должно ускорить процесс:

cut -d ' ' -f1-3 File1 | sort -u | grep -h -f - File1 File2 | sort -t ' ' -k 1,2g
2 голосов
/ 12 апреля 2010

Вот способ сделать это в Perl:

#!/usr/local/bin/perl
use warnings;
use strict;
open my $file1, "<", "file1" or die $!;
my %file1keys;
while (<$file1>) {
    my @keys = split /\s+/, $_;
    next unless @keys;
    $file1keys{$keys[0]}{$keys[1]}{$keys[2]} = [$., $_];
}
close $file1 or die $!;
open my $file2, "<", "file2" or die $!;
while (<$file2>) {
    my @keys = split /\s+/, $_;
    next unless @keys;
    if (my $found = $file1keys{$keys[0]}{$keys[1]}{$keys[2]}) {
        print "Keys occur at file1:$found->[0] and file2:$..\n";
    }
}
close $file2 or die $!;
1 голос
/ 08 июля 2016

Использование datamash * свертывание операция, плюс немного косметики sort ing и tr ing:

cat File* | datamash -t ' ' -s -g1,2,3  collapse 4 | sort -g -k2 | tr ',' ' '

Вывод (общие строки имеют 5-е поле, необычные строки - нет):

foo 1 boo 2.3
foo 1 scaf 3 4.5
bar 2 scaf 3.3 1.00
1 голос
/ 21 августа 2015

Решение, подобное предложенному Джонатаном Леффлером.

Создайте 2 временных отсортированных файла с другим разделителем, в котором соответствующие столбцы объединены в первом поле. Затем присоедините временные файлы к первому полю и выведите второе поле.

$ cat file1.txt |awk -F" " '{print $1"-"$2"-"$3";"$0}' |sort >file1.tmp
$ cat file2.txt |awk -F" " '{print $1"-"$2"-"$3";"$0}' |sort >file2.tmp

$ join -t; -o 1.2 file1.tmp file2.tmp >file1.same.txt
$ join -t; -o 2.2 file1.tmp file2.tmp >file2.same.txt
$ rm -f file1.tmp file2.tmp

$ cat file1.same.txt
bar 2 scaf 3.3
foo 1 scaf 3

$ cat file2.same.txt
bar 2 scaf 1.00
foo 1 scaf 4.5
1 голос
/ 12 апреля 2010

Профессор, с которым я раньше работал, создал набор сценариев Perl, которые могут выполнять множество операций, подобных базам данных, над текстовыми файлами с ориентированными на столбцы. Это называется Fsdb . Он определенно может сделать это, и особенно стоит обратить внимание, если это не единственная необходимость (то есть вы не постоянно пишете собственные сценарии).

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