我们可以使用语法${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
此外,我还使用 、 和 命令编写了一些awk
解决sed
方案cut
。
现在,我的问题是:是否有基于不使用数组和 IFS 更改的参数扩展的更简单的解决方案?
假设 IFS 的默认值,您将每个八位字节提取到它自己的变量中:
或者进入一个数组:
我确实意识到您专门要求了一个没有暂时重新定义的解决方案
IFS
,但是我有一个您没有涵盖的甜蜜而简单的解决方案,所以这里是:这个简短的命令会将你的 IP 地址的元素放在 shell 的位置参数
$1
,$2
,$3
, 中$4
。但是,您可能希望先保存原始IFS
文件,然后再恢复。谁知道?也许您会因其简洁和高效而重新考虑并接受此答案。
(这以前被错误地给出为
IFS=. set -- $IP
)您的问题陈述可能比您预期的要宽松一些。冒着利用漏洞的风险,这里是muru 提到的解决方案:
这有点笨拙。它定义了两个一次性变量,并且不容易适应处理更多部分(例如,对于 MAC 或 IPv6 地址)。 Sergiy Kolodyaznyy 的回答启发我将上述内容概括为:
这设置
sec1
,sec2
,sec3
和sec4
, 可以用while
四次。slice
代替我的第一个解决方案(上图)。last3
last2
declare sec"$count"="value"
是一种分配给sec1
,和 的方法,sec2
,和. 这有点像,但更安全。sec3
sec4
count
1
2
3
4
eval
value
,"${slice%%.*}"
类似于我最初的答案分配给first
,second
和的值third
。不是最简单的,但您可以执行以下操作:
这应该适用于 ksh93(该
${var//pattern/replacement}
运算符来自哪里)、 4.3+ 、bash
busybox和sh
,当然,在 中,有更简单的方法。在旧版本中,您需要删除内引号。它也适用于在大多数其他 shell 中删除的那些内部引号,但不适用于 ksh93。yash
mksh
zsh
zsh
bash
假设
$IP
包含 IPv4 地址的有效四进制表示(尽管这也适用于四十六进制表示0x6d.0x60.0x4d.0xf
(在某些 shell 中甚至是八进制),但会以十进制输出值)。如果 的内容$IP
来自不受信任的来源,那将构成命令注入漏洞。基本上,当我们用 替换每个
.
in$IP
时+256*(
,我们最终会评估:所以我们从这 4 个字节中构造一个 32 位整数,就像最终的 IPv4 地址一样(尽管字节颠倒了)¹,然后使用
>>
,&
位运算符来提取相关字节。我们使用
${param+value}
标准运算符(这里$-
保证始终设置)而不是仅仅value
因为算术解析器会抱怨括号不匹配。这里的 shell 可以找到 open 的关闭))
,$((
然后在里面执行扩展,这将导致算术表达式求值。相反,
shell 会将$(((${IP//./"+256*("}))))&255))
)
那里的第二个和第三个 s 视为关闭))
并$((
报告语法错误。在 ksh93 中,您还可以执行以下操作:
bash
,mksh
,zsh
复制了 ksh93 的${var/pattern/replacement}
操作符,但没有复制捕获组处理部分。zsh
用不同的语法支持它:bash
在其正则表达式匹配运算符中支持某种形式的捕获组处理,但在${var/pattern/replacement}
.POSIXly,你会使用:
为避免对like的
noglob
值造成意外,子shell 将这些更改的范围限制为 options 和.$IP
10.*.*.*
$IFS
¹ IPv4 地址只是一个 32 位整数,例如 127.0.0.1 只是众多(尽管是最常见的)文本表示形式之一。环回接口的同一个典型 IPv4 地址也可以表示为 0x7f000001 或 127.1(这里可能更合适的说法是它是
1
127.0/8 A 类网络上的地址),或 0177.0.1,或 1 的其他组合以八进制、十进制或十六进制表示的 4 个数字。例如,您可以将所有这些传递给ping
您,您会看到它们都将 ping localhost。如果您不介意在or或or中设置任意临时变量(此处
$n
)的副作用,您可以简单地将所有这些表示转换回 32 位整数:bash
ksh93
zsh -o octalzeroes
lksh -o posix
>>
然后用上面的/&
组合提取所有组件。mksh
使用带符号的 32 位整数作为其算术表达式,您可以使用$((# n=32,...))
它来强制使用无符号的 32 位数字(以及posix
识别八进制常量的选项)。使用 zsh,您可以嵌套参数替换:
这在 bash 中是不可能的。
当然,让我们玩大象游戏。
或者
与
IP=12.34.56.78
.和
描述:
使用参数扩展
${IP// }
将 ip 中的每个点转换为左括号一个点和一个右括号。添加一个初始括号和一个右括号,我们得到:这将为测试语法中的正则表达式匹配创建四个捕获括号:
这允许在没有第一个组件(整个正则表达式匹配)的情况下打印数组 BASH_REMATCH:
括号的数量会自动调整为匹配的字符串。因此,这将匹配 IPv6 地址的 MAC 或 EUI-64,尽管它们的长度不同:
使用它:
这是一个使用 POSIX 完成的小解决方案
/bin/sh
(在我的情况下是dash
),一个重复使用参数扩展的函数(所以IFS
这里没有)和命名管道,并且由于Stephane 的回答noglob
中提到的原因包括选项。这是这样工作的:
并
ip
更改为109.*.*.*
4 次迭代的循环保持计数器占有效 IPv4 地址的 4 部分,而使用命名管道的杂技需要在脚本中进一步使用 ip 地址部分,而不是将变量卡在循环的子外壳中。
为什么不使用 awk 的简单解决方案?
$ IP="192.168.1.1" $ echo $IP | awk -F '.' '{ print $1" "$2" "$3" "$4;}'
结果
$ 192 168 1 1