据我所知,shell中的while循环是在子shell中执行的,因此它们不能修改循环之外的变量。
我正在编写一个 shell 脚本,我想将机器的所有内部 IP 存储到一个变量中,因此使用 for 循环处理该变量,以使用 iptables 对它们进行一一过滤。
我可以写这段代码:
ip route show default | awk '{ print $5 }' | while read line; do
ip address show dev ${line} scope global | awk '/inet / {sub(/\/.*/, "", $2); print $2}' | while read line; do
echo "${line} "
done
done
输出:
10.17.0.49
192.168.1.4
我的问题是:
如何将 while 循环发出的所有这些行存储到单个变量中(因为 while 循环变量是易失性的)?
答案1
要打印默认路由中涉及的接口的所有全局范围本地地址,我将使用 JSON 格式,该格式可以以更可靠的方式以编程方式处理:
perl -MJSON -le '
$default_routes = decode_json(qx(ip -j route show default));
for (@$default_routes) {$devs{$_->{dev}} = 1}
$addresses = decode_json(qx(ip -j address show));
for (@$addresses) {
if ($devs{$_->{ifname}}) {
for (@{$_->{addr_info}}) {
print $_->{local} if $_->{scope} eq "global";
}
}
}'
或者同样使用jq
:
ip -j address show |
jq -r --argjson devs "$(
ip -j route show default|jq 'map({"key":.dev})|from_entries'
)" '.[]|select(.ifname|in($devs)).addr_info[]|
select(.scope == "global").local'
(它需要相对较新的版本来iproute2
支持 JSON 输出)。
要将其(或者更常见的是某个命令输出的每一行)放入数组中bash
,请使用:
readarray -t array < <(
that-command above
)
如果目的是获取通过默认路由发出的数据包将获得的源 IP 地址,请参见示例我的答案到如何获取我自己的 IP 地址并将其保存到 shell 脚本中的变量中?。
答案2
为了简洁地回答提出的实际问题,无论如何存储 while 循环的输出的上下文:
将输出存储到文件中,例如/path/to/file
:
while command; do
thing
done > /path/to/file
将输出存储到变量中,例如my_var
:
my_var="$(while command; do thing; done)"
答案3
我知道有两种方法可以做到这一点(在bash中):
- 使用
< <(command)
称为进程替换(这不会通过管道将内容传递到循环while
)。 - 使用
{ code or commands }
调用的命令分组。
因此,如果您想将所有 IP 存储在变量(或本例中为数组)中,可以使用以下代码:
流程替代:
process_subs () {
local array=()
while read -r line; do
while read -r line; do
array+=("$line")
done < <(ip address show dev "${line}" scope global | awk '/inet / {sub(/\/.*/, "", $2); print $2}')
done < <(ip route show default | awk '{ print $5 }')
echo Array: "${array[@]}"
echo Length: ${#array[@]}
}
命令分组:
command_grouping () {
ip route show default | awk '{ print $5 }' | while read -r line; do
ip address show dev "${line}" scope global | awk '/inet / {sub(/\/.*/, "", $2); print $2}' | {
local array=()
while read -r line; do
array+=("$line")
done
echo Array: "${array[@]}"
echo Length: ${#array[@]}
}
done
}