Вопрос или проблема
У нас есть следующий текстовый файл:
172.55.34.48 172.55.33.95
172.55.32.163 172.55.34.48
172.55.32.163 172.55.33.95
Что нам нужно сделать, так это поменять местами два столбца, если в первом столбце найдено значение, которое уже встречалось ранее в этом столбце. Для каждого такого значения (в данном случае это IP-адреса) максимальное количество повторений составляет два.
В приведённом примере нам нужно поменять 172.55.33.95
местами с 172.55.32.163
, так как 172.55.32.163
уже был найден в предыдущей строке.
Я попробовал
awk 'prev && ($1 != prev) {print seen[prev]} {seen[$1] = $0; prev = $1} END {print seen[$1]}' /tmp/new.txt
но это помогло лишь убрать строки, где значение в первом столбце уже встречалось ранее.
Ответ может быть таким простым, как эта программа awk:
$ awk '{found[$1]++; if (found[$1]>1) {buf=$1; $1=$2; $2=buf}} 1' input.txt
172.55.34.48 172.55.33.95
172.55.32.163 172.55.34.48
172.55.33.95 172.55.32.163
Это будет хранить количество вхождений каждого значения первого столбца в массиве found
, увеличивая соответствующее значение массива. Если значение оказывается больше одного, это означает, что текущее значение уже было встречено, и столбцы нужно поменять местами.
Для этого значение первого столбца сохраняется в буфере, заменяется значением второго столбца, а значение второго столбца в свою очередь заменяется сохранённым значением. В конце текущая строка выводится с учётом всех изменений (если таковые имеются), что и означает кажущийся “блуждающим” 1
.
Этот простой подход имеет один недостаток: если новое значение первого столбца (ранее второго) встречается в первом столбце снова на более поздней строке или уже встречалось на предыдущей строке, это не будет поймано. Улучшенная версия, которая учитывает появления на более поздних строках и пропускает строки, где замена также приведет к дублированию значения первого столбца, может выглядеть следующим образом:
awk '{f[$1]++; if (f[$1]>1) {if (f[$2]>0) {next}; buf=$1; $1=$2; $2=buf; f[$1]++; f[$2]--}} 1' input.txt
На Perl вы могли бы сделать:
perl -lae 'BEGIN {$, = " "} scalar(grep {$_ eq $F[0]} @buf) ? print(reverse(@F)) : do {print(@F); push(@buf, $F[0])}' input
-l
: автоматически убирает и добавляет разделитель полей по умолчанию (\n
) перед и после каждой оценки скрипта, переданного в-e
;-a
: автоматически разбивает каждую строку по разделителю полей ввода по умолчанию (
Скрипт установит разделитель выходных полей на
, затем для каждой строки он проверит, если первое поле уже хранится в @buf
.
Если это так, он развернёт текущую запись и напечатает её; в противном случае он напечатает текущую запись и добавит первое поле в @buf
.
% perl -lae 'BEGIN {$, = " "} scalar(grep {$_ eq $F[0]} @buf) ? print(reverse(@F)) : do {print(@F); push(@buf, $F[0])}' input
172.55.34.48 172.55.33.95
172.55.32.163 172.55.34.48
172.55.33.95 172.55.32.163
Ваши требования не совсем ясны, и ваш пример не охватывает все возможности того, что вы можете иметь в виду, но, используя любой awk, это реализует одну из возможных интерпретаций:
$ awk 'seen[$1]++{t=$2; $2=$1; $1=t} 1' file
172.55.34.48 172.55.33.95
172.55.32.163 172.55.34.48
172.55.33.95 172.55.32.163
или, возможно, вы хотите:
$ awk 'seen[$1]++{t=$2; $2=$1; $1=t; seen[$1]++} 1' file
172.55.34.48 172.55.33.95
172.55.32.163 172.55.34.48
172.55.33.95 172.55.32.163
или:
$ awk 'seen[$1]++{t=$2; $2=$1; $1=t; seen[$2]--; seen[$1]++} 1' file
172.55.34.48 172.55.33.95
172.55.32.163 172.55.34.48
172.55.33.95 172.55.32.163
или:
$ awk '$1==prev{t=$2; $2=$1; $1=t} {prev=$1} 1' file
172.55.34.48 172.55.33.95
172.55.32.163 172.55.34.48
172.55.33.95 172.55.32.163
Ответ или решение
Для решения задачи по обмену значениями в столбцах текстового файла, если встречается дублирующееся значение в первом столбце, можно использовать несколько подходов, включая языки программирования awk
и Perl
. Давайте рассмотрим оба подхода.
Решение на awk
Можно использовать следующую команду awk
, которая отслеживает количество вхождений каждого значения в первом столбце и в случае, если такое значение встречается повторно, выполняет обмен значениями в столбцах:
awk '{found[$1]++; if (found[$1]>1) {if (found[$2]>0) {next}; buf=$1; $1=$2; $2=buf; found[$1]++; found[$2]--}} 1' input.txt
Пояснение:
found[$1]++
— увеличиваем счетчик для текущего значения первого столбца.if (found[$1]>1)
— проверяем, встречалось ли это значение ранее.- Если значение во втором столбце (
$2
) уже встречалось, считаем, что нужно пропустить эту строку, чтобы избежать создания новых дублирующихся значений. - В противном случае, производим обмен значений в столбцах.
1
в конце командного блокаawk
заставляетawk
напечатать каждую строку, включая модифицированные.
Решение на Perl
В Perl
можно использовать следующий скрипт для реализации аналогичной логики:
perl -lae 'BEGIN {$, = " "} scalar(grep {$_ eq $F[0]} @buf) ? print(reverse(@F)) : do {print(@F); push(@buf, $F[0])}' input.txt
Пояснение:
BEGIN {$, = " "}
— устанавливает разделитель вывода на пробел.scalar(grep {$_ eq $F[0]} @buf)
— проверяет, встречается ли первое значение в массиве@buf
.- Если оно уже существует, выводится обратный порядок полей, а если нет, текущая строка печатается и значение добавляется в
@buf
.
Заключение
Оба предложенных подхода позволяют решить задачу обмена значениями в столбцах при обнаружении дублирующихся значений в первом столбце. Первый подход является более компактным и элегантным с помощью awk
, в то время как второй подход на Perl
может дать больше возможностей для расширений при необходимости. Выбор между ними зависит от ваших предпочтений и требований к языку программирования.
Применение этих решений гарантирует, что данные остаются последовательными и корректными, избегая создания новых дубликатов при обмене значениями.