Объединение нескольких полей в текстовые файлы в 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