Объединение нескольких полей в текстовые файлы в Unix

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

Файл1 выглядит следующим образом:

foo 1 scaf 3 
bar 2 scaf 3.3

Файл2 выглядит следующим образом:

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

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

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

Ответ 1

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

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

Ответ 2

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

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

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

Однако, в качестве расширения 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

Неудачные попытки сделать соединение делают то, что он не сделает

<ы >   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

В 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 (только) - это не то, что требуется.

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

Ответ 3

Вот ответ правильный (с точки зрения использования стандартных инструментов GNU coreutils, а не для записи пользовательского script в 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

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

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

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

    $ 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". Мы делаем то же самое с файлом2. КСТАТИ. <file awk, является просто причудливым способом записи awk file или cat file | awk.

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

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

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

  • Последнее, что нужно объяснить объяснениям сопутствующих соединений: -j1 -o1.2,1.3,1.4,1.5,2.5

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

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

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

  • вам следует освоить пакет coreutils, эти инструменты очень эффективны в сочетании, и вам почти никогда не нужно писать специальную программу для работы с такими случаями,
  • Основные утилиты utils также быстро и сильно тестируются, поэтому они всегда являются лучшим выбором.

Ответ 4

Как насчет:

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

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

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

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

Ответ 5

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

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

Затем вы можете использовать join обычно в поле "1"

Ответ 6

Вот как это сделать в 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 $!;

Ответ 7

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

Ответ 8

Аналогичное решение, предложенное Джонатаном Леффлером.

Создайте 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

Ответ 9

Используя 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

Ответ 10

Простой метод (нет 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

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

  • cut создает список всех строк для поиска.
  • grep -f - коммутатор вводит строки из cut и ищет для них файлы File1 и File2.
  • 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