Bash: Extraia uma das quatro seções de um endereço IPv4

9

Podemos usar a sintaxe ${var##pattern} e ${var%%pattern} para extrair a última e a primeira seção de um endereço 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##*.}

Como podemos extrair a segunda ou terceira seção de um endereço IPv4 usando a expansão de parâmetros?

Aqui está a minha solução: Eu uso uma matriz e mudo a variável 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

Também escrevi algumas soluções usando os comandos awk , sed e cut .

Agora, minha pergunta é: Existe uma solução mais simples baseada na expansão de parâmetros que não usa matriz e alteração do IFS?

    
por sci9 29.01.2018 / 04:58

9 respostas

16

Assumindo o valor padrão do IFS, você extrai cada octeto em sua própria variável com:

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

Ou em uma matriz com:

A=(${IP//./ })
    
por 29.01.2018 / 13:57
7

Sua declaração de problema pode ser um pouco mais liberal do que você pretendia. Correndo o risco de explorar uma lacuna, aqui está a solução muru mencionada :

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

Isso é um tanto desajeitado. Ele define duas variáveis descartáveis, e não é prontamente adaptado para lidar com mais seções (por exemplo, para um endereço MAC ou IPv6). A resposta de Sergiy Kolodyazhnyy me inspirou para generalizar o acima para isso:

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

Define sec1 , sec2 , sec3 e sec4 , que podem ser verificados com

printf 'Section 1: %s\n' "$sec1"
printf 'Section 2: %s\n' "$sec2"
printf 'Section 3: %s\n' "$sec3"
printf 'Section 4: %s\n' "$sec4"
  • O loop while deve ser fácil de entender - itera quatro vezes.
  • Sergiy escolheu slice como o nome de uma variável que toma o lugar de last3 e last2 na minha primeira solução (acima).
  • declare sec"$count"="value" é uma maneira para atribuir a sec1 , sec2 , sec3 e sec4 quando count é 1 , 2 , 3 e 4 . É um pouco como eval , mas é mais seguro.
  • O value , "${slice%%.*}" , é análogo aos valores minha resposta original atribui a first , second e third .
por 29.01.2018 / 05:26
5

Eu sei que você especificamente pediu uma solução que NÃO redefiniu temporariamente IFS , mas eu tenho uma solução doce e simples que você não cobriu, então aqui vai:

IFS=. ; set -- $IP

Esse curto comando colocará os elementos do seu endereço IP nos parâmetros posicionais do shell $1 , $2 , $3 , $4 . No entanto, você provavelmente desejará salvar primeiro o IFS original e restaurá-lo depois.

Quem sabe? Talvez você reconsidere e aceite essa resposta por sua brevidade e eficiência.

(Isso foi anteriormente incorretamente dado como IFS=. set -- $IP )

    
por 29.01.2018 / 05:42
4

Com zsh, você pode aninhar substituições de parâmetro:

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

Isso não é possível no bash.

    
por 29.01.2018 / 05:49
4

Claro, vamos jogar o jogo dos elefantes.

$ 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
    
por 29.01.2018 / 17:49
4

Não é o mais fácil , mas você pode fazer 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

Isso deve funcionar em ksh93 (de onde vem esse operador ${var//pattern/replacement} ), bash , mksh e zsh , embora, é claro, em zsh , existem abordagens muito mais simples .

Isso pressupõe que $IP contenha uma representação quad-decimal válida de um endereço IPv4 (embora isso também funcione para representações quad-hexadecimais como 0x6d.0x60.0x4d.0xf (e até octal em algumas shells), mas produziria os valores em decimal) . Se o conteúdo de $IP vier de uma fonte não confiável, isso equivaleria a uma vulnerabilidade de injeção de comando.

Basicamente, como estamos substituindo cada . em $IP por +256*( , acabamos avaliando:

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

Portanto, estamos construindo um inteiro de 32 bits desses 4 bytes, como um endereço IPv4 é (embora com os bytes invertidos) ¹ e, em seguida, usando os operadores >> , & bitwise para extrair os bytes relevantes.

No ksh93, você também pode fazer:

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

bash , mksh , zsh copiou o operador ${var/pattern/replacement} do ksh93, mas não essa parte de manipulação do grupo de captura. zsh suporta-o com uma sintaxe diferente:

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

bash suporta alguma forma de manipulação de grupos de captura em seu operador de correspondência de expressões regulares , mas não em ${var/pattern/replacement} .

POSIXly, você usaria:

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

O noglob para evitar surpresas ruins para valores de $IP como 10.*.*.* , a subshell para limitar o escopo dessas alterações às opções e $IFS .

¹ Um endereço IPv4 é apenas um inteiro de 32 bits e 127.0.0.1, por exemplo, é apenas uma das muitas (embora as mais comuns) representações textuais. Esse mesmo endereço IPv4 típico da interface de loopback também pode ser representado como 0x7f000001 ou 127.1 (talvez um mais apropriado aqui para dizer que é o endereço 1 na rede classe A 127.0 / 8), ou 0177.0.1, ou o outro combinações de 1 a 4 números expressos em octal, decimal ou hexadecimal. Você pode passar todos aqueles para ping , por exemplo, e você verá que todos eles farão ping localhost.

Se você não se importar com o efeito colateral de definir uma variável temporária arbitrária (aqui $n ), em bash ou ksh93 ou zsh -o octalzeroes ou lksh -o posix , você pode simplesmente converter todas essas representações de volta para um inteiro de 32 bits com:

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

E, em seguida, extraia todos os componentes com combinações >> / & , como acima.

$ 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

mksh usa inteiros assinados de 32 bits para suas expressões aritméticas, você pode usar $((# n=32,...)) para forçar o uso de números de 32 bits não assinados (e a opção posix para reconhecer constantes octal).

    
por 29.01.2018 / 16:22
3

com 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}"

Descrição:

Uso da expansão de parâmetro ${IP// } para converter cada ponto no ip para um parêntese de abertura um ponto e um parêntese de fechamento. Adicionando um parêntese inicial e um parêntese de fechamento, obtemos:

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

que criará quatro parênteses de captura para a correspondência de expressão regular na sintaxe de teste:

[[ $IP =~ $regex ]]

Isso permite a impressão da matriz BASH_REMATCH sem o primeiro componente (toda a correspondência de expressão regular):

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

A quantidade de parênteses é ajustada automaticamente para a string correspondente. Portanto, isso corresponderá a um MAC ou a um EUI-64 de um endereço IPv6, apesar de eles terem duração diferente:

#!/bin/bash
IP=$1

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

Usando:

$ ./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
    
por 30.01.2018 / 03:04
3

Aqui está uma pequena solução feita com POSIX /bin/sh (no meu caso é dash ), uma função que usa repetidamente a expansão de parâmetro (portanto, não IFS aqui) e pipes nomeados e inclui a opção noglob razões mencionadas em a resposta 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}"

Isso funciona assim:

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

E com ip alterado para 109.*.*.*

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

O contador de 4 iterações é responsável por 4 seções de um endereço IPv4 válido, enquanto acrobacias com pipes nomeados respondem pela necessidade de usar seções de endereço IP dentro do script em vez de ter variáveis presas em um subshell de um loop.

    
por 30.01.2018 / 07:45
0
$ ip_=192.168.2.3

$ echo $ip_ 

192.168.2.3

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

192
    
por 29.01.2018 / 13:26