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


9

Podemos usar a sintaxe ${var##pattern}e ${var%%pattern}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á minha solução: eu uso uma matriz e altero 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 tenho escrito algumas soluções que utilizam os awk, sede cutcomandos.

Agora, minha pergunta é: Existe uma solução mais simples com base na expansão de parâmetros que não usa mudança de matriz e IFS?


1
Você só deve definir IFSpara readlá:IFS=. read -a ArrIP<<<"$IP"
muru

1
Não no bash sem usar várias variáveis, pelo menos. Uma expansão de parâmetro único não pode obter o segundo ou terceiro componentes. O Zsh pode aninhar expansões de parâmetros, portanto pode ser possível lá.
muru

@muru Você poderia fornecer a solução Zsh?
sci9

4
Que garantia você tem de que sempre estará lidando com endereços IP v4 e nunca terá um endereço IP v6?
Mawg diz que restabelece Monica

4
Existe algum motivo que IFS=. read a b c d <<< "$IP"não é aceitável (se você estiver usando o Bash)? Por que isso precisa ser feito com a expansão de parâmetros?
precisa saber é

Respostas:


16

Supondo que o valor padrão do IFS você extraia cada octeto em sua própria variável com:

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

Ou em uma matriz com:

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

2
+1. Parece-me que este é o método mais simples e direto que cumpre as restrições dos POs.
Digital Trauma

7

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

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

Isso é um pouco 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 a generalizar o que foi exposto acima:

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

Isso define sec1, sec2, sec3e sec4, o que pode ser verificado 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 whileloop deve ser fácil de entender - itera quatro vezes.
  • Sergiy escolheu slicecomo o nome de uma variável que substitui last3e last2na minha primeira solução (acima).
  • declare sec"$count"="value"é uma maneira de atribuir a sec1, sec2, sec3e sec4 quando counté 1, 2, 3e 4. É um pouco parecido eval, mas mais seguro.
  • A value, "${slice%%.*}", é análogo ao valoriza meus cessionários resposta original para first, seconde third.

6

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

IFS=. ; set -- $IP

Esse comando curta vai colocar os elementos do seu endereço IP no shell de parâmetros posicionais $1 , $2, $3, $4. No entanto, você provavelmente desejará primeiro salvar o original IFSe restaurá-lo posteriormente.

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

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


5
Eu não acho que funcione: se você alterar IFSna mesma linha de comando, o novo valor não terá efeito quando as variáveis ​​na mesma linha de comando forem expandidas. O mesmo que comx=1; x=2 echo $x
ilkkachu

6
@chepner, mas, novamente, setnão utiliza $IFSnada, $IFSé usado apenas para a divisão de palavras $IP, mas aqui é atribuído tarde demais, de modo que não tem efeito. Esta resposta está basicamente errada. IP=109.96.77.15 bash -c 'IFS=. set -- $IP; echo "$2"'não produz nada, seja no modo POSIX ou não. Você precisaria IFS=. command eval 'set -- $IP', ouIFS=. read a b c d << "$IP"
Stéphane Chazelas

4
Provavelmente funciona para você porque você configurou o IFS .em um dos seus testes anteriores. Execute IP=1.2.3.4 bash -xc 'IFS=. set $IP; echo "$2"'e você verá que não funciona. E veja IP=1.2.3.4 bash -o posix -xc 'IFS=. set $IP; echo "\$1=$1 \$2=$2 IFS=$IFS"'para ilustrar o argumento de @ chepner.
Stéphane Chazelas

2
a pressa desperdiçou, a resposta está aparentemente errada e deve ser corrigida para funcionar, pois ainda é mais ou menos como eu faria isso, apenas com mais código.
Lizardx

3
@ user1404316, você pode postar o conjunto exato de comandos que você usou para testá-lo? (Além da versão do seu shell, talvez.) Há outros quatro usuários que lhe disseram aqui nos comentários que ele não funciona como está escrito na resposta. Com exemplos.
ilkkachu

5

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 no ksh93 (onde esse ${var//pattern/replacement}operador vem), bash4.3+, busybox sh, yash, mkshe zsh, embora, naturalmente , em zsh, existem abordagens muito mais simples . Nas versões anteriores bash, você precisaria remover as aspas internas. Também funciona com as aspas internas removidas na maioria dos outros shells, mas não no ksh93.

Isso pressupõe que $IPcontenha 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 alguns shells), mas produziria os valores em decimal). Se o conteúdo $IPvier de uma fonte não confiável, isso equivaleria a uma vulnerabilidade de injeção de comando.

Basicamente, como estamos substituindo cada .no $IPcom +256*(, acabamos avaliação:

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

Então, nós estamos construindo um inteiro de 32 bit fora dessas 4 bytes como um endereço IPv4 em última análise, é (embora com os bytes invertida) ¹ e, em seguida, usando os >>, &operadores bit a bit para extrair os bytes relevantes.

Usamos o ${param+value}operador padrão (aqui no $-qual é garantido que seja sempre definido) em vez de apenas valueporque, caso contrário, o analisador aritmético reclamaria de parênteses incompatíveis. O shell aqui pode encontrar o fechamento ))para a abertura $((e , em seguida, executar as expansões dentro que irá resultar na expressão aritmética para avaliar.

Com $(((${IP//./"+256*("}))))&255))vez, o shell trataria o segundo e terceiro )s lá como o fechamento ))de $((e relatar um erro de sintaxe.

No ksh93, você também pode:

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

bash, mksh, zsh Copiou de ksh93 ${var/pattern/replacement}operador, mas não que a captura do grupo manipulação parte. zshsuporta com uma sintaxe diferente:

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

bashsuporta alguma forma de manipulação de grupo de captura em seu operador de correspondência regexp , mas não em ${var/pattern/replacement}.

POSIXly, você usaria:

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

Para noglobevitar surpresas ruins para valores de $IPlike 10.*.*.*, o subshell para limitar o escopo dessas alterações às opções e $IFS.


Address Um endereço IPv4 é apenas um número inteiro de 32 bits e 127.0.0.1, por exemplo, é apenas uma das muitas (embora as mais comuns) representações textuais. O 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 1endereço na rede classe 127.0 / 8 classe A), ou 0177.0.1 ou as outras combinações de 1 a 4 números expressos em octal, decimal ou hexadecimal. Você pode passar todos para, pingpor exemplo, e verá que todos executarão ping no host local.

Se você não se importa com o efeito colateral de definir uma variável temporária arbitrária (aqui $n), em bashou ksh93ou zsh -o octalzeroesou lksh -o posix, você pode simplesmente converter todas essas representações em um número inteiro de 32 bits com:

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

E então 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

mkshusa números inteiros de 32 bits assinados para suas expressões aritméticas, você pode usá $((# n=32,...))-lo para forçar o uso de números de 32 bits não assinados (e a posixopção para reconhecer constantes octais).


Eu entendo o grande conceito, mas nunca vi isso ${-+antes. Também não consigo encontrar nenhuma documentação. Funciona, mas estou curioso para confirmar, é apenas para transformar a string em uma expressão matemática? Onde posso encontrar a definição formal? Além disso, as cotações extras dentro da seção de substituição de expansão de parâmetros não funcionam no GNU bash, versão 4.1.2 (2), versão CentOS 6.6. Eu tive que fazer issoecho "$((${-+"(${IP//./+256*(}))))"}>>16&255))"
Levi Uzodike 03/03

1
@LeviUzodike, veja editar.
Stéphane Chazelas

4

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

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

Isso não é possível no bash.


1
Em zsh, você pode preferir${${(s(.))ip}[3]}
Stéphane Chazelas

4

Claro, vamos jogar o jogo do elefante.

$ 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
O que é o "jogo do elefante"?
Curinga

1
@Wildcard é uma espécie de trocadilho / piada elíptica, é uma referência tanto para pessoas cegas que descrevem uma história de elefantes, que há partes para observar que todos terão suas próprias opiniões e ao antigo costume de um rei que queria para dar presentes com manutenção ruinosamente caras, suavizado ao longo dos séculos para presentes de valor meramente duvidosa que tendem a ser regifted para valor de entretenimento .. Ambos pareciam aplicar-se aqui :-)
jthill

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:

O uso da expansão de parâmetro ${IP// }para converter cada ponto no ip em 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 regex na sintaxe de teste:

[[ $IP =~ $regex ]]

Isso permite a impressão da matriz BASH_REMATCH sem o primeiro componente (toda a regex corresponde):

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

A quantidade de parênteses é ajustada automaticamente para a sequência correspondente. Portanto, isso corresponderá a um MAC ou a EUI-64 de um endereço IPv6, apesar de serem de tamanho diferente:

#!/bin/bash
IP=$1

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

Usando isso:

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

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âmetros (então não IFSaqui) e pipes nomeados, e inclui a noglobopção pelos motivos mencionados na 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 da seguinte maneira:

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

E com ipalterado para109.*.*.*

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

O contador de manutenção de loops de 4 iterações é responsável por 4 seções de um endereço IPv4 válido, enquanto as acrobacias com pipes nomeados são necessárias para usar ainda mais as seções do endereço IP no script, em vez de ter variáveis ​​presas no subshell de um loop.


Modifiquei sua resposta para que ela não use um pipe e a adicionei à minha resposta existente .
G-Man diz 'Reinstate Monica'

0

Por que não usar solução simples com o awk?

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

Resultado $ 192 168 1 1


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.