Bash: extraiga una de las cuatro secciones de una dirección IPv4

9

Podemos usar la sintaxis ${var##pattern}y ${var%%pattern}extraer la última y la primera sección de una dirección 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##*.}

¿Cómo podemos extraer la segunda o tercera sección de una dirección IPv4 usando la expansión de parámetros?

Aquí está mi solución: uso una matriz y cambio 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

También he escrito algunas soluciones que utilizan las awk, sedy cutlos comandos.

Ahora, mi pregunta es: ¿Existe una solución más simple basada en la expansión de parámetros que no use el cambio de matriz e IFS?

sci9
fuente
1
Solo debe configurar IFSpara readallí:IFS=. read -a ArrIP<<<"$IP"
muru
1
No en bash sin usar al menos múltiples variables. Una sola expansión de parámetros no puede obtener el segundo o tercer componente. Zsh puede anidar expansiones de parámetros, por lo que podría ser posible allí.
muru
@muru ¿Podría proporcionar la solución Zsh?
sci9
44
¿Qué garantía tiene de que siempre tratará con direcciones IP v4 y nunca tendrá una dirección IP v6?
Mawg dice que reinstale a Mónica el
44
¿Hay alguna razón IFS=. read a b c d <<< "$IP"que no sea aceptable (si está usando Bash, eso es)? ¿Por qué tiene que hacerse con la expansión de parámetros?
ilkkachu

Respuestas:

16

Suponiendo el valor predeterminado de IFS, extrae cada octeto en su propia variable con:

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

O en una matriz con:

A=(${IP//./ })
Jason Musgrove
fuente
2
+1. Me parece que este es el método más simple y directo que cumple con las restricciones de los OP.
Trauma digital el
7

Su planteamiento del problema puede ser un poco más liberal de lo que pretendía. A riesgo de explotar una escapatoria, aquí está la solución a la que aludía :

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

Esto es algo torpe. Define dos variables desechables y no se adapta fácilmente para manejar más secciones (por ejemplo, para una dirección MAC o IPv6).  La respuesta de Sergiy Kolodyazhnyy me inspiró a generalizar lo anterior a esto:

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

Este conjuntos sec1, sec2, sec3y sec4, que pueden ser verificados 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"
  • El whilebucle debe ser fácil de entender: se repite cuatro veces.
  • Sergiy eligió slicecomo nombre para una variable que toma el lugar de last3y last2en mi primera solución (arriba).
  • declare sec"$count"="value"es una manera de asignar a sec1, sec2, sec3y sec4 cuando countes 1, 2, 3y 4. Es un poco como eval, pero más seguro.
  • El value, "${slice%%.*}"es análogo a los valores de mis cesionarios respuesta original a first, secondy third.
G-Man dice 'restablecer a Mónica'
fuente
6

Me doy cuenta de que pediste específicamente una solución que NO redefinió temporalmente IFS, pero tengo una solución dulce y simple que no cubriste, así que aquí va:

IFS=. ; set -- $IP

Ese comando corto pondrá los elementos de su dirección IP en el shell de parámetros posicionales $1 , $2, $3, $4. Sin embargo, es probable que primero desee guardar el original IFSy luego restaurarlo.

¿Quién sabe? Quizás reconsidere y acepte esta respuesta por su brevedad y eficiencia.

(Esto se dio anteriormente incorrectamente como IFS=. set -- $IP)

usuario1404316
fuente
55
No creo que funcione: si cambia IFSen la misma línea de comando, el nuevo valor no tendrá efecto cuando se expandan las variables en la misma línea de comando. Igual que conx=1; x=2 echo $x
ilkkachu
66
@chepner, pero de nuevo, setno hace uso de $IFSnada, $IFSsolo se usa para dividir la palabra $IP, pero aquí se asigna demasiado tarde, por lo que no tiene ningún efecto. Esta respuesta es básicamente incorrecta. IP=109.96.77.15 bash -c 'IFS=. set -- $IP; echo "$2"'no genera nada, ya sea en modo POSIX o no. Necesitarías IFS=. command eval 'set -- $IP', oIFS=. read a b c d << "$IP"
Stéphane Chazelas
44
Probablemente funcione para usted porque configuró IFS .en una de sus pruebas anteriores. Corre IP=1.2.3.4 bash -xc 'IFS=. set $IP; echo "$2"'y verás que no funciona. Y mira IP=1.2.3.4 bash -o posix -xc 'IFS=. set $IP; echo "\$1=$1 \$2=$2 IFS=$IFS"'para ilustrar el punto de @ chepner.
Stéphane Chazelas
2
la prisa hizo desperdicio, la respuesta aparentemente es incorrecta, y debería corregirse para que funcione, ya que todavía es más o menos como lo haría, solo con más código.
Lizardx
3
@ user1404316, ¿puede publicar el conjunto exacto de comandos que utilizó para probarlo? (Además, la versión de su shell, tal vez). Hay otros cuatro usuarios que le han dicho aquí en los comentarios que no funciona como está escrito en la respuesta. Con ejemplos
ilkkachu
5

No es lo más fácil , pero podrías hacer algo como:

$ 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

Eso debería funcionar en ksh93 (donde ese ${var//pattern/replacement}operador viene), bash4.3+, busybox sh, yash, mkshy zsh, aunque, por supuesto , en zsh, existen métodos mucho más simples . En versiones anteriores de bash, necesitaría eliminar las comillas internas. Funciona también con esas comillas internas eliminadas en la mayoría de los otros shells, pero no con ksh93.

Se supone que $IPcontiene una representación válida de cuatro decimales de una dirección IPv4 (aunque eso también funcionaría para representaciones de cuatro hexadecimales como 0x6d.0x60.0x4d.0xf(e incluso octal en algunos shells) pero generaría los valores en decimal). Si el contenido $IPproviene de una fuente no confiable, eso equivaldría a una vulnerabilidad de inyección de comando.

Básicamente, lo que estamos reemplazando cada .en $IPcon +256*(, terminamos la evaluación:

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

Así que estamos construyendo un entero de 32 bits de los 4 bytes como una dirección IPv4 en última instancia es (aunque con los bytes invertidos) ¹ y luego usando los >>, &operadores bit a bit para extraer los bytes correspondientes.

Usamos el ${param+value}operador estándar (aquí en el $-que se garantiza que siempre se establecerá) en lugar de solo valueporque de lo contrario el analizador aritmético se quejaría de paréntesis no coincidentes. El shell aquí puede encontrar el cierre ))para la apertura $((, y luego realizar las expansiones en el interior que resultarán en la expresión aritmética para evaluar.

Con $(((${IP//./"+256*("}))))&255))lugar, la carcasa podría tratar la segunda y tercera )s allí como el cierre ))para $((y informar de un error de sintaxis.

En ksh93, también puedes hacer:

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

bash, mksh, zsh Han copiado de ksh93 ${var/pattern/replacement}operador, pero no que la captura de grupos manipulación de la pieza. zshlo admite con una sintaxis diferente:

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

bashadmite alguna forma de manejo de grupos de captura en su operador de coincidencia regexp , pero no en ${var/pattern/replacement}.

POSIXY, usarías:

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

Para noglobevitar sorpresas negativas para valores $IPsimilares 10.*.*.*, la subshell para limitar el alcance de esos cambios en las opciones y $IFS.


¹ Una dirección IPv4 es solo un número entero de 32 bits y 127.0.0.1, por ejemplo, es solo una de las muchas representaciones textuales (aunque las más comunes). Esa misma dirección IPv4 típica de la interfaz de bucle invertido también se puede representar como 0x7f000001 o 127.1 (quizás una más apropiada aquí para decir que es la 1dirección en la red 127.0 / 8 clase A), o 0177.0.1, o las otras combinaciones de 1 a 4 números expresados ​​como octales, decimales o hexadecimales. Puede pasar todos esos, pingpor ejemplo, y verá que todos harán ping localhost.

Si no te importa el efecto secundario de establecer una variable temporal arbitraria (en este caso $n), en basho ksh93, o zsh -o octalzeroes, o lksh -o posix, simplemente puede convertir todas esas representaciones de nuevo a un número entero de 32 bits con:

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

Y luego extraiga todos los componentes con >>/ &combinaciones como arriba.

$ 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 enteros de 32 bits con signo para sus expresiones aritméticas, puede usar $((# n=32,...))allí para forzar el uso de números de 32 bits sin signo (y la posixopción para que reconozca constantes octales).

Stéphane Chazelas
fuente
Entiendo el gran concepto, pero nunca lo había visto ${-+antes. Tampoco puedo encontrar ninguna documentación al respecto. Funciona, pero tengo curiosidad por confirmar, ¿es solo para convertir la cadena en una expresión matemática? ¿Dónde puedo encontrar la definición formal? Además, las citas adicionales dentro de la sección de reemplazo de expansión de parámetros no funcionan en GNU bash, versión 4.1.2 (2), versión CentOS 6.6. Tuve que hacer esto en su lugarecho "$((${-+"(${IP//./+256*(}))))"}>>16&255))"
Levi Uzodike
1
@LeviUzodike, ver edición.
Stéphane Chazelas
4

Con zsh, puede anidar sustituciones de parámetros:

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

Esto no es posible en bash.

muru
fuente
1
En zsh, usted puede preferir${${(s(.))ip}[3]}
Stéphane Chazelas
4

Claro, juguemos al juego del 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
jthill
fuente
2
¿Qué es el "juego de elefantes"?
Comodín el
1
@Wildcard es un juego de palabras / broma elíptico, es una referencia tanto a las personas ciegas que describen una historia de elefantes, que hay muchas partes para ver que todos tendrán su propia opinión, y a la antigua costumbre de un rey que quería para dar obsequios con un mantenimiento ruinosamente costoso, suavizado a lo largo de los siglos a obsequios de valor simplemente dudoso que tienden a ser regulados por valor de entretenimiento. Ambos parecían aplicarse aquí :-)
hasta el
3

Con IP=12.34.56.78.

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

Y

#!/bin/bash
IP=$1

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

Descripción:

Uso de la expansión de parámetros ${IP// }para convertir cada punto en la ip en un paréntesis de apertura, un punto y un paréntesis de cierre. Al agregar un paréntesis inicial y un paréntesis de cierre, obtenemos:

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

que creará cuatro paréntesis de captura para la coincidencia de expresiones regulares en la sintaxis de prueba:

[[ $IP =~ $regex ]]

Eso permite la impresión de la matriz BASH_REMATCH sin el primer componente (toda la coincidencia de expresiones regulares):

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

La cantidad de paréntesis se ajusta automáticamente a la cadena coincidente. Entonces, esto coincidirá con un MAC o un EUI-64 de una dirección IPv6 a pesar de ser de diferente longitud:

#!/bin/bash
IP=$1

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

Utilizándolo:

$ ./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
Isaac
fuente
3

Aquí hay una pequeña solución hecha con POSIX /bin/sh(en mi caso eso es dash), una función que usa repetidamente la expansión de parámetros (así que no IFSaquí), y tuberías con nombre, e incluye la noglobopción por las razones mencionadas en la respuesta de 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}"

Esto funciona así:

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

Y con ipcambiado a109.*.*.*

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

El bucle que mantiene el contador de 4 iteraciones representa 4 secciones de una dirección IPv4 válida, mientras que las acrobacias con canalizaciones con nombre necesitan usar más secciones de la dirección IP dentro de la secuencia de comandos en lugar de tener variables atascadas en una subshell de un bucle.

Sergiy Kolodyazhnyy
fuente
Modifiqué tu respuesta para que no use una tubería y la agregué a mi respuesta existente .
G-Man dice 'reinstalar a Monica'
0

¿Por qué no usar una solución simple con awk?

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

Resultado $ 192 168 1 1

Trường Giang Nguyễn
fuente
-2
$ ip_=192.168.2.3

$ echo $ip_ 

192.168.2.3

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

192
Behrooz Mohamadi nasab
fuente
"... usando la expansión de parámetros ..."
Jeff Schaller
¿Y dónde están los otros tres?
G-Man dice 'reinstalar a Monica' el