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á minha solução: eu uso um array 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 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 use alteração de array e IFS?
Assumindo o valor padrão do IFS, você extrai cada octeto em sua própria variável com:
Ou em um array com:
Percebo que você pediu especificamente uma solução que NÃO redefiniu temporariamente
IFS
, mas tenho uma solução doce e simples que você não cobriu, então aqui vai:Esse comando curto colocará os elementos do seu endereço IP nos parâmetros posicionais
$1
do shell ,$2
,$3
,$4
. No entanto, você provavelmente desejará primeiro salvar o originalIFS
e restaurá-lo posteriormente.Quem sabe? Talvez você reconsidere e aceite esta resposta por sua brevidade e eficiência.
(Isto foi previamente dado incorretamente como
IFS=. set -- $IP
)Sua declaração de problema pode ser um pouco mais liberal do que você pretendia. Correndo o risco de explorar uma brecha, aqui está a solução a que Muru aludiu :
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 a generalizar o que foi dito acima para isso:
Isso define
sec1
,sec2
,sec3
esec4
, que podem ser verificados comwhile
loop deve ser fácil de entender — itera quatro vezes.slice
como nome para uma variável que ocupa o lugar delast3
elast2
na minha primeira solução (acima).declare sec"$count"="value"
é uma maneira de atribuir asec1
, e quando ésec2
, e . É um pouco como , mas mais seguro.sec3
sec4
count
1
2
3
4
eval
value
,"${slice%%.*}"
, é análogo aos valores que minha resposta original atribui afirst
,second
ethird
.Não é o mais fácil , mas você pode fazer algo como:
Isso deve funcionar em ksh93 (de onde
${var//pattern/replacement}
vem esse operador),bash
4.3+, busyboxsh
, eyash
, embora, é claro , em , existam abordagens muito mais simples . Em versões mais antigas de , você precisaria remover as aspas internas. Ele também funciona com as aspas internas removidas na maioria dos outros shells, mas não no ksh93.mksh
zsh
zsh
bash
Isso pressupõe que
$IP
contém uma representação quad-decimal válida de um endereço IPv4 (embora isso também funcione para representações quad-hexadecimais como0x6d.0x60.0x4d.0xf
(e até octal em alguns shells), mas produziria os valores em decimal). Se o conteúdo$IP
vier de uma fonte não confiável, isso equivaleria a uma vulnerabilidade de injeção de comando.Basicamente, como vamos substituindo every
.
in$IP
por+256*(
, acabamos avaliando:Portanto, estamos construindo um inteiro de 32 bits a partir desses 4 bytes, como um endereço IPv4 é (embora com os bytes invertidos)¹ 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 sempre será definido) em vez de apenasvalue
porque, caso contrário, o analisador aritmético reclamaria sobre parênteses incompatíveis. O shell aqui pode encontrar o fechamento))
para a abertura$((
e, em seguida, realizar as expansões internas que resultarão na expressão aritmética a ser avaliada.Em
vez disso, o shell trataria o segundo e o terceiro s como o$(((${IP//./"+256*("}))))&255))
)
fechamento e relataria um erro de sintaxe.))
$((
No ksh93, você também pode fazer:
bash
,mksh
,zsh
copiaram o${var/pattern/replacement}
operador de ksh93, mas não a parte de manipulação do grupo de captura.zsh
o suporta com uma sintaxe diferente:bash
oferece suporte a 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:
O
noglob
para evitar surpresas ruins para valores de$IP
como10.*.*.*
, o subshell para limitar o escopo dessas alterações para 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 a mais comum) 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 mais apropriado aqui para dizer que é o
1
endereço na rede 127.0/8 classe A), ou 0177.0.1, ou as outras combinações de 1 a 4 números expressos como octal, decimal ou hexadecimal. Você pode passar todos eles paraping
, por exemplo, e verá que todos farão ping no localhost.Se você não se importa com o efeito colateral de definir uma variável temporária arbitrária (aqui
$n
), embash
ouksh93
ouzsh -o octalzeroes
oulksh -o posix
, você pode simplesmente converter todas essas representações de volta para um inteiro de 32 bits com:E então extraia todos os componentes com
>>
/&
combinações como acima.mksh
usa números inteiros de 32 bits assinados para suas expressões aritméticas, você pode usá$((# n=32,...))
-los para forçar o uso de números de 32 bits não assinados (e aposix
opção de reconhecer constantes octais).Com zsh, você pode aninhar substituições de parâmetros:
Isso não é possível no bash.
Claro, vamos jogar o jogo do elefante.
ou
Com
IP=12.34.56.78
.E
Descrição:
Uso de 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:que criará quatro parênteses de captura para a correspondência regex na sintaxe de teste:
Isso permite a impressão da matriz BASH_REMATCH sem o primeiro componente (toda a correspondência de regex):
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 terem comprimentos diferentes:
Usando isso:
Aqui está uma pequena solução feita com POSIX
/bin/sh
(no meu casodash
), uma função que usa repetidamente a expansão de parâmetros (portanto, nãoIFS
aqui) e pipes nomeados, e incluinoglob
a opção pelos motivos mencionados na resposta de Stephane .Isso funciona assim:
E com
ip
mudou para109.*.*.*
O loop mantendo o contador de 4 iterações é responsável por 4 seções de um endereço IPv4 válido, enquanto acrobacias com pipes nomeados representam a necessidade de usar mais seções de endereço IP dentro do script, em vez de ter variáveis presas em um subshell de um loop.
Por que não usar uma 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