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?


1
Ви повинні встановити тільки IFSдля readтам:IFS=. read -a ArrIP<<<"$IP"
Муру

1
Не в bash без використання принаймні декількох змінних. Розширення одного параметра не може отримати другий або третій компоненти. Zsh може вкладати розширення параметрів, так що це можливо.
муру

@muru Не могли б ви надати рішення Zsh?
sci9

4
Яка у вас гарантія того, що ви завжди матимете справу з IP-адресами v4 і ніколи не матимете IP-адреси v6?
Мауг каже, що повернемо Моніку

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.

6

Я розумію, що ви спеціально попросили рішення, яке НЕ тимчасово переосмислити IFS, але у мене є солодке і просте рішення, яке ви не висвітлювали, так ось:

IFS=. ; set -- $IP

Це коротка команда поставить елементи вашого IP - адреси в оболонкових позиційних параметрів $1 , $2, $3, $4. Однак ви, ймовірно, захочете спочатку зберегти оригінал IFSта відновити його згодом.

Хто знає? Можливо, ви переглянете та приймете цю відповідь заради її стислості та ефективності.

(Це раніше було неправильно вказано як IFS=. set -- $IP)


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
поспішність зробила відходи, відповідь, мабуть, неправильна, і її слід виправити, оскільки це все ще приблизно так, як я би це зробив, лише з більшою кількістю коду.
Лізардкс

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 класу A), або 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, версія 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

Це неможливо в баші.


1
В zsh, ви можете віддати перевагу${${(s(.))ip}[3]}
Stéphane 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

2
Що таке "гра слонів"?
Wildcard

1
@Wildcard начебто еліптичний каламбур / жарт, це посилання як на сліпих людей, що описують історію слона, то так, можливо, деталі, щоб подивитися на те, що кожен буде мати власні сили, і на стародавній звичай царя, який хотів дарувати подарунки зі згубно дорогим обслуговуванням, пом'якшеним століттями, подарунками просто сумнівної цінності, які, як правило, регламентуються для розважальних цінностей. Обидва, здавалося, застосовуються тут :-)
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тут немає ), і назвала "pipe", і включає 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


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.