Вопрос или проблема
На самом деле, это для кластера Redis. У меня есть 3 IP-адреса мастеров и 3 IP-адреса слейвов, нужно получить 3 пары IP-адресов, где адреса в паре находятся в разных подсетях /24.
Иными словами, given a list of n
IP-адресов (n
— четное число, и не более половины IP-адресов находятся в одной подсети /24
), как разделить их на пары n
/2, где два адреса в каждой паре находятся в разных подсетях /24?
Пары должны быть сохранены в виде ключей и значений ассоциативного массива ip_map
. Например, дан список IP-адресов, хранящийся в массиве $ips
:
ips=(
"172.211.91.63"
"172.211.90.61"
"172.211.91.30"
"172.211.90.173"
"172.211.89.233"
"172.211.89.166"
)
Результат может выглядеть следующим образом:
declare -A ip_map=(
[172.211.91.63]=172.211.90.61
[172.211.91.30]=172.211.89.233
[172.211.90.173]=172.211.89.166
)
Интуитивно я думаю, что если отсортировать ваши IP-адреса по ведрам, по одному для каждой подсети, и продолжать выбирать по 2 из 2 самых полных ведер, это должно позволить вам объединить их все. Это могло бы выглядеть так:
perl -e '
push @{$s{s/\.\d+$//r}}, $_ for @ARGV;
@l = values %s;
for ($n = @ARGV; $n > 0; $n -= 2) {
@l = sort {@$b <=> @$a} @l;
printf "ip_map[%s]=%s\n", pop(@{$l[0]}), pop(@{$l[1]});
}' -- "${ips[@]}"
Что на вашем примере дает:
ip_map[172.211.91.30]=172.211.89.166
ip_map[172.211.90.173]=172.211.91.63
ip_map[172.211.90.61]=172.211.89.233
code for @ARGV
перебирает параметры, переданные встроенному скрипту (выражение-e
), используя переменную по умолчанию$_
в качестве переменной цикла. Это более короткая формаfor (@ARGV) {code}
.s/\.\d+$//r
(которая действует по умолчанию на$_
) удаляет часть.<digits>
в конце, но с флагомr
результат возвращается вместо того, чтобы сохраняться обратно в$_
. Так что это расширяется до части подсети /24 IP-адреса.- В первой строке мы создаем ассоциативный массив
%s
(также известный как hash вperl
), где$s{subnet}
является ссылкой на список IP-адресов в этой подсети.@{that}
разыменовывает его, чтобы мы моглиpush
IP-адрес ($_
) в список. @l = values %s
: получает значения хеша, то есть ссылки на наши ведерные списки в@l
.- Итак, n/2 раз мы выбираем два IP-адреса из двух самых больших подсетей, сначала сортируя их по размеру (когда список используется в скалярном контексте, например в
@$b <=> @$a
, он расширяется до количества элементов в нем, поэтому мы сравниваем длину списков, чтобы их отсортировать) и затемpop
один из первого и второго списка (pop(@{$l[...]})
).
Вы можете оценить вывод в bash (с помощью source <(that-code)
или eval "$(that-code)"
), но вы могли бы также использовать perl
для всего этого, оболочки (особенно bash) не очень хороши в программировании.
Если бы мне пришлось использовать оболочку, я бы предпочел zsh вместо bash, где что-то эквивалентное могло бы выглядеть так:
typeset -A s ip_map
for ip ($ips) s[${ip%.*}]="x$s[${ip%.*}] $ip"
l=( $s )
repeat $#ips/2 {
l=( ${(O)l} )
ip_map[${l[1][(w)-1]}]=${l[2][(w)-1]}
l[1]=${l[1]#x} l[2]=${l[2]#x}
l[1]=${l[1]% *} l[2]=${l[2]% *}
}
Ассоциативные массивы zsh, как и массивы bash, не могут содержать списки, здесь мы храним список IP-адресов, разделенный пробелами (так что мы можем использовать индексирование на основе (w)
позже) в значениях ассоциативного массива и с префиксом строки из x
, использованной в качестве некоторой формы отметки для подсчета, по одной для каждого IP-адреса, так что это выглядит так:
typeset -A s=(
[172.211.89]='xx 172.211.89.233 172.211.89.166'
[172.211.90]='xx 172.211.90.61 172.211.90.173'
[172.211.91]='xx 172.211.91.63 172.211.91.30'
)
Таким образом, ${(O)l}
, которую мы используем для O
рганизации списка в обратном порядке, лексически сортирует по количеству элементов.
Ответ или решение
Для решения задачи, заключающейся в парном связывании IP-адресов из разных подсетей /24, можно использовать скрипт на Perl или Zsh. Этот подход потребует сначала группировать IP-адреса по их подсетям, а затем формировать пары из различных подсетей. Ниже приведено полное решение с примерами.
Шаги решения:
- Сборка входных данных: Создайте массив IP-адресов, который вам нужно будет обработать.
- Группировка адресов по подсетям: Разделите IP-адреса по первому трем октетам (класс /24).
- Формирование пар: Извлеките по одному IP-адресу из двух наиболее заполненных подсетей.
Пример реализации на Perl:
Вот пример скрипта на Perl, который выполняет указанные шаги.
#!/usr/bin/perl
use strict;
use warnings;
my @ips = (
"172.211.91.63",
"172.211.90.61",
"172.211.91.30",
"172.211.90.173",
"172.211.89.233",
"172.211.89.166"
);
# Хеш для группировки IP-адресов по подсетям
my %subnets;
# Группируем IP-адреса по подсетям /24
foreach my $ip (@ips) {
my $subnet = $ip; $subnet =~ s/\.\d+$//; # Удаление последнего октета
push @{$subnets{$subnet}}, $ip;
}
# Параметры для формирования пар
my @pairs;
while (scalar @ips > 0) {
# Сортировка подсетей по количеству IP-адресов
my @sorted_subnets = sort { scalar @{$subnets{$b}} <=> scalar @{$subnets{$a}} } keys %subnets;
# Выбор двух IP-адресов из двух наибольших подсетей
if (scalar @sorted_subnets < 2) {
die "Недостаточно подсетей для формирования пар.";
}
my $ip1 = pop @{$subnets{$sorted_subnets[0]}};
my $ip2 = pop @{$subnets{$sorted_subnets[1]}};
# Присоединяем IP-адреса в парах
push @pairs, "$ip1 => $ip2";
# Удаляем подсеть, если она пустая
delete $subnets{$sorted_subnets[0]} if scalar @{$subnets{$sorted_subnets[0]}} == 0;
delete $subnets{$sorted_subnets[1]} if scalar @{$subnets{$sorted_subnets[1]}} == 0;
}
# Вывод результата
foreach my $pair (@pairs) {
print "$pair\n";
}
Пример реализации на Zsh:
Теперь приведем аналогичное решение на Zsh.
#!/bin/zsh
# Объявляем массив IP-адресов
ips=(
"172.211.91.63"
"172.211.90.61"
"172.211.91.30"
"172.211.90.173"
"172.211.89.233"
"172.211.89.166"
)
# Объявляем ассоциативный массив для подсетей
typeset -A subnets
# Группировка IP-адресов по подсетям
for ip in $ips; do
subnet="${ip%.*}"
subnets[$subnet]+=" $ip"
done
# Список пар IP-адресов
pairs=()
# Формирование пар
while [ ${#ips[@]} -gt 0 ]; do
sorted_subnets=($(printf "%s\n" "${!subnets[@]}" | sort -n))
if [ ${#sorted_subnets[@]} -lt 2 ]; then
echo "Недостаточно подсетей для формирования пар."
exit 1
fi
ip1="${subnets[${sorted_subnets[1]}]%% *}" # Берём первый IP из первой подсети
ip2="${subnets[${sorted_subnets[0]}]%% *}" # Берём первый IP из второй подсети
pairs+=("$ip1 => $ip2")
# Удаляем IP из массивов
subnets[${sorted_subnets[1]}]="${subnets[${sorted_subnets[1]}]#* }"
subnets[${sorted_subnets[0]}]="${subnets[${sorted_subnets[0]}]#* }"
# Удаляем подсеть, если её IP-адреса исчерпаны
if [ -z "${subnets[${sorted_subnets[1]}]}" ]; then
unset subnets[${sorted_subnets[1]}]
fi
if [ -z "${subnets[${sorted_subnets[0]}]}" ]; then
unset subnets[${sorted_subnets[0]}]
fi
done
# Вывод результата
for pair in "${pairs[@]}"; do
echo "$pair"
done
Вывод
После выполнения любого из предложенных скриптов, вы получите пары IP-адресов, находящихся в разных подсетях /24. Эти скрипты обеспечивают эффективное группирование и парное связывание IP-адресов, что является важным для конфигурации кластеров Redis или других распределенных систем.
Если у вас есть дополнительные вопросы или требуется помощь в адаптации кода, не стесняйтесь спрашивать!