我编写了一个 bash 脚本,它实时读取 wifi 使用情况,并将其写入$HOME
系统连接到的每个 wifi 的名为“usage”的文件(在目录中)。脚本工作得很好,但有两件事让我困扰:
它的“磁盘写入总数”不断增加。这是正常/预期的吗,因为
usage
每当我的系统连接到 wifi 时,我的脚本就会在 while 循环中不断更新文件。它使用的总内存固定为 496 KB。它在脚本运行时不断创建大量临时文件,这些文件也会立即被删除。在下图中,请注意以 开头的文件
sed*
是那些临时文件。一旦我刷新该目录,这些文件就会消失,并且新的文件会不断出现。
我的脚本有问题吗?或者这种行为是正常的吗?
剧本:
#!/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
filepath="$HOME/usage" #since cron jobs have a current working directory usually set as home
date_check() {
if [ -f $filepath ]
then
cur_date=$(date +%s)
last_mod_date=$(date -r $filepath +%s)
if [ $(date +%m) == 02 ] && [ $cycle_date -gt 28 ]
then
cycle_date=28
fi
cyc_date=$(date -d "$(date +%Y)-$(date +%m)-$cycle_date" +%s)
if [[ $cyc_date -gt $last_mod_date ]] && [[ $cyc_date -lt $cur_date ]]
then
rm $filepath
fi
fi
}
wifi_record_update() {
availability=""
for dev in $net_interface
do
if [ $(iwconfig wlp0s20f3 | grep ESSID | cut -d: -f2) != "off/any" ]
then
availability="yes"
ssid=$(iwconfig wlp0s20f3 | grep ESSID | cut -d: -f2)
mac=$(iwconfig wlp0s20f3 | grep "Access Point" | tr -s ' ' | cut -d ' ' -f7)
if grep -Fq "$mac" $filepath
then
used=$(grep "$mac" $filepath | cut -d ' ' -f3)
else
echo "$mac $ssid 0" >> $filepath
fi
break
fi
done
sed -i "/off\/any/d" $filepath ###to delete garbage records which sometimes get collected with mac name set as off/any
}
########### main
##### identifying all wifi network adapter interfaces
net_interface=()
for dev in $(ls /sys/class/net/);
do
if [ -d "/sys/class/net/$dev/wireless/" ]
then
net_interface+=("$dev")
fi
done
##### getting cycle date
if [ -f $filepath ]
then
cycle_date=$(head -n 1 $filepath)
else
cycle_date=1 #default date at the start of first ever run of this script
fi
##### deletes $filepath file if the cycle date is passed
date_check;
if ! [[ -f $filepath ]]
then
echo $cycle_date > $filepath
fi
##### main while loop
while true
do
wifi_record_update
while [ "$availability" != "" ]
do
prev=$cur
cur=$(cat /sys/class/net/$dev/statistics/rx_bytes)
add=$((cur-prev))
echo "$(awk -v ad=$add -v ma=$mac '{if ($1==ma) {$3=$3+ad}; print $0 }' $filepath)" > $filepath ### updating the used value
wifi_record_update
done
done
exit 0
https://github.com/atul-g/bash_utility_scripts/blob/master/wifi_usage/wifi_usage.sh
答案1
是的,你的 shell 脚本有问题。嗯,有几件事。让我们快速回顾一下相关功能的代码:
wifi_record_update() {
availability=""
for dev in $net_interface
do
if [ $(iwconfig wlp0s20f3 | grep ESSID | cut -d: -f2) != "off/any" ]
then
availability="yes"
ssid=$(iwconfig wlp0s20f3 | grep ESSID | cut -d: -f2)
mac=$(iwconfig wlp0s20f3 | grep "Access Point" | tr -s ' ' | cut -d ' ' -f7)
if grep -Fq "$mac" $filepath
then
used=$(grep "$mac" $filepath | cut -d ' ' -f3)
else
echo "$mac $ssid 0" >> $filepath
fi
break
fi
done
sed -i "/off\/any/d" $filepath ###to delete garbage records which sometimes get collected with mac name set as off/any
}
为了便于将来参考,以下审查适用于提交脚本的 c65636db940235fd7458fb4c4432324401400658。
您经常调用sed -i
,这就是创建大量临时文件的原因(尽管稍后会删除)。
此外,您sed -i
仅使用它来删除本来就不应该写入文件的记录。
更重要的是,您已经有了适当的代码,旨在防止写入这些行:
if [ $(iwconfig wlp0s20f3 | grep ESSID | cut -d: -f2) != "off/any" ]
then
...
fi
sed -i
您应该首先调查为什么上述机制无法正常工作,而不是添加那个丑陋的黑客行为。
现在让我们来看看脚本的实际问题。您已经建立了一个典型的竞争条件:
if [ $(iwconfig wlp0s20f3 | grep ESSID | cut -d: -f2) != "off/any" ]
then
...
ssid=$(iwconfig wlp0s20f3 | grep ESSID | cut -d: -f2)
...
fi
您两次询问 SSID,在这两行之间,它可能已更改。这就是为什么尽管你进行了额外的检查,你还是得到了那些“off/any”线。正确的解决方案是仅获取 SSID 一次,并仅处理该一个值:
ssid=$(iwconfig wlp0s20f3 | grep ESSID | cut -d: -f2)
if [ "$ssid" != off/any ]
then
...
fi
哦,顺便说一句,你引用了!=
比较的错误的一面。没有必要引用无害的字符串“off/any”,但是有充分的理由引用子 shell 调用的结果"$(...)"
。而且,引用每个变量扩展,您在我的更改后看到的。
为了避免这种错误和类似的典型引用错误,我强烈建议通过优秀的脚本来运行您的脚本外壳检查工具。在将其用于生产之前(以及在我们使用它进行任何操作之前),请对您编写的每个 shell 脚本执行此操作:
shellcheck YOUR-SCRIPT.sh
但还有更多!在这些修正之后,仍然存在第二个竞争条件,它会ssid/mac
不时生成不正确的组合。原因完全相同:您iwconfig
在不同的时间点两次询问结果,在此期间接入点可能已更改:
ssid=$(iwconfig wlp0s20f3 | grep ESSID | cut -d: -f2)
if [ "$ssid" != off/any ]
then
...
mac=$(iwconfig wlp0s20f3 | grep "Access Point" | tr -s ' ' | cut -d ' ' -f7)
...
fi
这里正确的解决方案是仅获取整个iwconfig
输出一次,并从完全相同的时间点提取 SSID 和 MAC。由于 的输出iwconfig
相对较小,因此我们只使用变量而不是创建文件。此外,请注意,shell 变量可能包含多行,echo
只要您正确引用(即echo "$iwconfig_output"
而不是echo $iwconfig_output
),它们就可以正确再现:
iwconfig_output=$(iwconfig wlp0s20f3)
ssid=$(echo "$iwconfig_output" | grep ESSID | cut -d: -f2)
if [ "$ssid" != off/any ]
then
...
mac=$(echo "$iwconfig_output" | grep "Access Point" | tr -s ' ' | cut -d ' ' -f7)
...
fi
该脚本中可能存在更多问题,因此我强烈建议对脚本的其余部分应用相同的竞争条件分析和正确的引用。