Bash: извлеките один из четырех разделов адреса IPv4

9

Мы можем использовать синтаксис ${var##pattern}и ${var%%pattern}извлечь последний и первый раздел адреса IPv4:

IP=109.96.77.15
echo IP: $IP
echo 'Extract the first section using ${var%%pattern}: ' ${IP%%.*}
echo 'Extract the last section using ${var##pattern}: ' ${IP##*.}

Как мы можем извлечь второй или третий раздел адреса IPv4, используя расширение параметра?

Вот мое решение: я использую массив и изменяю переменную IFS.

:~/bin$ IP=109.96.77.15
:~/bin$ IFS=. read -a ArrIP<<<"$IP"
:~/bin$ echo ${ArrIP[1]}
    96
:~/bin$ printf "%s\n" "${ArrIP[@]}"
    109
    96
    77
    15

Кроме того, я написал несколько решений с использованием awk, sedи cutкоманды.

Теперь мой вопрос: существует ли более простое решение, основанное на расширении параметров, которое не использует изменение массива и IFS?

sci9
источник
1
Вы должны только IFSдля readтам:IFS=. read -a ArrIP<<<"$IP"
Муру
1
Не в bash без использования как минимум нескольких переменных. Расширение с одним параметром не может получить второй или третий компоненты. Zsh может вкладывать расширения параметров, поэтому там это возможно.
Муру
@muru Не могли бы вы предоставить решение Zsh?
sci9
4
Какая у вас гарантия, что вы всегда будете иметь дело с адресами IP v4 и никогда не будете иметь адрес IP v6?
Mawg говорит восстановить Monica
4
Есть ли какая-то причина IFS=. read a b c d <<< "$IP", не приемлемая (если вы используете Bash, то есть)? Почему это должно быть сделано с расширением параметра?
ilkkachu

Ответы:

16

Предполагая значение IFS по умолчанию, вы извлекаете каждый октет в его собственную переменную с помощью:

read A B C D <<<"${IP//./ }"

Или в массив с:

A=(${IP//./ })
Джейсон Масгроув
источник
2
+1. Мне кажется, что это самый простой, самый простой метод, который соблюдает ограничения ОП.
Цифровая травма
7

Ваша постановка проблемы может быть немного более либеральной, чем вы предполагали. На риск использования лазейки, вот решение, на которое ссылается Муру :

first=${IP%%.*}
last3=${IP#*.}
second=${last3%%.*}
last2=${last3#*.}
third=${last2%.*}
fourth=${last2#*.}
echo "$IP -> $first, $second, $third, $fourth"

Это несколько неуклюже. Он определяет две одноразовые переменные и не может быть легко адаптирован для обработки большего количества разделов (например, для адреса MAC или IPv6).  Ответ Сергея Колодяжного вдохновил меня обобщить сказанное на это:

slice="$IP"
count=1
while [ "$count" -le 4 ]
do
    declare sec"$count"="${slice%%.*}"
    slice="${slice#*.}"
    count=$((count+1))
done

Это наборы sec1, sec2, sec3и sec4, которые могут быть проверены

printf 'Section 1: %s\n' "$sec1"
printf 'Section 2: %s\n' "$sec2"
printf 'Section 3: %s\n' "$sec3"
printf 'Section 4: %s\n' "$sec4"
  • whileЦикл должен быть легко понять - он перебирает четыре раза.
  • Сергий выбрал в sliceкачестве имени переменной , которая занимает место last3и last2в моем первом растворе (выше).
  • declare sec"$count"="value"это способ назначить sec1, sec2, sec3и , sec4 когда countэто 1, 2, 3и 4. Это немного похоже eval, но безопаснее.
  • Символ value, "${slice%%.*}"аналогичен значениям, которые присваивает мой исходный ответ first, secondи third.
G-Man говорит: «Восстанови Монику»
источник
6

Я действительно понимаю, что вы специально просили решение, которое НЕ было временно переопределено IFS, но у меня есть приятное и простое решение, которое вы не рассмотрели, поэтому вот так:

IFS=. ; set -- $IP

Это короткая команда поставит элементы вашего IP - адреса в оболочечных позиционных параметров $1 , $2, $3, $4. Тем не менее, вы, вероятно, захотите сначала сохранить оригинал, IFSа затем восстановить его.

Кто знает? Может быть, вы пересмотрите и примете этот ответ за его краткость и эффективность.

(Это было ранее неправильно указано как IFS=. set -- $IP)

user1404316
источник
5
Я не думаю, что это работает: если вы изменяете IFSв той же командной строке, новое значение не вступает в силу, когда переменные в той же командной строке раскрываются. То же, что и сx=1; x=2 echo $x
ilkkachu
6
@chepner, но опять же, setвообще не использует $IFS, $IFSиспользуется только для разделения слов $IP, но здесь он назначается слишком поздно, так что это не имеет никакого эффекта. Этот ответ в основном неверен. IP=109.96.77.15 bash -c 'IFS=. set -- $IP; echo "$2"'ничего не выводит ли в режиме POSIX или нет. Вам нужно IFS=. command eval 'set -- $IP', илиIFS=. read a b c d << "$IP"
Стефан Шазелас
4
Вероятно, это работает для вас, потому что вы установили IFS .в одном из ваших предыдущих тестов. Беги, IP=1.2.3.4 bash -xc 'IFS=. set $IP; echo "$2"'и ты увидишь, что это не работает. И посмотрите, IP=1.2.3.4 bash -o posix -xc 'IFS=. set $IP; echo "\$1=$1 \$2=$2 IFS=$IFS"'чтобы проиллюстрировать точку зрения @ chepner.
Стефан Шазелас
2
Поспешность действительно потратила впустую, ответ, по-видимому, неправильный, и должен быть исправлен, чтобы работать, так как это все равно примерно так, как я бы сделал, только с большим количеством кода.
Lizardx
3
@ user1404316, можете ли вы опубликовать точный набор команд, которые вы использовали для его проверки? (Возможно, плюс версия вашей оболочки.) В комментариях есть еще четыре пользователя, которые сказали, что она не работает так, как написано в ответе. С примерами.
ilkkachu
5

Не самый простой , но вы можете сделать что-то вроде:

$ IP=109.96.77.15
$ echo "$((${-+"(${IP//./"+256*("}))))"}&255))"
109
$ echo "$((${-+"(${IP//./"+256*("}))))"}>>8&255))"
96
$ echo "$((${-+"(${IP//./"+256*("}))))"}>>16&255))"
77
$ echo "$((${-+"(${IP//./"+256*("}))))"}>>24&255))"
15

Это должно работать в ksh93 (где , что ${var//pattern/replacement}оператор приходит от), bash4.3+, BusyBox sh, yash, mkshи zsh, хотя, конечно , в zsh, есть гораздо более простые подходы . В старых версиях bashвам нужно будет удалить внутренние кавычки. Это работает с теми внутренними кавычками, удаленными в большинстве других оболочек, но не ksh93.

Это предполагает, что $IPсодержит правильное четырехзначное представление адреса IPv4 (хотя это также будет работать для четырехугольных представлений типа 0x6d.0x60.0x4d.0xf(и даже восьмеричных в некоторых оболочках), но вывести значения в десятичном виде). Если содержимое $IPприходит из ненадежного источника, это равносильно уязвимости, связанной с внедрением команд.

В принципе, так как мы заменяем каждый .в $IPс +256*(, мы в конечном итоге оценка:

 $(( (109+256*(96+256*(77+256*(15))))>> x &255 ))

Таким образом , мы построения 32 разрядное целое число из этих 4 байта , как IPv4 - адрес в конечном счете , является (хотя и с байты в обратном порядке ) ¹ , а затем , используя >>, &битовые операторы для извлечения соответствующих байтов.

Мы используем ${param+value}стандартный оператор (здесь, $-который гарантированно будет всегда установлен), а не просто valueпотому, что в противном случае арифметический синтаксический анализатор будет жаловаться на несовпадающие скобки. Оболочка здесь может найти закрытие ))для открытия $((, а затем выполнить расширения внутри, что приведет к вычислению арифметического выражения.

С $(((${IP//./"+256*("}))))&255))вместо этого, оболочка будет рассматривать второй и третий )МЕЕТСЯ как закрытие ))для $((и сообщит об ошибке.

В ksh93 вы также можете сделать:

$ echo "${IP/@(*).@(*).@(*).@(*)/\2}"
96

bash, mksh, zsh Скопировали ksh93 игровую ${var/pattern/replacement}оператора , но не то, что обработка части захвата-группы. zshподдерживает его с другим синтаксисом:

$ setopt extendedglob # for (#b)
$ echo ${IP/(#b)(*).(*).(*).(*)/$match[2]}'
96

bashподдерживает некоторую форму обработки группы захвата в своем операторе сопоставления регулярных выражений , но не в ${var/pattern/replacement}.

POSIXly, вы бы использовали:

(IFS=.; set -o noglob; set -- $IP; printf '%s\n' "$2")

noglob, Чтобы избежать неприятных сюрпризов для значений , $IPкак 10.*.*.*, подоболочка ограничить сферу действия этих изменений параметров и $IFS.


Address IPv4-адрес - это всего лишь 32-разрядное целое число, а, например, 127.0.0.1 - это лишь одно из многих (хотя и наиболее распространенных) текстовых представлений. Тот же типичный IPv4-адрес интерфейса обратной связи также может быть представлен как 0x7f000001 или 127.1 (возможно, здесь более уместно сказать, что это 1адрес в сети класса 127.0 / 8), или 0177.0.1, или другими комбинациями 1 до 4 чисел, выраженных как восьмеричное, десятичное или шестнадцатеричное. Вы можете передать все это, pingнапример, и увидите, что все они будут пинговать localhost.

Если вы не возражаете против побочного эффекта установки произвольной временной переменной (здесь $n), в bashили ksh93или zsh -o octalzeroesили lksh -o posix, вы можете просто преобразовать все эти представления обратно в 32-битное целое число с помощью:

$((n=32,(${IP//./"<<(n-=8))+("})))

А затем извлеките все компоненты с помощью >>/ &комбинаций, как указано выше.

$ IP=0x7f000001
$ echo "$((n=32,(${IP//./"<<(n-=8))+("})))"
2130706433
$ IP=127.1
$ echo "$((n=32,(${IP//./"<<(n-=8))+("})))"
2130706433
$ echo "$((n=32,((${IP//./"<<(n-=8))+("}))>>24&255))"
127
$ perl -MSocket -le 'print unpack("L>", inet_aton("127.0.0.1"))'
2130706433

mkshиспользует 32-разрядные целые числа со знаком для своих арифметических выражений, вы можете использовать $((# n=32,...))их для принудительного использования 32-разрядных чисел без знака (и posixвозможность распознавать восьмеричные константы).

Стефан Шазелас
источник
Я понимаю большую концепцию, но я никогда не видел ${-+раньше. Я также не могу найти никакой документации по этому вопросу. Это работает, но мне просто любопытно подтвердить, это просто превратить строку в математическое выражение? Где я могу найти формальное определение? Кроме того, дополнительные цитаты в разделе замены расширения параметров не работают в GNU bash, версия 4.1.2 (2) -релиз CentOS 6.6. Я должен был сделать это вместо этогоecho "$((${-+"(${IP//./+256*(}))))"}>>16&255))"
Леви Узодике
1
@LeviUzodike, см. Редактировать.
Стефан Шазелас
4

С zsh вы можете вкладывать подстановки параметров:

$ ip=12.34.56.78
$ echo ${${ip%.*}##*.}
56
$ echo ${${ip#*.}%%.*}
34

Это невозможно в bash.

Мур
источник
1
В zsh, вы можете предпочесть${${(s(.))ip}[3]}
Стефан Chazelas
4

Конечно, давайте играть в игру со слоном.

$ ipsplit() { local IFS=.; ip=(.$*); }
$ ipsplit 10.1.2.3
$ echo ${ip[1]}
10

или

$ ipsplit() { local IFS=.; echo $*; }
$ set -- `ipsplit 10.1.2.3`
$ echo $1
10
jthill
источник
2
Что такое "игра в слонов"?
Wildcard
1
@Wildcard своего рода эллиптическая игра слов / шутка, это отсылка как к истории о слонах, описывающей слепых, так что есть части, на которые можно посмотреть, как каждый будет по-своему, и древний обычай короля, который хотел дарить подарки с разорительно дорогой содержание, размягчается в течение веков даров лишь сомнительной ценности , которые имеют тенденцию быть regifted как развлечение .. И , казалось применить здесь :-)
jthill
3

С IP=12.34.56.78.

IFS=. read a b c d <<<"$IP"

А также

#!/bin/bash
IP=$1

regex="(${IP//\./)\.(})"
[[ $IP =~ $regex ]]
echo "${BASH_REMATCH[@]:1}"

Описание:

Использование раскрытия параметра ${IP// }для преобразования каждой точки в ip в открывающую скобку, точку и закрывающую скобку. Добавляя начальные скобки и закрывающие скобки, мы получаем:

regex=(12)\.(34)\.(56)\.(78)

который создаст четыре круглых скобки для соответствия регулярному выражению в синтаксисе теста:

[[ $IP =~ $regex ]]

Это позволяет печатать массив BASH_REMATCH без первого компонента (все совпадения с регулярным выражением):

echo "${BASH_REMATCH[@]:1}"

Количество круглых скобок автоматически подбирается к соответствующей строке. Таким образом, это будет соответствовать MAC-адресу или EUI-64 IPv6-адреса, несмотря на то, что они имеют разную длину:

#!/bin/bash
IP=$1

regex="(${IP//:/):(})"
[[ $IP =~ $regex ]]
echo "${BASH_REMATCH[@]:1}"

Используй это:

$ ./script 00:0C:29:0C:47:D5
00 0C 29 0C 47 D5

$ ./script 00:0C:29:FF:FE:0C:47:D5
00 0C 29 FF FE 0C 47 D5
Исаак
источник
3

Вот небольшое решение, сделанное с POSIX /bin/sh(в моем случае это dash), функцией, которая многократно использует расширение параметров (поэтому IFSздесь нет ) и именованные каналы, и включает noglobопцию по причинам, упомянутым в ответе Стефана .

#!/bin/sh
set -o noglob
get_ip_sections(){
    slice="$1"
    count=0
    while [ -n "${slice}" ] && [ "$count" -ne 4 ]
    do
        num="${slice%%.*}"
        printf '%s ' "${num}"
        slice="${slice#*${num}.}"
        count=$((count+1))
    done
}

ip="109.96.77.15"
named_pipe="/tmp/ip_stuff.fifo"
mkfifo "${named_pipe}"
get_ip_sections "$ip" > "${named_pipe}" &
read sec1 sec2 sec3 sec4 < "${named_pipe}"
printf 'Actual ip:%s\n' "${ip}"
printf 'Section 1:%s\n' "${sec1}"
printf 'Section 3:%s\n' "${sec3}"
rm  "${named_pipe}"

Это работает так:

$ ./get_ip_sections.sh 
Actual ip:109.96.77.15
Section 1:109
Section 3:77

И с ipизменено на109.*.*.*

$ ./get_ip_sections.sh 
Actual ip:109.*.*.*
Section 1:109
Section 3:*

Счетчик циклов, состоящий из 4 итераций, учитывает 4 секции действительного адреса IPv4, в то время как акробатика с именованными каналами учитывает необходимость дальнейшего использования секций IP-адреса в скрипте, в отличие от того, что переменные застряли в подоболочке цикла.

Сергей Колодяжный
источник
Я изменил ваш ответ, чтобы он не использовал трубу, и добавил его в свой существующий ответ .
G-Man говорит: «Восстанови Монику»
0

Почему бы не использовать простое решение с awk?

$ IP="192.168.1.1" $ echo $IP | awk -F '.' '{ print $1" "$2" "$3" "$4;}'

Результат $ 192 168 1 1

Trường Giang Nguyễn
источник
-2
$ ip_=192.168.2.3

$ echo $ip_ 

192.168.2.3

$ echo $ip_ |cut -d "." -f 1

192
Бехруз Мохамади Насаб
источник
«... используя расширение параметров ...»
Джефф Шаллер
А где остальные три?
G-Man говорит: «Восстановите Монику»