Bash: Extraire l'une des quatre sections d'une adresse IPv4


9

Nous pouvons utiliser la syntaxe ${var##pattern}et ${var%%pattern}pour extraire la dernière et la première section d'une adresse 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##*.}

Comment extraire la deuxième ou la troisième section d'une adresse IPv4 en utilisant l'expansion des paramètres?

Voici ma solution: j'utilise un tableau et change la variable 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

Aussi j'ai écrit des solutions en utilisant les awk, sedet les cutcommandes.

Maintenant, ma question est: existe-t-il une solution plus simple basée sur l' expansion des paramètres qui n'utilise pas de changement de tableau et IFS?


1
Vous ne devez définir IFSpour le readlà:IFS=. read -a ArrIP<<<"$IP"
muru

1
Pas en bash sans utiliser au moins plusieurs variables. Une extension à paramètre unique ne peut pas obtenir les deuxième ou troisième composants. Zsh peut imbriquer des extensions de paramètres, donc cela pourrait être possible.
muru

@muru Pourriez-vous s'il vous plaît fournir la solution Zsh?
sci9

4
Quelle garantie avez-vous que vous aurez toujours affaire à des adresses IP v4 et que vous n'aurez jamais d'adresse IP v6?
Mawg dit réintégrer Monica le

4
Y at - il une raison quelconque IFS=. read a b c d <<< "$IP"n'est pas acceptable (si vous utilisez Bash, qui est)? Pourquoi faut-il le faire avec l'expansion des paramètres?
ilkkachu

Réponses:


16

En supposant que la valeur par défaut de IFS vous extrayez chaque octet dans sa propre variable avec:

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

Ou dans un tableau avec:

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

2
+1. Il me semble que c'est la méthode la plus simple et la plus directe qui respecte les restrictions des PO.
Digital Trauma

7

Votre énoncé de problème peut être un peu plus libéral que vous ne le vouliez. Au risque d'exploiter une faille, voici la solution à laquelle Muru a fait allusion :

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

C'est quelque peu maladroit. Il définit deux variables à jeter et n'est pas facilement adapté pour gérer plus de sections (par exemple, pour une adresse MAC ou IPv6).  La réponse de Sergiy Kolodyazhnyy m'a inspiré à généraliser ce qui précède à ceci:

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

Ceci définit sec1, sec2, sec3et sec4qui peuvent être vérifiées

printf 'Section 1: %s\n' "$sec1"
printf 'Section 2: %s\n' "$sec2"
printf 'Section 3: %s\n' "$sec3"
printf 'Section 4: %s\n' "$sec4"
  • La whileboucle doit être facile à comprendre - elle se répète quatre fois.
  • Sergiy a choisi slicele nom d'une variable qui remplace last3et last2dans ma première solution (ci-dessus).
  • declare sec"$count"="value"est un moyen d'assigner à sec1, sec2, sec3et sec4 quand countest 1, 2, 3et 4. C'est un peu comme eval, mais plus sûr.
  • La value, "${slice%%.*}"est analogue à des valeurs mes ayants réponse originales first, secondet third.

6

Je me rends compte que vous avez spécifiquement demandé une solution qui N'A PAS REDÉFINI temporairement IFS, mais j'ai une solution douce et simple que vous n'avez pas couverte, alors voici:

IFS=. ; set -- $IP

Cette commande courte mettra les éléments de votre adresse IP dans le shell paramètres de position $1 , $2, $3, $4. Cependant, vous voudrez probablement d'abord enregistrer l'original IFSet le restaurer ensuite.

Qui sait? Vous allez peut-être reconsidérer et accepter cette réponse pour sa brièveté et son efficacité.

(Cela était auparavant incorrectement donné comme IFS=. set -- $IP)


5
Je ne pense pas que cela fonctionne: si vous changez IFSsur la même ligne de commande, la nouvelle valeur ne prend pas effet lorsque les variables sur la même ligne de commande sont développées. Comme avecx=1; x=2 echo $x
ilkkachu

6
@chepner, mais encore une fois, setn'utilise pas du $IFStout, $IFSn'est utilisé que pour le fractionnement du mot $IP, mais ici, il est attribué trop tard, donc cela n'a aucun effet. Cette réponse est fondamentalement fausse. IP=109.96.77.15 bash -c 'IFS=. set -- $IP; echo "$2"'ne produit rien que ce soit en mode POSIX ou non. Vous auriez besoin IFS=. command eval 'set -- $IP', ouIFS=. read a b c d << "$IP"
Stéphane Chazelas

4
Cela fonctionne probablement pour vous, car vous aviez défini IFS .dans l'un de vos tests précédents. Courez IP=1.2.3.4 bash -xc 'IFS=. set $IP; echo "$2"'et vous verrez que cela ne fonctionne pas. Et voyez IP=1.2.3.4 bash -o posix -xc 'IFS=. set $IP; echo "\$1=$1 \$2=$2 IFS=$IFS"'pour illustrer le point de @ chepner.
Stéphane Chazelas

2
la hâte a fait du gaspillage, la réponse est apparemment fausse et devrait être corrigée pour fonctionner, car c'est toujours à peu près la façon dont je le ferais, seulement avec plus de code.
Lizardx

3
@ user1404316, pouvez-vous publier l'ensemble exact de commandes que vous avez utilisé pour le tester? (Plus la version de votre shell, peut-être.) Il y a quatre autres utilisateurs qui vous ont dit ici dans les commentaires que cela ne fonctionne pas comme écrit dans la réponse. Avec des exemples.
ilkkachu

5

Pas le plus simple , mais vous pourriez faire quelque chose comme:

$ 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

Cela devrait fonctionner dans ksh93 (où que l' ${var//pattern/replacement}opérateur vient), bash4.3 +, busybox sh, yash, mkshet zsh, même si bien sûr dans zsh, il y a des approches beaucoup plus simples . Dans les anciennes versions de bash, vous devez supprimer les guillemets internes. Cela fonctionne avec ces guillemets internes supprimés dans la plupart des autres shells, mais pas ksh93.

Cela suppose qu'il $IPcontient une représentation quadri-décimale valide d'une adresse IPv4 (bien que cela fonctionnerait également pour des représentations quad-hexadécimales comme 0x6d.0x60.0x4d.0xf(et même octal dans certains shells) mais produirait les valeurs en décimal). Si le contenu de $IPprovient d'une source non fiable, cela équivaudrait à une vulnérabilité d'injection de commande.

Au fond, comme nous remplaçons tous .dans $IPavec +256*(, on finit par évaluer:

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

Nous sommes donc construire un entier de 32 bits sur ces 4 octets comme une adresse IPv4 est en fin de compte (mais avec les octets inversés) ¹, puis en utilisant les >>, &opérateurs de bits pour extraire les octets pertinents.

Nous utilisons l' ${param+value}opérateur standard (ici sur $-lequel il est garanti d'être toujours défini) au lieu de simplement valueparce que sinon l'analyseur arithmétique se plaindrait de parenthèses incompatibles. Le shell ici peut trouver la fermeture ))de l'ouverture $((, puis effectuer les extensions à l'intérieur qui entraîneront l'expression arithmétique à évaluer.

Avec à la $(((${IP//./"+256*("}))))&255))place, le shell traiterait le deuxième et le troisième )s comme la fermeture ))de $((et rapporterait une erreur de syntaxe.

Dans ksh93, vous pouvez également faire:

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

bash, mksh, zsh Ont copié de ksh93 ${var/pattern/replacement}opérateur , mais pas cette partie de manutention capture groupe. zshle prend en charge avec une syntaxe différente:

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

bashprend en charge une certaine forme de gestion de groupe de capture dans son opérateur de correspondance d'expressions régulières , mais pas dans ${var/pattern/replacement}.

POSIXly, vous utiliseriez:

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

Le noglobpour éviter les mauvaises surprises pour les valeurs de $IPlike 10.*.*.*, le sous-shell pour limiter la portée de ces changements d'options et $IFS.


¹ Une adresse IPv4 n'est qu'un entier de 32 bits et 127.0.0.1, par exemple, n'est qu'une des nombreuses représentations textuelles (bien que les plus courantes). Cette même adresse IPv4 typique de l'interface de bouclage peut également être représentée comme 0x7f000001 ou 127.1 (peut-être une plus appropriée ici pour dire que c'est l' 1adresse sur le réseau 127.0 / 8 classe A), ou 0177.0.1, ou les autres combinaisons de 1 à 4 nombres exprimés en octal, décimal ou hexadécimal. Vous pouvez passer tous ceux-ci à, pingpar exemple, et vous verrez qu'ils cingleront tous localhost.

Si cela ne vous dérange pas l'effet secondaire de la définition d'une variable temporaire arbitraire (ici $n), dans bashou ksh93ou zsh -o octalzeroesou lksh -o posix, vous pouvez simplement reconvertir toutes ces représentations en un entier 32 bits avec:

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

Et puis extrayez tous les composants avec >>/ &combinaisons comme ci-dessus.

$ 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

mkshutilise des entiers 32 bits signés pour ses expressions arithmétiques, vous pouvez l'utiliser $((# n=32,...))pour forcer l'utilisation de nombres 32 bits non signés (et l' posixoption pour qu'il reconnaisse les constantes octales).


Je comprends le grand concept, mais je n'en ai jamais vu ${-+auparavant. Je ne trouve pas non plus de documentation dessus. Cela fonctionne, mais je suis juste curieux de confirmer, est-ce juste pour transformer la chaîne en une expression mathématique? Où puis-je trouver la définition officielle? De plus, les citations supplémentaires dans la section de remplacement de l'extension des paramètres ne fonctionnent pas dans GNU bash, version 4.1.2 (2) -release CentOS 6.6. Je devais le faire à la placeecho "$((${-+"(${IP//./+256*(}))))"}>>16&255))"
Levi Uzodike

1
@LeviUzodike, voir modifier.
Stéphane Chazelas

4

Avec zsh, vous pouvez imbriquer des substitutions de paramètres:

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

Ce n'est pas possible dans bash.


1
Dans zsh, vous pouvez préférer${${(s(.))ip}[3]}
Stéphane Chazelas

4

Bien sûr, jouons au jeu de l'éléphant.

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

ou

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

2
Qu'est-ce que le "jeu des éléphants"?
Wildcard

1
@Wildcard est une sorte de jeu de mots / blague elliptique, c'est une référence à la fois aux aveugles qui décrivent une histoire d'éléphant, il y a tellement de parties à regarder que tout le monde va avoir sa propre opinion, et à l'ancienne coutume d'un roi qui voulait de donner des cadeaux à l' entretien ruineux, adouci au fil des siècles à des cadeaux de valeur simplement douteux qui ont tendance à être regifted valeur de divertissement .. Tous deux semblaient appliquer ici :-)
jthill

3

Avec IP=12.34.56.78.

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

Et

#!/bin/bash
IP=$1

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

La description:

Utilisation de l'expansion des paramètres ${IP// }pour convertir chaque point de l'ip en une parenthèse ouvrante, un point et une parenthèse fermante. En ajoutant une parenthèse initiale et une parenthèse fermante, nous obtenons:

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

ce qui créera quatre parenthèses de capture pour la correspondance d'expression régulière dans la syntaxe de test:

[[ $IP =~ $regex ]]

Cela permet l'impression du tableau BASH_REMATCH sans le premier composant (la correspondance regex entière):

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

La quantité de parenthèses est automatiquement ajustée à la chaîne correspondante. Ainsi, cela correspondra soit à un MAC soit à un EUI-64 d'une adresse IPv6 malgré leur longueur différente:

#!/bin/bash
IP=$1

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

En l'utilisant:

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

Voici une petite solution faite avec POSIX /bin/sh(dans mon cas, c'est le cas dash), une fonction qui utilise à plusieurs reprises l'expansion des paramètres (donc pas IFSici), et des canaux nommés, et inclut une nogloboption pour les raisons mentionnées dans la réponse de Stéphane .

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

Cela fonctionne comme suit:

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

Et avec ipchangé en109.*.*.*

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

Le compteur de maintien de boucle de 4 itérations représente 4 sections d'une adresse IPv4 valide, tandis que les acrobaties avec des canaux nommés expliquent la nécessité d'utiliser davantage des sections d'adresse IP dans le script, au lieu d'avoir des variables coincées dans un sous-shell d'une boucle.


J'ai modifié votre réponse pour qu'elle n'utilise pas de pipe et je l' ai ajoutée à ma réponse existante .
G-Man dit `` Réintègre Monica '' le

0

Pourquoi ne pas utiliser une solution simple avec awk?

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

Résultat $ 192 168 1 1


En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.