原始代码中最直接的问题是使用vpn_remote 作为我们正在为其搜索键的数组的名称,而不是vpn_remote_protocol 或vpn_remote_port。 下一个问题是使用"${!array[*]}",它生成一个将所有键连接在一起的单个字符串;就像for item in "two words" 只运行一次,two words 作为分配给项目的值,在这里循环"${!vpn_remote_port[*]}" 将运行for index in...只运行一次, "0 1 2" 作为项目。 (相比之下,如果变量名不正确,"" 是 "${!array[@]}" 的计算结果;在被视为 0 的数字上下文中,这就是您看到第一项而没有其他项的原因。
这个故事的寓意
在不了解差异和明确原因的情况下使用 "${!array[@]}",切勿使用 "${!array[*]}"、${!array[*]} 或 ${!array[@]}。
因此:
#!/bin/bash
protocol="udp,tcp-client,udp,"
port="1111,2222,3333,"
IFS=',' read -a vpn_remote_protocol <<< "${protocol}"
IFS=',' read -a vpn_remote_port <<< "${port}"
for index in "${!vpn_remote_port[@]}"; do
echo "iptables -A OUTPUT -o docker_int -p ${vpn_remote_protocol[$index]} --dport ${vpn_remote_port[$index]} -j ACCEPT"
done
...当run in an online interpreter 时,作为输出发出:
iptables -A OUTPUT -o docker_int -p udp --dport 1111 -j ACCEPT
iptables -A OUTPUT -o docker_int -p tcp-client --dport 2222 -j ACCEPT
iptables -A OUTPUT -o docker_int -p udp --dport 3333 -j ACCEPT
深入探讨:为什么其他替代方案都错了
为什么是"${!array[@]}"?让我们比较每一个的行为,但是在一个更具挑战性的情况下,当我们的键不是纯数字时(尽管我们可能通过使用不正确的方法来生成错误,即使是纯数字的键,如果我们有数字我们的IFS 值):
declare -A array=(
["first key"]="first value"
["second key"]="second value"
["third key"]="third value"
)
printf '%s\n' 'Using "${!array[@]}":'
printf ' - %s\n' "${!array[@]}"
printf '\n%s\n' 'Using "${!array[*]}":'
printf ' - %s\n' "${!array[*]}"
printf '\n%s\n' 'Using ${!array[*]}:'
printf ' - %s\n' ${!array[*]}
printf '\n%s\n' 'Using ${!array[@]}:'
printf ' - %s\n' ${!array[@]}
...为此我们see the following output:
Using "${!array[@]}":
- second key
- third key
- first key
Using "${!array[*]}":
- second key third key first key
Using ${!array[*]}:
- second
- key
- third
- key
- first
- key
Using ${!array[@]}:
- second
- key
- third
- key
- first
- key
所以:
-
"${!arrayname[*]}" 是错误的,因为它扩展为一个字符串,其中所有键都连接在一起(IFS 中的第一个字符——默认情况下是空格——它们之间)。
-
${!arrayname[*]} 和 ${!arrayname[@]} 都是错误的,因为键在被视为项目列表之前会受到分词和全局扩展的影响
-
"${!arrayname[@]}" 做正确的事,将每个键视为一个单词,不受可能首先破坏它的操作的影响。