Bash:提取IPv4地址的四个部分之一


9

我们可以使用语法${var##pattern}${var%%pattern}提取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##*.}

我们如何使用参数扩展来提取IPv4地址的第二部分或第三部分?

这是我的解决方案:我使用数组并更改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

此外,我已经写了使用一些解决方案awksed以及cut命令。

现在,我的问题是:是否有一个不使用数组和IFS更改的基于参数扩展的简单解决方案?


1
您只应IFSread那里设置:IFS=. read -a ArrIP<<<"$IP"
muru

1
不使用bash至少不使用多个变量。单参数扩展无法获得第二或第三部分。Zsh可以嵌套参数扩展,因此可能在那里存在。
muru

@muru能否请您提供Zsh解决方案?
sci9

4
您如何保证始终使用IP v4地址,而永远不会使用IP v6地址?
Mawg说恢复Monica

4
是否存在某些IFS=. read a b c d <<< "$IP"不可接受的原因(如果您使用的是Bash)?为什么必须通过参数扩展来完成?
ilkkachu

Answers:



7

您的问题陈述可能比您预期的要宽松一些。冒着漏洞的风险,这里提到的解决方案大师是

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

这有点笨拙。它定义了两个丢弃变量,并且不容易适应处理更多部分(例如,用于MAC或IPv6地址)。  Sergiy Kolodyazhnyy的答案激发了我对以上内容的概括:

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

此套sec1sec2sec3sec4,可与验证

printf 'Section 1: %s\n' "$sec1"
printf 'Section 2: %s\n' "$sec2"
printf 'Section 3: %s\n' "$sec3"
printf 'Section 4: %s\n' "$sec4"
  • while循环应易于理解-重复四次。
  • Sergiy 在我的第一个解决方案中(上面)选择slice了一个变量来代替last3last2
  • declare sec"$count"="value"是指派的方式sec1sec2sec3sec4count1234。有点像eval,但是更安全。
  • value"${slice%%.*}"是类似于值我原来的答复则分配给firstsecondthird

6

我确实意识到您专门要求了一个DID不是临时重新定义的解决方案IFS,但是我有一个不错的简单解决方案,您没有涉及,所以这里是:

IFS=. ; set -- $IP

这简短的命令将会把您的IP地址的元素在shell的位置参数 $1$2$3$4。但是,您可能需要先保存原始文件,IFS然后再将其还原。

谁知道?也许您会因为简洁和高效而重新考虑并接受此答案。

(这以前被误认为是IFS=. set -- $IP


5
我认为这行不通:如果IFS在同一命令行上进行更改,则当扩展同一命令行上的变量时,新值将不会生效。与x=1; x=2 echo $x
ilkkachu '18

6
@chepner,但是再一次,set它根本没有使用$IFS$IFS仅用于的单词拆分$IP,但是在这里它分配得太晚了,所以没有效果。这个答案基本上是错误的。IP=109.96.77.15 bash -c 'IFS=. set -- $IP; echo "$2"'不论是否处于POSIX模式,均不输出任何内容。你需要IFS=. command eval 'set -- $IP',或IFS=. read a b c d << "$IP"
斯特凡Chazelas

4
这可能对您有用,因为您.在先前的测试之一中已将IFS设置为。运行IP=1.2.3.4 bash -xc 'IFS=. set $IP; echo "$2"',您会发现它不起作用。并查看IP=1.2.3.4 bash -o posix -xc 'IFS=. set $IP; echo "\$1=$1 \$2=$2 IFS=$IFS"'以说明@chepner的观点。
斯特凡Chazelas

2
匆忙确实造成了浪费,答案显然是错误的,应该予以纠正以起作用,因为仅使用更多代码,这仍然是我的大致做法。
Lizardx

3
@ user1404316,您可以张贴用于测试的确切命令集吗?(也许还有您的Shell的版本。)在这里,还有其他四个用户在注释中告诉您说它不符合答案中的要求。有例子。
ilkkachu

5

这不是最简单的方法,但是您可以执行以下操作:

$ 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

这应该在ksh93的工作(如该${var//pattern/replacement}运营商来自),bash4.3以上,busybox的shyashmkshzsh,但当然zsh,有更简单的方法。在的旧版本中bash,您需要删除内引号。它也适用于在大多数其他shell中删除的那些内引号,但不适用于ksh93。

假定$IP包含一个有效的IPv4地址的四进制表示形式(尽管它也适用于类似四进制的表示形式0x6d.0x60.0x4d.0xf(在某些shell中甚至是八进制的表示形式,但将以十进制形式输出值)。如果的内容$IP来自不受信任的来源,则将构成命令注入漏洞。

基本上,当我们用替换每个.in $IP+256*(,我们最终进行评估:

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

因此,我们构建了这4个字节像IPv4地址的32位整数最终是(虽然与字节逆转)¹,然后使用>>&位运算符来提取相关的字节。

我们使用${param+value}标准运算符($-保证始终在此处设置),而不是仅仅value因为否则算术解析器会抱怨括号不匹配。此处的壳可以找到))开口的闭合$((然后在内部进行膨胀,这将导致算术表达式求值。

随着$(((${IP//./"+256*("}))))&255))相反,外壳会认为第二和第三)有S作为结束))$((,并报告语法错误。

在ksh93中,您还可以执行以下操作:

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

bashmkshzsh 已复制ksh93的的${var/pattern/replacement}运营商,但不是捕获组处理的一部分。zsh用不同的语法支持它:

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

bash确实在其regexp匹配运算符中支持某种形式的捕获组处理,但在${var/pattern/replacement}

POSIXly,您将使用:

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

noglob避免坏的惊喜的价值观$IP一样10.*.*.*,子shell来限制这些更改选项和范围$IFS


¹IPv4地址仅为32位整数,例如127.0.0.1只是许多(尽管是最常见的)文本表示形式之一。环回接口的相同的典型IPv4地址也可以表示为0x7f000001或127.1(也许更合适的说法1是127.0 / 8 A类网络上的地址)或0177.0.1,或其他1的组合最多4个数字,以八进制,十进制或十六进制表示。您可以将所有这些传递ping给例如,然后您将看到它们将对localhost进行ping操作。

如果您不介意$nbashksh93zsh -o octalzeroes或中设置任意临时变量(在此处)的副作用lksh -o posix,则可以使用以下命令将所有这些表示形式简单地转换回32位整数:

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

然后使用上述>>/ &组合提取所有组件。

$ 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对它的算术表达式使用带符号的32位整数,可以$((# n=32,...))在其中强制使用无符号的32位数字(以及posix用于识别八进制常量的选项)。


我了解这个大概念,但是我从未见过${-+。我也找不到任何文档。它有效,但是我很好奇要确认,是否只是将字符串转换为数学表达式?在哪里可以找到正式定义?另外,参数扩展替换部分中的多余引号在GNU bash版本4.1.2(2)-发行的CentOS 6.6中不起作用。我不得不这样做echo "$((${-+"(${IP//./+256*(}))))"}>>16&255))"
Levi Uzodike

1
@LeviUzodike,请参阅编辑。
StéphaneChazelas

4

使用zsh,您可以嵌套参数替换:

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

在bash中这是不可能的。


1
zsh,你可能更喜欢${${(s(.))ip}[3]}
斯特凡Chazelas

4

当然,让我们玩大象游戏。

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

要么

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

2
什么是“大象游戏”?
通配符

1
@Wildcard有点像椭圆双关语/笑话,既是描述盲人描述大象故事的参考,也有很多地方可以看一下每个人都有自己的看法,还参考了想要国王的古老习俗送礼时要付出昂贵的代价,经过几个世纪的发展才逐渐变软,变成了仅具有可疑价值的礼物,这些礼物往往被视为娱乐价值。.两者似乎都适用于这里:-)
jthill '18

3

IP=12.34.56.78

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

#!/bin/bash
IP=$1

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

描述:

使用参数扩展${IP// }将ip中的每个点转换为圆括号和圆括号。添加初始括号和结束括号,我们得到:

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

这将在测试语法中为正则表达式匹配创建四个捕获括号:

[[ $IP =~ $regex ]]

这样就可以在没有第一个组件的情况下打印BASH_REMATCH数组(整个正则表达式匹配):

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

括号的数量会自动调整为匹配的字符串。因此,尽管它们的长度不同,但这将匹配MAC或IPv6地址的EUI-64:

#!/bin/bash
IP=$1

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

使用它:

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

这是使用POSIX /bin/sh(在我的情况下为dash)完成的一个小型解决方案,该函数重复使用参数扩展(IFS此处未提供)和命名管道,并包含noglob用于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}"

这样工作:

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

ip改为109.*.*.*

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

4次迭代的循环保持计数器说明了有效IPv4地址的4个部分,而使用命名管道的杂技说明需要在脚本中进一步使用IP地址的部分,而不是将变量卡在循环的子外壳中。


我已经修改了您的答案,因此它不使用管道并将其添加到我现有的答案中
G-Man说'Resstate Monica''Feb

0

为什么不在awk中使用简单的解决方案?

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

结果 $ 192 168 1 1


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.