Bash: Extrahieren Sie einen der vier Abschnitte einer IPv4-Adresse


9

Wir können die Syntax verwenden ${var##pattern}und ${var%%pattern}den letzten und ersten Abschnitt einer IPv4-Adresse extrahieren:

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

Wie können wir den zweiten oder dritten Abschnitt einer IPv4-Adresse mithilfe der Parametererweiterung extrahieren?

Hier ist meine Lösung: Ich verwende ein Array und ändere die IFS-Variable.

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

Ich habe auch einige Lösungen mit den geschrieben awk, sedund cutBefehlen.

Meine Frage lautet nun: Gibt es eine einfachere Lösung basierend auf der Parametererweiterung, bei der keine Array- und IFS-Änderungen verwendet werden?


1
Sie sollten nur IFSfür die readdort IFS=. read -a ArrIP<<<"$IP"
einstellen

1
Nicht in Bash, ohne mindestens mehrere Variablen zu verwenden. Eine einzelne Parametererweiterung kann die zweite oder dritte Komponente nicht erhalten. Zsh kann Parametererweiterungen verschachteln, sodass dies dort möglicherweise möglich ist.
Muru

@muru Könnten Sie bitte die Zsh-Lösung bereitstellen?
Sci9

4
Welche Garantie haben Sie, dass Sie immer mit IP v4-Adressen arbeiten und niemals eine IP v6-Adresse haben?
Mawg sagt, Monica am

4
Gibt es einen Grund, der IFS=. read a b c d <<< "$IP"nicht akzeptabel ist (wenn Sie Bash verwenden)? Warum muss es mit Parametererweiterung gemacht werden?
Ilkkachu

Antworten:


16

Unter der Annahme des Standardwerts von IFS extrahieren Sie jedes Oktett in eine eigene Variable mit:

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

Oder in ein Array mit:

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

2
+1. Es scheint mir, dass dies die einfachste und einfachste Methode ist, die die Einschränkungen des OP einhält.
Digitales Trauma

7

Ihre Problemstellung ist möglicherweise etwas liberaler als beabsichtigt. Auf die Gefahr hin, eine Lücke auszunutzen, ist hier die Lösung, auf die muru anspielt :

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

Das ist etwas klobig. Es definiert zwei Wegwerfvariablen und ist nicht ohne weiteres für die Verarbeitung weiterer Abschnitte geeignet (z. B. für eine MAC- oder IPv6-Adresse).  Die Antwort von Sergiy Kolodyazhnyy hat mich dazu inspiriert, Folgendes zu verallgemeinern:

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

Diese Sätze sec1, sec2, sec3und sec4, die mit werden kann verifiziert

printf 'Section 1: %s\n' "$sec1"
printf 'Section 2: %s\n' "$sec2"
printf 'Section 3: %s\n' "$sec3"
printf 'Section 4: %s\n' "$sec4"
  • Die whileSchleife sollte leicht zu verstehen sein - sie wird viermal wiederholt.
  • Sergiy wählte sliceals Namen für eine Variable, die den Platz von last3und last2in meiner ersten Lösung (oben) einnimmt .
  • declare sec"$count"="value"ist ein Weg , um zuzuordnen sec1, sec2, sec3und sec4 wenn countist 1, 2, 3und 4. Es ist ein bisschen wie eval, aber sicherer.
  • Das value, "${slice%%.*}"ist analog zu dem meine ursprünglichen Antwort Werte zugewiesen first, secondund third.

6

Mir ist klar, dass Sie speziell nach einer Lösung gefragt haben, die NICHT vorübergehend neu definiert wurde IFS, aber ich habe eine süße und einfache Lösung, die Sie nicht behandelt haben.

IFS=. ; set -- $IP

Der kurze Befehl , um die Elemente Ihrer IP - Adresse in der Shell gesetzt wird Positionsparameter $1 , $2, $3, $4. Wahrscheinlich möchten Sie jedoch zuerst das Original speichern IFSund anschließend wiederherstellen.

Wer weiß? Vielleicht überdenken und akzeptieren Sie diese Antwort für ihre Kürze und Effizienz.

(Dies wurde zuvor fälschlicherweise als angegeben IFS=. set -- $IP)


5
Ich denke nicht, dass das funktioniert: Wenn Sie IFSin derselben Befehlszeile ändern , wird der neue Wert nicht wirksam, wenn Variablen in derselben Befehlszeile erweitert werden. Gleich wie mitx=1; x=2 echo $x
ilkkachu

6
@chepner, aber auch hier wird setüberhaupt nicht verwendet $IFS, $IFSwird nur für die Wortaufteilung von verwendet $IP, aber hier wird es zu spät zugewiesen, so dass dies keine Auswirkung hat. Diese Antwort ist grundsätzlich falsch. IP=109.96.77.15 bash -c 'IFS=. set -- $IP; echo "$2"'gibt nichts aus, ob im POSIX-Modus oder nicht. Sie würden brauchen IFS=. command eval 'set -- $IP', oderIFS=. read a b c d << "$IP"
Stéphane Chazelas

4
Es funktioniert wahrscheinlich für Sie, weil Sie IFS .in einem Ihrer vorherigen Tests eingestellt hatten. Laufen IP=1.2.3.4 bash -xc 'IFS=. set $IP; echo "$2"'Sie und Sie werden sehen, dass es nicht funktioniert. Und sehen Sie IP=1.2.3.4 bash -o posix -xc 'IFS=. set $IP; echo "\$1=$1 \$2=$2 IFS=$IFS"', um @ chepners Punkt zu veranschaulichen.
Stéphane Chazelas

2
Eile hat Verschwendung gemacht, die Antwort ist anscheinend falsch und sollte korrigiert werden, um zu funktionieren, da es immer noch ungefähr so ​​ist, wie ich es tun würde, nur mit mehr Code.
Lizardx

3
@ user1404316, können Sie den genauen Befehlssatz veröffentlichen, mit dem Sie ihn getestet haben? (Plus vielleicht die Version Ihrer Shell.) Es gibt vier andere Benutzer, die Ihnen hier in den Kommentaren gesagt haben, dass es nicht so funktioniert, wie in der Antwort geschrieben. Mit Beispielen.
Ilkkachu

5

Nicht die einfachste , aber Sie könnten so etwas tun:

$ 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

Das sollte in ksh93 arbeiten (wo die ${var//pattern/replacement}Betreiber herkommt), bash4.3+, Busybox sh, yash, mkshund zsh, obwohl natürlich in zsh, gibt es viel einfachere Ansätze . In älteren Versionen von bashmüssten Sie die inneren Anführungszeichen entfernen. Es funktioniert mit den inneren Anführungszeichen, die auch in den meisten anderen Shells entfernt wurden, jedoch nicht mit ksh93.

Dies setzt voraus, dass $IPeine gültige Quad-Dezimal-Darstellung einer IPv4-Adresse enthalten ist (obwohl dies auch für Quad-Hexadezimal-Darstellungen wie 0x6d.0x60.0x4d.0xf(und in einigen Shells sogar oktal) funktionieren würde, die Werte jedoch dezimal ausgeben würde). Wenn der Inhalt von $IPaus einer nicht vertrauenswürdigen Quelle stammt, würde dies eine Sicherheitsanfälligkeit bezüglich Befehlsinjektion darstellen.

Im Grunde genommen , wie wir alle sind zu ersetzen .in $IPmit +256*(, wir Auswertung am Ende:

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

Also haben wir ein 32 - Bit - Integer aus diesem 4 Bytes wie eine IPv4 - Adresse sind die Konstruktion ist schließlich (wenn auch mit den Bytes umgekehrt) ¹ und dann mit den >>, &Bitoperatoren das relevante Bytes zu extrahieren.

Wir verwenden den ${param+value}Standardoperator (hier $-wird immer festgelegt), anstatt nur, valueweil sich der arithmetische Parser sonst über nicht übereinstimmende Klammern beschweren würde. Die Shell hier kann den Abschluss ))für die Öffnung $((finden und dann die Erweiterungen im Inneren ausführen, die dazu führen, dass der arithmetische Ausdruck ausgewertet wird.

Mit $(((${IP//./"+256*("}))))&255))stattdessen behandeln würde die Schale der zweiten und dritten )s dort als Schließ ))für $((und einen Syntaxfehler melden.

In ksh93 können Sie auch Folgendes tun:

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

bash, mksh, zsh Haben ksh93 des kopierten ${var/pattern/replacement}Betreiber aber nicht , dass der Fang-Gruppe einen Teil der Handhabung. zshunterstützt es mit einer anderen Syntax:

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

bashunterstützt eine Form der Erfassung von Erfassungsgruppen in seinem Regexp-Matching-Operator , jedoch nicht in ${var/pattern/replacement}.

POSIXly würden Sie verwenden:

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

Die noglobvermeiden böse Überraschungen für Werte $IPwie 10.*.*.*die Subshell den Umfang dieser Änderungen an Optionen zu begrenzen und $IFS.


¹ Eine IPv4-Adresse ist nur eine 32-Bit-Ganzzahl und 127.0.0.1 ist beispielsweise nur eine von vielen (wenn auch am häufigsten verwendeten) Textdarstellungen. Dieselbe typische IPv4-Adresse der Loopback-Schnittstelle kann auch als 0x7f000001 oder 127.1 (hier möglicherweise besser geeignet, um zu sagen, dass es sich um die 1Adresse im 127.0 / 8-Klasse-A-Netzwerk handelt) oder 0177.0.1 oder die anderen Kombinationen von 1 dargestellt werden bis 4 Zahlen ausgedrückt als Oktal, Dezimal oder Hexadezimal. Sie können all diese zum pingBeispiel an übergeben und Sie werden sehen, dass sie alle localhost pingen.

Wenn Ihnen der Nebeneffekt des Festlegens einer beliebigen temporären Variablen (hier $n) in bashoder ksh93oder zsh -o octalzeroesoder nichts ausmacht lksh -o posix, können Sie einfach alle diese Darstellungen zurück in eine 32-Bit-Ganzzahl konvertieren mit:

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

Und dann extrahieren Sie alle Komponenten mit >>/ &Kombinationen wie oben.

$ 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

mkshverwendet vorzeichenbehaftete 32-Bit-Ganzzahlen für seine arithmetischen Ausdrücke. Dort können Sie $((# n=32,...))die Verwendung vorzeichenloser 32-Bit-Zahlen erzwingen (und die posixOption, Oktalkonstanten zu erkennen).


Ich verstehe das große Konzept, aber ich habe es noch nie gesehen ${-+. Ich kann auch keine Dokumentation dazu finden. Es funktioniert, aber ich bin nur neugierig zu bestätigen, ob es nur darum geht, die Zeichenfolge in einen mathematischen Ausdruck umzuwandeln. Wo finde ich die formale Definition? Außerdem funktionieren die zusätzlichen Anführungszeichen im Abschnitt zum Ersetzen der Parametererweiterung in GNU bash, Version 4.1.2 (2) - Release CentOS 6.6 nicht. Ich musste das stattdessen tunecho "$((${-+"(${IP//./+256*(}))))"}>>16&255))"
Levi Uzodike

1
@LeviUzodike, siehe bearbeiten.
Stéphane Chazelas

4

Mit zsh können Sie Parametersubstitutionen verschachteln:

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

Dies ist in Bash nicht möglich.


1
In zsh, können Sie bevorzugen${${(s(.))ip}[3]}
Stéphane Chazelas

4

Klar, lass uns das Elefantenspiel spielen.

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

oder

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

2
Was ist das "Elefantenspiel"?
Wildcard

1
@Wildcard ist eine Art elliptisches Wortspiel / Witz, es bezieht sich sowohl auf die Blinden, die eine Elefantengeschichte beschreiben, als auch auf den alten Brauch eines Königs, der es wollte Geschenke mit ruinös teurem Unterhalt zu geben, die im Laufe der Jahrhunderte zu Geschenken von nur zweifelhaftem Wert erweicht wurden, die dazu neigen, für den Unterhaltungswert wieder in Schwung gebracht zu werden. Beide schienen hier
zuzutreffen

3

Mit IP=12.34.56.78.

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

Und

#!/bin/bash
IP=$1

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

Beschreibung:

Verwendung der Parametererweiterung ${IP// }, um jeden Punkt in der IP in eine öffnende Klammer, einen Punkt und eine schließende Klammer umzuwandeln. Wenn wir eine anfängliche Klammer und eine schließende Klammer hinzufügen, erhalten wir:

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

Dadurch werden vier Erfassungsklammern für die Regex-Übereinstimmung in der Testsyntax erstellt:

[[ $IP =~ $regex ]]

Dies ermöglicht das Drucken des Arrays BASH_REMATCH ohne die erste Komponente (die gesamte Regex-Übereinstimmung):

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

Die Anzahl der Klammern wird automatisch an die übereinstimmende Zeichenfolge angepasst. Dies entspricht also entweder einem MAC oder einem EUI-64 einer IPv6-Adresse, obwohl diese unterschiedlich lang sind:

#!/bin/bash
IP=$1

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

Es benutzen:

$ ./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

Hier ist eine kleine Lösung, die mit POSIX /bin/sh(in meinem Fall dash) erstellt wurde, einer Funktion, die wiederholt die Parametererweiterung (also IFShier nicht) und benannte Pipes verwendet und noglobaus den in Stephanes Antwort genannten Gründen eine Option enthält .

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

Das funktioniert so:

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

Und mit ipgeändert zu109.*.*.*

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

Der Schleifenzähler von 4 Iterationen berücksichtigt 4 Abschnitte einer gültigen IPv4-Adresse, während die Akrobatik mit Named Pipes die Notwendigkeit berücksichtigt, Abschnitte der IP-Adresse innerhalb des Skripts weiter zu verwenden, anstatt Variablen in einer Unterschale einer Schleife festzuhalten.


Ich habe Ihre Antwort so geändert, dass keine Pipe verwendet wird, und sie meiner vorhandenen Antwort hinzugefügt .
G-Man sagt "Reinstate Monica"

0

Warum nicht eine einfache Lösung mit awk verwenden?

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

Ergebnis $ 192 168 1 1


Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.