Bash: estrae una delle quattro sezioni di un indirizzo IPv4


9

Possiamo usare la sintassi ${var##pattern}ed ${var%%pattern}estrarre l'ultima e la prima sezione di un indirizzo 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##*.}

Come possiamo estrarre la seconda o la terza sezione di un indirizzo IPv4 usando l'espansione dei parametri?

Ecco la mia soluzione: utilizzo un array e cambio la variabile 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

Inoltre ho scritto alcune soluzioni che utilizzano i awk, sede cutcomandi.

Ora, la mia domanda è: esiste una soluzione più semplice basata sull'espansione dei parametri che non utilizza la modifica di array e IFS?


1
Si dovrebbe solo impostare IFSper readlì:IFS=. read -a ArrIP<<<"$IP"
Muru

1
Non in bash senza usare almeno più variabili. Un'espansione a singolo parametro non può ottenere il secondo o il terzo componente. Zsh può nidificare espansioni di parametri, quindi potrebbe essere possibile lì.
Muru,

@muru Potresti fornire la soluzione Zsh per favore?
sci9,

4
Che garanzia hai che ti occuperai sempre degli indirizzi IP v4 e che non avrai mai un indirizzo IP v6?
Mawg dice di reintegrare Monica il

4
C'è qualche motivo IFS=. read a b c d <<< "$IP"non è accettabile (se stai usando Bash, cioè)? Perché deve essere fatto con l'espansione dei parametri?
ilkkachu,

Risposte:


16

Supponendo che il valore predefinito di IFS si estragga ciascun ottetto nella propria variabile con:

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

O in un array con:

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

2
+1. Mi sembra che questo sia il metodo più semplice e diretto che rispetti le restrizioni dei PO.
Trauma digitale

7

La tua dichiarazione del problema potrebbe essere un po 'più liberale di quanto volevi. A rischio di sfruttare una scappatoia, ecco la soluzione a cui si fa riferimento :

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

Questo è un po 'goffo. Definisce due variabili usa e getta e non è prontamente adattato per gestire più sezioni (ad es. Per un indirizzo MAC o IPv6).  La risposta di Sergiy Kolodyazhnyy mi ha ispirato a generalizzare quanto sopra a questo:

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

Questo imposta sec1, sec2, sec3e sec4che possono essere verificati con

printf 'Section 1: %s\n' "$sec1"
printf 'Section 2: %s\n' "$sec2"
printf 'Section 3: %s\n' "$sec3"
printf 'Section 4: %s\n' "$sec4"
  • Il whileciclo dovrebbe essere facile da capire: scorre quattro volte.
  • Sergiy ha scelto slicecome nome una variabile che prende il posto di last3e last2nella mia prima soluzione (sopra).
  • declare sec"$count"="value"è un modo per assegnare sec1, sec2, sec3e sec4 quando countè 1, 2, 3e 4. È un po 'come eval, ma più sicuro.
  • Il value, "${slice%%.*}", è analogo ai valori miei assegna risposta originali first, seconde third.

6

Mi rendo conto che hai specificamente richiesto una soluzione che NON È STATA ridefinita temporaneamente IFS, ma ho una soluzione dolce e semplice che non hai trattato, quindi ecco:

IFS=. ; set -- $IP

Quel breve comando metterà gli elementi del proprio indirizzo IP nella shell di parametri posizionali $1 , $2, $3, $4. Tuttavia, probabilmente vorrai prima salvare l'originale IFSe ripristinarlo in seguito.

Chissà? Forse riconsidera e accetti questa risposta per la sua brevità ed efficienza.

(Questo è stato precedentemente erroneamente indicato come IFS=. set -- $IP)


5
Non penso che funzioni: se si cambia IFSsulla stessa riga di comando, il nuovo valore non ha effetto quando si espandono le variabili sulla stessa riga di comando. Come nel caso dix=1; x=2 echo $x
ilkkachu, il

6
@chepner, ma ancora una volta, setnon ne fa affatto uso $IFS, $IFSviene usato solo per la suddivisione delle parole $IP, ma qui è assegnato troppo tardi, quindi non ha alcun effetto. Questa risposta è sostanzialmente sbagliata. IP=109.96.77.15 bash -c 'IFS=. set -- $IP; echo "$2"'non emette nulla in modalità POSIX o meno. Ne avresti bisogno IFS=. command eval 'set -- $IP', oppureIFS=. read a b c d << "$IP"
Stéphane Chazelas il

4
Probabilmente funziona per te perché hai impostato IFS .in uno dei tuoi test precedenti. Esegui IP=1.2.3.4 bash -xc 'IFS=. set $IP; echo "$2"'e vedrai che non funziona. E guarda IP=1.2.3.4 bash -o posix -xc 'IFS=. set $IP; echo "\$1=$1 \$2=$2 IFS=$IFS"'per illustrare il punto di @ chepner.
Stéphane Chazelas,

2
la fretta ha sprecato, la risposta è apparentemente sbagliata e dovrebbe essere corretta per funzionare, dato che è ancora più o meno come lo farei, solo con più codice.
Lizardx,

3
@utente1404316, puoi pubblicare il set esatto di comandi che hai usato per testarlo? (Più la versione della tua shell, forse.) Ci sono altri quattro utenti che ti hanno detto qui nei commenti che non funziona come scritto nella risposta. Con esempi.
ilkkachu,

5

Non il più semplice , ma potresti fare qualcosa del tipo:

$ 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

Che dovrebbe funzionare in ksh93 (dove quel ${var//pattern/replacement}operatore viene), bash4.3 e versioni successive, busybox sh, yash, mkshe zsh, anche se naturalmente in zsh, ci sono approcci molto più semplici . Nelle versioni precedenti di bash, avresti bisogno di rimuovere le virgolette interne. Funziona con quelle citazioni interne rimosse anche nella maggior parte delle altre shell, ma non in ksh93.

Ciò presuppone che $IPcontenga una rappresentazione quad-decimale valida di un indirizzo IPv4 (sebbene funzionerebbe anche per rappresentazioni quad-esadecimali come 0x6d.0x60.0x4d.0xf(e persino ottale in alcune shell) ma genererebbe i valori in decimali). Se il contenuto di $IPproviene da una fonte non attendibile, ciò equivarrebbe a una vulnerabilità nell'iniezione di comando.

Fondamentalmente, come stiamo sostituendo ogni .in $IPcon +256*(, finiamo per valutare:

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

Quindi stiamo costruendo un intero a 32 bit da quelle 4 byte come un indirizzo IPv4 in ultima analisi, è (anche se con i byte invertiti) ¹ e poi utilizzando i >>, &operatori bit per bit per estrarre i byte rilevanti.

Usiamo l' ${param+value}operatore standard (qui su $-cui è garantito che sia sempre impostato) anziché semplicemente valueperché altrimenti il ​​parser aritmetico si lamenterebbe di parentesi non corrispondenti. Qui la shell può trovare la chiusura ))per l'apertura $((, quindi eseguire le espansioni all'interno che determineranno l'espressione aritmetica da valutare.

Con $(((${IP//./"+256*("}))))&255))invece, il guscio tratterebbe, secondo e terzo )s lì come la chiusura ))per $((e riportare un errore di sintassi.

In ksh93, puoi anche fare:

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

bash, mksh, zsh Hanno copiato di ksh93 ${var/pattern/replacement}operatore, ma non che la cattura-gruppo di movimentazione e sollevamento. zshlo supporta con una sintassi diversa:

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

bashsupporta una qualche forma di gestione dei gruppi di acquisizione nel suo operatore di regexp matching , ma non in ${var/pattern/replacement}.

POSIXly, useresti:

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

Il noglobper evitare brutte sorprese per valori di $IPlike 10.*.*.*, la subshell per limitare l'ambito di tali modifiche alle opzioni e $IFS.


¹ Un indirizzo IPv4 è solo un numero intero a 32 bit e, ad esempio, 127.0.0.1 è solo una delle molte (anche se le più comuni) rappresentazioni testuali. Lo stesso tipico indirizzo IPv4 dell'interfaccia di loopback può anche essere rappresentato come 0x7f000001 o 127.1 (forse uno più appropriato qui per dire che è l' 1indirizzo sulla rete di classe A 127.0 / 8), o 0177.0.1, o le altre combinazioni di 1 a 4 numeri espressi come ottali, decimali o esadecimali. Puoi passare tutti quelli ad pingesempio e vedrai che eseguiranno il ping di localhost.

Se non ti interessa l'effetto collaterale dell'impostazione di una variabile temporanea arbitraria (qui $n), in basho ksh93o zsh -o octalzeroeso lksh -o posix, puoi semplicemente riconvertire tutte quelle rappresentazioni in un numero intero a 32 bit con:

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

E quindi estrarre tutti i componenti con >>/ &combinazioni come sopra.

$ 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

mkshusa numeri interi a 32 bit con segno per le sue espressioni aritmetiche, puoi usarlo $((# n=32,...))per forzare l'uso di numeri a 32 bit senza segno (e l' posixopzione per riconoscere le costanti ottali).


Capisco il grande concetto, ma non l'ho mai visto ${-+prima. Non riesco a trovare alcuna documentazione su di esso. Funziona, ma sono solo curioso di confermare, è solo per trasformare la stringa in un'espressione matematica? Dove posso trovare la definizione formale? Inoltre, le citazioni extra all'interno della sezione di sostituzione dell'espansione dei parametri non funzionano in GNU bash, versione 4.1.2 (2)-release CentOS 6.6. Dovevo invece farloecho "$((${-+"(${IP//./+256*(}))))"}>>16&255))"
Levi Uzodike il

1
@LeviUzodike, vedi modifica.
Stéphane Chazelas il

4

Con zsh, è possibile nidificare sostituzioni di parametri:

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

Questo non è possibile in bash.


1
In zsh, potresti preferire${${(s(.))ip}[3]}
Stéphane Chazelas il

4

Certo, giochiamo al gioco dell'elefante.

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

o

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

2
Qual è il "gioco dell'elefante"?
Wildcard

1
@Wildcard è una specie di gioco di parole / scherzo ellittico, è un riferimento sia ai ciechi che descrivono una storia di elefanti, ci sono così parti da guardare a tutti che avranno il loro introito, e all'antica usanza di un re che voleva fare regali con manutenzioni rovinosamente costose, addolciti nel corso dei secoli a doni di valore meramente discutibile che tendono a ridursi a valore di intrattenimento .. Entrambi sembravano applicarsi qui :-)
jillill

3

Con IP=12.34.56.78.

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

E

#!/bin/bash
IP=$1

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

Descrizione:

Uso dell'espansione dei parametri ${IP// }per convertire ogni punto nell'ip in una parentesi aperta un punto e una parentesi chiusa. Aggiungendo una parentesi iniziale e una parentesi di chiusura, otteniamo:

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

che creerà quattro parentesi di acquisizione per la corrispondenza regex nella sintassi del test:

[[ $IP =~ $regex ]]

Ciò consente la stampa dell'array BASH_REMATCH senza il primo componente (l'intera corrispondenza regex):

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

La quantità di parentesi viene automaticamente adattata alla stringa corrispondente. Quindi, questo corrisponderà a un MAC o un EUI-64 di un indirizzo IPv6 nonostante siano di diversa lunghezza:

#!/bin/bash
IP=$1

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

Usandolo:

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

Ecco una piccola soluzione fatta con POSIX /bin/sh(nel mio caso dash), una funzione che usa ripetutamente l'espansione dei parametri (quindi non IFSqui), e named pipe, e include l' noglobopzione per i motivi menzionati nella risposta di Stephane .

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

Funziona così:

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

E con ipcambiato in109.*.*.*

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

Il contatore che mantiene il ciclo di 4 iterazioni rappresenta 4 sezioni di un indirizzo IPv4 valido, mentre le acrobazie con pipe denominate tengono conto della necessità di utilizzare ulteriormente le sezioni dell'indirizzo IP all'interno dello script anziché avere variabili bloccate in una sottostruttura di un ciclo.


Ho modificato la tua risposta in modo che non usi una pipe e l'ho aggiunta alla mia risposta esistente .
G-Man dice "Ripristina Monica" il

0

Perché non usare una soluzione semplice con awk?

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

Risultato $ 192 168 1 1


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.