如何在Bash中迭代每行命令输出?

5

我有一个从/proc/stat读取并计算CPU使用率的脚本。在/proc/stat中有三行相关的内容:

cpu  1312092 24 395204 12582958 77712 456 3890 0 0 0
cpu0 617029 12 204802 8341965 62291 443 2718 0 0 0
cpu1 695063 12 190402 4240992 15420 12 1172 0 0 0

目前,我的脚本仅读取第一行并从中计算使用情况:

cpu=($( cat /proc/stat | grep '^cpu[^0-9] ' ))
unset cpu[0]
idle=${cpu[4]}
total=0
for value in "${cpu[@]}"; do
  let total=$(( total+value ))
done

let usage=$(( (1000*(total-idle)/total+5)/10 ))
echo "$usage%"

这个能够正常工作,因为脚本只解析这一行代码:
cpu  1312092 24 395204 12582958 77712 456 3890 0 0 0

轻松获取以cpu0cpu1开头的行并不难。

cpu=$( cat /proc/stat | grep '^cpu[0-9] ' )

但我不知道如何遍历每一行并应用相同的过程。我已经尝试在子shell中重置内部字段分隔符,如下所示:

cpus=$( cat /proc/stat | grep '^cpu[0-9] ' )
(
IFS=$'\n'
for cpu in $cpus; do
    cpu=($cpu)
    unset cpu[0]
    idle=${cpu[4]}
    total=0
    for value in "${cpu[@]}"; do
        let total=$(( total+value ))
    done
    let usage=$(( (1000*(total-idle)/total+5)/10 ))
    echo -n "$usage%"
done
)

但是这会导致语法错误。
line 18: (1000*(total-idle)/total+5)/10 : division by 0 (error token is "+5)/10 ")

如果我在循环中回显 cpu 变量,它似乎正确地分隔了行。我查看了这个线程,我认为我将 cpu 变量分配给一个数组是正确的,但是否还有其他错误我没有注意到?
我将我的脚本放到这个网站上,除了一个关于在 $() 中使用 cat 的警告之外,它并没有显示任何错误,所以我感到困惑。

我认为你应该尝试使用awk来完成这个任务。 - Staven
@Staven 我会研究一下的。这种情况在awk中是否只需要一行代码就能解决? - Michael A
@janos 感谢您的提醒。尽管您的bash答案很有帮助,但另一个答案中的awk脚本比您的一行awk脚本更优秀。 - Michael A
@janos 非单行代码更易读,并利用了 awk 允许浮点除法的特性。我在我的 bash 代码中使用的 (1000 * (total - idle) / total + 5) / 10 技巧(也在你的 awk 代码中)在 awk 中是不必要的。它只会增加混乱。 - Michael A
@user30588 嗯,好的,那是真的。 - janos
显示剩余2条评论
2个回答

6

在循环的中间更改此行:

IFS=' ' cpu=($cpu)

你需要这样做是因为在循环外你设置了IFS=$'\n',但是使用这个设置后cpu($cpu)将不会按照你的期望执行。
顺便说一下,我会这样编写你的脚本:
#!/bin/bash -e

grep ^cpu /proc/stat | while IFS=$'\n' read cpu; do
    cpu=($cpu)
    name=${cpu[0]}
    unset cpu[0]
    idle=${cpu[4]}
    total=0
    for value in "${cpu[@]}"; do
        ((total+=value))
    done
    ((usage=(1000 * (total - idle) / total + 5) / 10))
    echo "$name $usage%"
done

使用 awk 的等效方式:

awk '/^cpu/ { total=0; idle=$5; for (i=2; i<=NF; ++i) { total += $i }; print $1, int((1000 * (total - idle) / total + 5) / 10) }' < /proc/stat

3
如果删除换行符,所有内容都可以变成一行。 - Staven
不知道你为什么这么说。对我来说,在一行上写这个是很自然的,我并不是在争论在一行上写更好。我看到你写了几乎相同的内容,但是拆成了多行,这样更易读。 - janos

3
因为OP提出了问题,以下是一个awk程序。
awk '
    /cpu[0-9] .*/ {
        total = 0
        idle = $5
        for(i = 0; i <= NF; i++) { total += $i; }
        printf("%s: %f%%\n", $1, 100*(total-idle)/total);
    }
' /proc/stat
< p > /cpu [0-9].*/ 的意思是“对于每个匹配此表达式的行执行”。 像 $1 这样的变量会按您所期望的方式运行,但第一个字段的索引为1,而不是 0:在 awk 中, $0 表示整行。


awk是否没有bash的舍入问题?这就是为什么我使用(1000 * (total - idle) / total + 5) / 10而不是仅仅使用100*(total-idle)/total的原因。 - Michael A
@user30588 我不清楚。Bash有哪些舍入问题?不过,我在printf中放错了格式符号,现在已经修复了。 - Staven
“bash”并不会有太多的四舍五入问题,因为它只执行整数除法:“a/b”始终是一个整数,余数被舍去。 - chepner
1
那么,awk 没有这样的限制。例如,尝试 awk 'BEGIN { print 1/2; }' /dev/null - Staven

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接