Bash: IPv4 adresinin dört bölümünden birini çıkarın


9

Sözdizimini ${var##pattern}ve ${var%%pattern}bir IPv4 adresinin son ve ilk bölümünü ayıklamak için kullanabiliriz :

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##*.}

Parametre genişletme kullanarak bir IPv4 adresinin ikinci veya üçüncü bölümünü nasıl ayıklayabiliriz?

İşte benim çözüm: Ben bir dizi kullanın ve IFS değişkeni değiştirin.

:~/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

Ayrıca ben kullanarak bazı çözümler yazdım awk, sedve cutkomutları.

Şimdi, sorum şu: Dizi ve IFS değiştirmeyi kullanmayan parametre genişletmeye dayalı daha basit bir çözüm var mı ?


1
Yalnızca belirlesin IFSiçin read: oradaIFS=. read -a ArrIP<<<"$IP"
Muru

1
En azından birden fazla değişken kullanmadan bash içinde değil. Tek bir parametre genişletmesi ikinci veya üçüncü bileşenleri alamaz. Zsh, parametre genişletmelerini iç içe yerleştirebilir, bu yüzden orada mümkün olabilir.
muru

@muru Zsh çözümünü sunabilir misiniz?
sci9

4
Her zaman IP v4 adresleriyle uğraşacağınız ve hiçbir zaman IP v6 adresiniz olmayacağının garantisi nedir?
Mawg, Monica

4
Kabul IFS=. read a b c d <<< "$IP"edilemez bir neden var mı (Bash kullanıyorsanız)? Neden parametre genişletme ile yapılması gerekiyor?
ilkkachu

Yanıtlar:


16

Varsayılan IFS değeri varsayıldığında, her sekizliyi kendi değişkenine aşağıdakilerle çıkarırsınız:

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

Veya aşağıdakileri içeren bir diziye:

A=(${IP//./ })

2
+1. Bana öyle geliyor ki bu OP'lerin kısıtlamalarına uyan en basit, en basit yöntem.
Dijital Travma

7

Sorun ifadeniz istediğinizden biraz daha liberal olabilir. Bir boşluktan yararlanma riski altında, burada muru için önerilen çözüm :

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

Bu biraz tıknaz. İki boşaltma değişkenini tanımlar ve daha fazla bölümü işlemek için kolayca uyarlanmamıştır (örneğin bir MAC veya IPv6 adresi için).  Sergiy Kolodyazhnyy'nin cevabı , yukarıdakileri genelleştirmek için bana ilham verdi:

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

Bu kümeler sec1, sec2, sec3ve sec4, ile doğrulanabilir hangi

printf 'Section 1: %s\n' "$sec1"
printf 'Section 2: %s\n' "$sec2"
printf 'Section 3: %s\n' "$sec3"
printf 'Section 4: %s\n' "$sec4"
  • whileDöngü anlaşılması kolay olmalı - bu dört kez yinelenir.
  • Sergiy seçti sliceyerini alan bir değişken adı olarak last3ve last2ilk çözelti (yukarıda).
  • declare sec"$count"="value"atamak için bir yoldur sec1, sec2, sec3ve sec4 ne zaman countolduğunu 1, 2, 3ve 4. Biraz benziyor evalama daha güvenli.
  • value, "${slice%%.*}", Benim özgün yanıt devrettikleri değerleri benzerdir first, secondve third.

6

Özellikle geçici olarak yeniden tanımlamamış bir çözüm istediğini fark ettim IFS, ama kapsamadığın tatlı ve basit bir çözümüm var, işte gidiyor:

IFS=. ; set -- $IP

Bu kısa komut kabuğun içinde IP adresinin unsurları koyacağız konumsal parametreler $1 , $2, $3, $4. Ancak, muhtemelen orijinali önce kaydetmek IFSve daha sonra geri yüklemek istersiniz .

Kim bilir? Belki de bu cevabı kısaca ve etkinliği için yeniden düşünecek ve kabul edeceksiniz.

(Bu daha önce yanlış olarak verildi IFS=. set -- $IP)


5
Çalıştığını sanmıyorum: IFSaynı komut satırında değişiklik yaparsanız , aynı komut satırındaki değişkenler genişletildiğinde yeni değer etkili olmaz. x=1; x=2 echo $x
İle

6
@chepner, ama tekrar setkullanımını yapmaz $IFShiç, $IFSsadece kelime bölme için kullanılır $IP, ama burada hiçbir etkisi yoktur bu yüzden, çok geç atanır. Bu cevap temel olarak yanlıştır. IP=109.96.77.15 bash -c 'IFS=. set -- $IP; echo "$2"'POSIX modunda olsun veya olmasın hiçbir şey çıktılamaz. İhtiyacınız IFS=. command eval 'set -- $IP'olan veyaIFS=. read a b c d << "$IP"
Stéphane Chazelas

4
Muhtemelen sizin için işe yarar çünkü IFS'yi .önceki testlerinizden birine ayarlamışsınızdır . Koş IP=1.2.3.4 bash -xc 'IFS=. set $IP; echo "$2"'ve işe yaramadığını göreceksin. Ve IP=1.2.3.4 bash -o posix -xc 'IFS=. set $IP; echo "\$1=$1 \$2=$2 IFS=$IFS"'@ chepner'ın amacını açıklamaya bakın .
Stéphane Chazelas

2
acele atık yaptı, cevap görünüşte yanlış ve çalışmak için düzeltilmelidir, çünkü hala kabaca nasıl yapacağımı, sadece daha fazla kod ile.
Lizardx

3
@ user1404316, test etmek için kullandığınız komutların tam setini gönderebilir misiniz? (Ayrıca belki de kabuğunuzun sürümü.) Burada yanıtta yazıldığı gibi çalışmadığını söyleyen dört kullanıcı daha var. Örneklerle.
ilkkachu

5

En kolay değil , ancak şöyle bir şey yapabilirsiniz:

$ 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

(Yani bu noktada ksh93 içinde çalışması gerekir ${var//pattern/replacement}operatör gelir), bash4.3+, busybox sh, yash, mkshve zshtabii gerçi de zsh, çok daha basit yaklaşım vardır . Eski sürümlerinde bash, iç tırnakları kaldırmanız gerekir. Diğer kabuklarda da kaldırılan bu iç tırnaklarla çalışır, ancak ksh93 ile çalışmaz.

Bu varsayım $IP, bir IPv4 adresinin geçerli bir dört ondalık gösterimini içerir (ancak bu, aynı zamanda 0x6d.0x60.0x4d.0xf(hatta bazı kabuklarda sekizlik) gibi dörtlü onaltılık gösterimler için de işe yarar, ancak değerleri ondalık olarak çıktılar). İçeriğinin $IPgüvenilmeyen bir kaynaktan gelmesi, komut enjeksiyon güvenlik açığı anlamına gelir.

Temel olarak, biz her değiştiriyorsanız olarak .yer $IPile +256*(biz değerlendirerek sonunda:

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

Biz bir IPv4 adresi gibi bu 4 bayt dışında 32 bitlik tamsayı inşa ediyoruz Yani sonuçta (bayt ters olsa birlikte) 'dir ¹ ve daha sonra kullanarak >>, &ilgili bayt ayıklamak için bitsel operatörleri.

Aksi takdirde aritmetik ayrıştırıcı eşleşmeyen parantezden şikayetçi olacağı için ${param+value}standart operatörü (burada $-her zaman ayarlanması garanti edilen) kullanırız value. Buradaki kabuk )), açıklığın kapanışını bulabilir $((ve daha sonra değerlendirilecek aritmetik ifadeye neden olacak şekilde genişletmeleri gerçekleştirebilir.

Bunun $(((${IP//./"+256*("}))))&255))yerine, kabuk )orada ikinci ve üçüncü s'yi kapanış ))olarak ele alır $((ve bir sözdizimi hatası bildirir.

Ksh93'te şunları da yapabilirsiniz:

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

bash, mksh, zsh Ksh93 en kopyaladığınız ${var/pattern/replacement}operatörü ama Yakalama-grup kısmını ele değil. zshfarklı bir sözdizimi ile destekler:

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

bashregexp eşleştirme işlecinde bir tür yakalama grubu işlemeyi destekliyor , ancak içeri almıyor ${var/pattern/replacement}.

POSIXly, şunu kullanırsınız:

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

noglobDeğerleri için kötü sürprizlerden kaçınmak için $IPböyle 10.*.*.*altkabuk seçenekleri ve söz konusu değişikliklerin kapsamını sınırlamak için, $IFS.


IP IPv4 adresi yalnızca 32 bit tam sayıdır ve örneğin 127.0.0.1 (en yaygın olsa da) metinsel gösterimlerden yalnızca biridir. Geridöngü arabiriminin aynı tipik IPv4 adresi 0x7f000001 veya 127.1 (belki 1de 127.0 / 8 sınıf A ağındaki adres olduğunu söylemek için daha uygun bir adres) veya 0177.0.1 veya 1'in diğer kombinasyonları olarak temsil edilebilir. sekizlik, ondalık veya onaltılı olarak ifade edilen 4 sayıya kadar. Bunların tümünü pingörneğin aktarabilirsiniz ve hepsinin yerel ana bilgisayara ping atacağını görürsünüz.

İsterseniz geçici bir değişken (burada $n), bashya ksh93da zsh -o octalzeroesya da ya da ayarlamanın yan etkisine dikkat lksh -o posixetmezseniz, tüm bu gösterimleri aşağıdaki şekilde 32 bit tam sayıya dönüştürebilirsiniz:

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

Ve sonra yukarıdaki gibi >>/ &kombinasyonları ile tüm bileşenleri çıkarın .

$ 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

mksharitmetik ifadeleri için imzalı 32 bit tamsayıları kullanırsa, $((# n=32,...))burada imzasız 32 bit sayılarının kullanımını (ve posixsekizli sabitleri tanıma seçeneği) zorlamak için kullanabilirsiniz .


Büyük konsepti anlıyorum, ama daha önce hiç görmedim ${-+. Üzerinde herhangi bir belge de bulamıyorum. İşe yarıyor, ama sadece teyit etmeyi merak ediyorum, sadece ipi matematiksel bir ifadeye dönüştürmek mi? Resmi tanımı nerede bulabilirim? Ayrıca, parametre genişletme değiştirme bölümündeki ekstra alıntılar GNU bash, sürüm 4.1.2 (2) -release CentOS 6.6'da çalışmaz. Bunun yerine bunu yapmak zorundaydımecho "$((${-+"(${IP//./+256*(}))))"}>>16&255))"
Levi Uzodike

1
@LeviUzodike, bkz. Düzenleme.
Stéphane Chazelas

4

Zsh ile parametre ikamelerini iç içe yerleştirebilirsiniz:

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

Bu bashta mümkün değil.


1
İçinde zsh, tercih edebilirsiniz${${(s(.))ip}[3]}
Stéphane Chazelas

4

Tabii, fil oyununu oynayalım.

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

veya

$ ipsplit() { local IFS=.; echo $*; }
$ set -- `ipsplit 10.1.2.3`
$ echo $1
10

2
"Fil oyunu" nedir?
Joker

1
@Wildcard tür eliptik bir cin / şaka, hem kör insanı tarif eden bir fil hikayesine bir referans, herkesin kendi isteğine sahip olacaklarına bakmak için bazı parçalar olabilir ve isteyen bir kralın eski geleneğine yıkıcı pahalı bakım ile hediyeler vermek, eğlence değeri için reifted eğilimindedir sadece şüpheli değeri hediyeler için yüzyıllar boyunca yumuşatılmış .. Her ikisi de burada geçerli görünüyordu :-)
jthill

3

İle IP=12.34.56.78.

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

Ve

#!/bin/bash
IP=$1

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

Açıklama:

${IP// }IP'deki her noktayı bir açılış parantezine bir noktaya ve bir kapanış parantezine dönüştürmek için parametre genişletme kullanımı . İlk parantez ve kapanış parantezini ekleyerek şunları elde ederiz:

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

test sözdiziminde normal ifade eşleşmesi için dört yakalama parantezi oluşturur:

[[ $IP =~ $regex ]]

Bu, BASH_REMATCH dizisinin ilk bileşen olmadan yazdırılmasına izin verir (tüm normal ifade eşleşmesi):

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

Parantez miktarı otomatik olarak eşleşen dizeye ayarlanır. Bu nedenle, bu, farklı uzunluklarda olmalarına rağmen IPv6 adresinin MAC veya EUI-64'üyle eşleşir:

#!/bin/bash
IP=$1

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

Kullanma:

$ ./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(benim durumumda dash), tekrar tekrar parametre genişletme ( IFSburada hayır ) kullanan bir işlev olan ve adlandırılmış yöneltmeler ile yapılan ve Stephane'ın cevabındanoglob belirtilen nedenler için seçenek içeren küçük bir çözüm .

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

Bu şu şekilde çalışır:

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

Ve ile ipdeğiştirildi109.*.*.*

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

4 yinelemeli döngü tutma sayacı, geçerli bir IPv4 adresinin 4 bölümünü açıklarken, adlandırılmış kanallara sahip akrobasi, değişkenlerin bir döngü alt kabuğuna yapışmasının aksine, komut dosyası içinde ip adresi bölümlerini daha fazla kullanmaları gerektiğini açıklar.


Cevabınızı değiştirdim, böylece pipo kullanmaz ve mevcut cevabıma ekledim .
G-Man,

0

Neden awk ile basit bir çözüm kullanmıyorsunuz?

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

Sonuç $ 192 168 1 1


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.