Вопрос или проблема
У меня есть два файла с разными форматами и колонками, разделенными табуляцией. Мне нужно сравнить столбцы column1
и column2
из file1
с file2
. Если они совпадают, мне нужно заменить значение в column6
file1
на значение в column3
file2
. Я пробовал использовать awk
, но не смог заменить значение. Можете ли вы дать совет по следующему коду?
awk 'FILENAME == ARGV[1] {
m[$1,$2] = $6;
next;
}
{
if (($1,$2) in m) {
m[$6]= $3; print m[$6];
}
}' file1 file2
первые строки файла1
1201 12011 1 0 0 0 1
1202 12021 1 0 0 0 1
1203 12031 1 0 0 0 1
1204 12041 1 0 0 0 2
1207 12071 1 0 0 0 2
1209 12091 1 0 0 0 1
1210 12101 1 0 0 0 1
1212 12121 1 0 0 0 1
1213 12131 1 0 0 0 1
1214 12141 1 0 0 0 2
первые строки файла2
1201 12011 1
1202 12021 1
1203 12031 1
1204 12041 1
1206 NA 1
1207 12071 2
1208 NA 1
1209 12091 2
1210 12101 2
Я хочу присвоить значения из файла2 в файл1, так как я хотел бы записать обновленное содержимое в другой файл out.txt
редактировать
Попробовал следующий код согласно комментариям
awk '{
if (FNR==NR) {
a[FNR]=$1;b[FNR]=$2;c[FNR]=$3}
else {
if (a[FNR] == $1 && b[FNR] ==$2) {
$6=c[FNR]} else {$6=$6};
print $0;
}
}' file2 file1
Получил следующий вывод
1201 12011 1 0 0 1 1
1202 12021 1 0 0 1 1
1203 12031 1 0 0 1 1
1204 12041 1 0 0 1 2
1207 12071 1 0 0 0 2
1209 12091 1 0 0 0 1
1210 12101 1 0 0 0 1
1212 12121 1 0 0 0 1
1213 12131 1 0 0 0 1
1214 12141 1 0 0 0 2
awk -F"\t" -v OFS="\t" '{
if (FNR==NR) {
a[FNR]=$1;b[FNR]=$2;c[FNR]=$3}
else {
if (a[FNR] == $1 && b[FNR] ==$2) {
$6=c[FNR]} else {$6=$6};
print $0
}
}' file2 file1
Я думаю, это выполнит задачу. Сначала он сохранит 1-й, 2-й и 3-й столбцы файла 2 в массивы a
, b
и c
соответственно с индексом FNR
, если FNR = NR
(это верно только для строк в файле2). В противном случае (это верно только для строк в файле1) происходит сравнение значений в массивах a
и b
с $1
и $2
. Если они совпадают, 6-е поле заменяется значением из массива c
, в противном случае оно сохраняет свое значение и печатает строку с разделителем табуляция
.
Альтернативный подход без использования awk
while read f1 f2 f3; do
sed -E -i "s/^($f1\s+$f2.*)([0-9]+)$/\1$f3/" file1;
done < file2
Предполагает, что последнее число – это целое. Если это не так, то ([0-9])$
нужно будет изменить на подходящую группу захвата.
Обратите внимание, что это зависит от изменения файла1 на месте, поэтому работайте с копией.
Смотря на ваш скрипт awk, вы правильно поняли об обработке каждого входного файла по-разному (хотя в большинстве случаев проверка NR == FNR
работает лучше и быстрее, потому что числовые сравнения быстрее строковых… что будет существенно, если в любом из файлов будет много тысяч входных строк).
Тем не менее, есть несколько проблем, которые мешают вашему скрипту делать то, что вам нужно.
-
Вы считываете файлы в неправильном порядке – так как вы хотите вывести строки из файла1 с замененным 6-м столбцом на 3-й столбец файла2, вам нужно сначала прочитать файл2, чтобы построить массив
m
(с индексами$1,$2
из файла2 и значениями$3
из файла2). -
Вы изменяете
m[$6]
и затем печатаете его. В лучшем случае это напечатает только один столбец.
Попробуйте что-то подобное:
$ awk 'NR == FNR { m[$1,$2] = $3; next }
($1,$2) in m { $6 = m[$1,$2] }
1' file2 file1
1201 12011 1 0 0 1 1
1202 12021 1 0 0 1 1
1203 12031 1 0 0 1 1
1204 12041 1 0 0 1 2
1207 12071 1 0 0 2 2
1209 12091 1 0 0 2 1
1210 12101 1 0 0 2 1
1212 12121 1 0 0 0 1
1213 12131 1 0 0 0 1
1214 12141 1 0 0 0 2
Кроме того, замените NR == FNR
в первой строке на ваш оригинальный FILENAME == ARGV[1]
.
Для каждой строки file2
(первый файл, который считывается – обратите внимание на порядок аргументов файлов в командной строке) она сохраняет $3
в массиве m
и переходит к следующей входной строке. Столбцы $1
и $2
используются в качестве индекса для массива.
Затем, при считывании file1
(и последующих входных файлов, если они есть), если ($1,$2)
является индексом в массиве, она заменяет $6
значением, хранящимся в массиве m
.
Затем она печатает текущую входную строку, независимо от того, была ли она изменена или нет (шаблон 1
сам по себе является идиомой/сокращением awk, означающим “напечатать текущую входную строку” – “1” оценивается как истинное, и действие по умолчанию – “напечатать”… таким образом, по сути “если истина истинна, то напечатать”)
Примечание: вы не предоставили образец ожидаемого вывода, поэтому я не могу проверить, соответствует ли вывод тому, что вам нужно (обычно мне нравится сохранять образец вывода OP в файл и использовать diff
или cmp
, чтобы убедиться, что мой вывод совпадает). Вывод моего скрипта awk соответствует моей интерпретации того, что вы спросили в своем вопросе.
Ответ или решение
Для решения задачи сравнения и замены значений между двумя файлами мы воспользуемся инструментом awk
. Рассмотрим более детально каждую часть этого подхода, чтобы обеспечить четкое понимание процесса.
Постановка задачи
Мы имеем два файла с данными, разделёнными табуляцией. Наша цель состоит в том, чтобы сравнить значения в первых двух колонках первого файла (file1
) со значениями в первых двух колонках второго файла (file2
). Когда нахождение совпадений будет установлено, необходимо заменить значение в шестой колонке file1
значением из третьей колонки file2
. В конце процесса результат будет записан в отдельный выходной файл (out.txt
).
Шаги решения
- Чтение данных из файлов: Сначала прочитаем данные из второго файла и сохраним их в массив для последующего доступа.
- Сравнение значений: Затем перейдём к ряду данных из первого файла и сравним значения с данными, уже сохранёнными в массиве.
- Замена и вывод: При нахождении совпадений, сделаем замену и выведем обновленные данные или оригинальные, если совпадений не будет.
Пример реализации через awk
Вот пример кода на awk
, который реализует описанные шаги:
awk -F"\t" -v OFS="\t" '
NR == FNR { m[$1, $2] = $3; next } # Сохраняем данные из file2 в массив m
($1, $2) in m { $6 = m[$1, $2] } # Замена значения в шестой колонке при совпадении
1 # Печать текущей строки
' file2 file1 > out.txt # Перенаправляем вывод в файл out.txt
Пояснение кода:
-F"\t"
устанавливает символ табуляции в качестве разделителя полей.-v OFS="\t"
устанавливает выходной разделитель на табуляцию, чтобы сохранить формат.NR == FNR
: Эта конструкция позволяет обрабатывать сначала первый файл (в данном случаеfile2
).m[$1, $2] = $3
: Создаём ассоциативный массив, где ключами являются значения из первой и второй колонок, а значениями — значения из третьей колонки.($1, $2) in m
: Проверяем, есть ли нахождение совпадений в массиве при чтении из второго файла.$6 = m[$1, $2]
: При совпадении заменяем значение в шестой колонке на соответствующее значение из массива.1
: Это сокращение дляprint
, которое выводит каждую строку (модифицированную или нет).
Дополнительные соображения
- Убедитесь, что вы работаете с копиями файлов, чтобы избежать потери данных.
- Использование этого подхода с
awk
является эффективным по времени, особенно при работе с большими файлами. - Если необходимо, вы можете дополнительно обрабатывать форматы файлов, добавляя функционал для обработки других разделителей или форматов данных.
Заключение
Данный подход позволяет легко сравнивать и заменять значения между двумя файлами с использованием мощного инструмента awk
. С приведённым скриптом вы сможете успешно решить задачу, обновив данные в file1
, основываясь на значения из file2
.