Bash: Wyodrębnij jedną z czterech sekcji adresu IPv4

9

Możemy użyć składni ${var##pattern}i ${var%%pattern}wyodrębnić ostatnią i pierwszą sekcję adresu 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##*.}

Jak możemy wyodrębnić drugą lub trzecią sekcję adresu IPv4 za pomocą rozszerzenia parametrów?

Oto moje rozwiązanie: używam tablicy i zmieniam zmienną 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

Również Pisałem kilka rozwiązań, za pomocą awk, sedi cutpoleceń.

Teraz moje pytanie brzmi: czy istnieje prostsze rozwiązanie oparte na rozszerzaniu parametrów, które nie wykorzystuje zmiany tablicy i IFS?

sci9
źródło
1
Należy ustawić tylko IFSna readtam:IFS=. read -a ArrIP<<<"$IP"
Muru
1
Nie w bash bez użycia wielu zmiennych. Rozszerzenie pojedynczego parametru nie może uzyskać drugiego lub trzeciego komponentu. Zsh może zagnieżdżać rozszerzenia parametrów, więc może być to możliwe.
mur
@muru Czy możesz podać rozwiązanie Zsh?
sci9,
4
Jaką masz gwarancję, że zawsze będziesz mieć do czynienia z adresami IP v4 i nigdy nie będziesz mieć adresu IP v6?
Mawg mówi o przywróceniu Moniki
4
Czy istnieje jakiś powód, który IFS=. read a b c d <<< "$IP"jest nie do przyjęcia (jeśli używasz Bash, to znaczy)? Dlaczego trzeba to zrobić z rozszerzaniem parametrów?
ilkkachu

Odpowiedzi:

16

Zakładając domyślną wartość IFS, wyodrębniasz każdy oktet do własnej zmiennej za pomocą:

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

Lub do tablicy z:

A=(${IP//./ })
Jason Musgrove
źródło
2
+1. Wydaje mi się, że jest to najprostsza i najprostsza metoda zgodna z ograniczeniami PO.
Cyfrowa trauma
7

Twoje zgłoszenie problemu może być nieco bardziej liberalne niż zamierzałeś. Ryzykując lukę, oto rozwiązanie, które nawiązywało do :

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

To jest trochę niezgrabne. Definiuje dwie zmienne usuwania i nie jest łatwo przystosowane do obsługi większej liczby sekcji (np. Dla adresu MAC lub IPv6).  Odpowiedź Siergieja Kolodyazhnyya zainspirowała mnie do uogólnienia powyższego na to:

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

To określa sec1, sec2, sec3i sec4, które mogą być weryfikowane

printf 'Section 1: %s\n' "$sec1"
printf 'Section 2: %s\n' "$sec2"
printf 'Section 3: %s\n' "$sec3"
printf 'Section 4: %s\n' "$sec4"
  • whilePętli powinny być łatwe do zrozumienia - to iteracje czterokrotnie.
  • Sergiy wybraliśmy slicejako nazwę zmiennej, która zajmuje miejsce last3i last2w moim pierwszym roztworze (powyżej).
  • declare sec"$count"="value"Jest to sposób przypisać do sec1, sec2, sec3i sec4 kiedy countjest 1, 2, 3i 4. To trochę jak eval, ale bezpieczniejsze.
  • value, "${slice%%.*}"Jest analogiczna do wartości mojego pierwotnego przypisuje odpowiedź na first, secondi third.
G-Man mówi „Przywróć Monikę”
źródło
6

Zdaję sobie sprawę, że konkretnie poprosiłeś o rozwiązanie, które NIE zostało tymczasowo na nowo zdefiniowane IFS, ale mam słodkie i proste rozwiązanie, którego nie omówiłeś, więc oto:

IFS=. ; set -- $IP

Że krótka komenda będzie umieścić elementy adresu IP w powłoce za parametrów pozycyjnych $1 , $2, $3, $4. Najprawdopodobniej jednak będziesz chciał najpierw zapisać oryginał, IFSa potem go przywrócić.

Kto wie? Może ponownie rozważysz i zaakceptujesz tę odpowiedź ze względu na jej zwięzłość i skuteczność.

(To było wcześniej niepoprawnie podane jako IFS=. set -- $IP)

użytkownik1404316
źródło
5
Nie sądzę, żeby to działało: jeśli zmienisz IFSw tym samym wierszu poleceń, nowa wartość nie zadziała, gdy zmienne w tym samym wierszu poleceń zostaną rozwinięte. Tak samo jak zx=1; x=2 echo $x
ilkkachu
6
@chepner, ale znowu, w setogóle nie korzysta $IFS, $IFSjest używany tylko do dzielenia słów na $IP, ale tutaj jest przypisany za późno, więc nie ma to żadnego efektu. Ta odpowiedź jest zasadniczo błędna. IP=109.96.77.15 bash -c 'IFS=. set -- $IP; echo "$2"'nie wyświetla nic, czy jest w trybie POSIX, czy nie. Potrzebujesz IFS=. command eval 'set -- $IP', lubIFS=. read a b c d << "$IP"
Stéphane Chazelas
4
Prawdopodobnie to działa, ponieważ ustawiłeś IFS na .jeden z poprzednich testów. Uruchom, IP=1.2.3.4 bash -xc 'IFS=. set $IP; echo "$2"'a zobaczysz, że to nie działa. I IP=1.2.3.4 bash -o posix -xc 'IFS=. set $IP; echo "\$1=$1 \$2=$2 IFS=$IFS"'zilustrujmy punkt @ chepnera.
Stéphane Chazelas
2
pośpiesz się marnowałem, odpowiedź jest najwyraźniej błędna i powinna zostać poprawiona do pracy, ponieważ nadal jest to mniej więcej tak, jak bym to zrobił, tylko z większym kodem.
Lizardx
3
@ user1404316, czy możesz opublikować dokładny zestaw poleceń użytych do jego przetestowania? (Być może plus wersję twojej powłoki.) Jest czterech innych użytkowników, którzy powiedzieli ci tutaj w komentarzach, że nie działa tak, jak napisano w odpowiedzi. Z przykładami.
ilkkachu
5

Nie najłatwiej , ale możesz zrobić coś takiego:

$ 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

To powinno działać w ksh93 (jeżeli ${var//pattern/replacement}operator pochodzi), bash4.3+, BusyBox sh, yash, mkshi zsh, choć oczywiście w zshnie są o wiele prostsze sposoby . W starszych wersjach bashtrzeba by usunąć wewnętrzne cytaty. Działa z tymi wewnętrznymi cudzysłowami usuniętymi również w większości innych powłok, ale nie w ksh93.

Zakłada się, że $IPzawiera prawidłową reprezentację adresu IPv4 w postaci dziesiętno-dziesiętnej (choć działałoby to również w przypadku reprezentacji cztero-szesnastkowych, takich jak 0x6d.0x60.0x4d.0xf(a nawet ósemkowa w niektórych powłokach), ale wyświetlałby wartości dziesiętnie). Jeśli zawartość $IPpochodzi z niezaufanego źródła, oznacza to lukę w zabezpieczeniach polegającą na wstrzykiwaniu poleceń.

Zasadniczo, jak jesteśmy zastępując każdy .w $IPz +256*(, skończymy oceny:

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

Więc jesteśmy konstruowania 32 bitową liczbę całkowitą z tych 4 bajtów, takich jak adres IPv4 ostatecznie jest (choć z bajty odwrócone) ¹, a następnie przy użyciu >>, &bitowe operatorów wyodrębnić odpowiednie bajty.

Używamy ${param+value}standardowego operatora (tutaj $-zawsze gwarantuje się ustawienie) zamiast tego, valueponieważ w przeciwnym razie parser arytmetyczny narzekałby na niedopasowany nawias. Powłoka tutaj może znaleźć zamknięcie ))dla otwarcia $((, a następnie wykonać rozszerzenia wewnątrz, które spowodują wyrażenie arytmetyczne do oceny.

Dzięki $(((${IP//./"+256*("}))))&255))zamiast, powłoka potraktuje drugi i trzeci )s tam jako zamknięcie ))dla $((i zgłosić błąd składni.

W ksh93 możesz także:

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

bash, mksh, zsh Zostały skopiowane ksh93 męska ${var/pattern/replacement}operatora, ale nie, że grupa przechwytywania obsługi część. zshobsługuje go z inną składnią:

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

bashobsługuje pewną formę obsługi grup przechwytywania w operatorze dopasowania wyrażeń regularnych , ale nie w ${var/pattern/replacement}.

POSIXly użyłbyś:

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

noglobAby uniknąć złych niespodzianek dla wartości $IPjak 10.*.*.*, podpowłoki aby ograniczyć zakres tych zmian do opcji i $IFS.


¹ Adres IPv4 to tylko 32-bitowa liczba całkowita, a na przykład 127.0.0.1 to tylko jedna z wielu (choć najczęstszych) reprezentacji tekstowych. Ten sam typowy adres IPv4 interfejsu sprzężenia zwrotnego można również przedstawić jako 0x7f000001 lub 127.1 (być może bardziej odpowiedni tutaj, aby powiedzieć, że jest to 1adres w sieci klasy 127.0 / 8 klasy A) lub 0177.0.1, lub inne kombinacje 1 do 4 liczb wyrażonych jako liczba ósemkowa, dziesiętna lub szesnastkowa. Możesz przekazać je wszystkim, pingna przykład, a zobaczysz, że wszystkie będą pingować localhost.

Jeśli nie przeszkadza ci efekt uboczny ustawiania dowolnej zmiennej tymczasowej (tutaj $n), w bashlub ksh93lub zsh -o octalzeroeslub lksh -o posix, możesz po prostu przekonwertować wszystkie te reprezentacje z powrotem na 32-bitową liczbę całkowitą za pomocą:

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

A następnie wyodrębnij wszystkie składniki za pomocą >>/ &kombinacji takich jak powyżej.

$ 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

mkshużywa 32-bitowych liczb całkowitych ze znakiem dla wyrażeń arytmetycznych, można $((# n=32,...))tam użyć , aby wymusić użycie 32-bitowych liczb bez znaku (i posixopcję rozpoznawania stałych ósemkowych).

Stéphane Chazelas
źródło
Rozumiem wielką koncepcję, ale nigdy wcześniej jej nie widziałem ${-+. Nie mogę też znaleźć żadnej dokumentacji na ten temat. Działa, ale jestem ciekawy, czy mogę zmienić ciąg w wyrażenie matematyczne? Gdzie mogę znaleźć formalną definicję? Ponadto dodatkowe cytaty w sekcji zastępowania rozszerzenia parametrów nie działają w GNU bash, wersja 4.1.2 (2) - wydanie CentOS 6.6. Musiałem to zrobić zamiast tegoecho "$((${-+"(${IP//./+256*(}))))"}>>16&255))"
Levi Uzodike
1
@LeviUzodike, patrz edycja.
Stéphane Chazelas
4

Zsh umożliwia zagnieżdżanie podstawień parametrów:

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

Nie jest to możliwe w bash.

muru
źródło
1
W zsh, może wolisz${${(s(.))ip}[3]}
Stéphane Chazelas
4

Jasne, zagrajmy w grę słoni.

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

lub

$ ipsplit() { local IFS=.; echo $*; }
$ set -- `ipsplit 10.1.2.3`
$ echo $1
10
jthill
źródło
2
Jaka jest „gra słoni”?
Wildcard
1
@Wildcard, rodzaj eliptycznego kalambura / żartu, odnosi się zarówno do niewidomych opisujących historię słonia, więc mogą być części, aby spojrzeć na każdego, kto ma własne zdanie, i na starożytny zwyczaj króla, który chciał dawanie prezentów ze straszliwie kosztownymi kosztami utrzymania, zmiękczonymi przez stulecia, do prezentów o wątpliwej wartości, które zwykle odzyskuje się dla rozrywki. Oba wydawały się mieć zastosowanie tutaj :-)
jthill 30.01.18
3

Z IP=12.34.56.78.

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

I

#!/bin/bash
IP=$1

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

Opis:

Wykorzystanie rozszerzenia parametru ${IP// }do przekształcenia każdej kropki w ip w nawias otwierający kropkę i nawias zamykający. Dodając nawias początkowy i nawias zamykający, otrzymujemy:

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

który utworzy cztery nawiasy przechwytujące dla dopasowania wyrażenia regularnego w składni testowej:

[[ $IP =~ $regex ]]

To pozwala na wydrukowanie tablicy BASH_REMATCH bez pierwszego komponentu (całe dopasowanie wyrażenia regularnego):

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

Ilość nawiasów jest automatycznie dostosowywana do dopasowanego ciągu. Będzie to więc pasowało do adresu MAC lub EUI-64 adresu IPv6, mimo że mają różną długość:

#!/bin/bash
IP=$1

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

Użyj tego:

$ ./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
Izaak
źródło
3

Oto małe rozwiązanie wykonane przy pomocy POSIX /bin/sh(w moim przypadku to dash), funkcja, która wielokrotnie wykorzystuje rozszerzanie parametrów (więc nie IFStutaj), i nazwane potoki oraz zawiera noglobopcję z powodów wymienionych w odpowiedzi Stephane'a .

#!/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}"

Działa to tak:

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

I ipzmieniono na109.*.*.*

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

Pętla utrzymująca licznik 4 iteracji odpowiada 4 sekcjom prawidłowego adresu IPv4, natomiast akrobatyka z nazwanymi potokami wymaga dalszego używania sekcji adresu IP w skrypcie, w przeciwieństwie do blokowania zmiennych w podpowłoce pętli.

Sergiy Kolodyazhnyy
źródło
Zmodyfikowałem twoją odpowiedź, aby nie używała potoku i dodałem ją do mojej istniejącej odpowiedzi .
G-Man mówi „Przywróć Monikę”
0

Dlaczego nie skorzystać z prostego rozwiązania z awk?

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

Wynik $ 192 168 1 1

Trường Giang Nguyễn
źródło
-2
$ ip_=192.168.2.3

$ echo $ip_ 

192.168.2.3

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

192
Behrooz Mohamadi nasab
źródło
„... przy użyciu rozszerzenia parametrów ...”
Jeff Schaller
A gdzie są pozostałe trzy?
G-Man mówi „Przywróć Monikę”