【问题标题】:How do I iterate through each line of a command's output in bash?如何在 bash 中遍历命令输出的每一行?
【发布时间】:2013-12-26 03:01:57
【问题描述】:

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

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 变量,它看起来像是正确地分隔了行。我查看了this thread,我认为我将 cpu 变量正确分配给了一个数组,但是我没有看到另一个错误吗?

我把我的脚本放到whats wrong with my script 中,除了关于在$() 中使用cat 的警告之外,它没有显示任何错误,所以我很难过。

【问题讨论】:

  • 我认为你应该在awk 中尝试这样做。
  • @Staven 我会调查的。像这样的东西在 awk 中几乎是单行吗?
  • @user30588 我在回答中也添加了awk 解决方案
  • @janos 感谢您的提醒。您的 bash 答案很有帮助,尽管另一个答案中的 awk 脚本优于您答案中的单行 awk 脚本。
  • @user30588 不客气。我不知道你说的“优越”是什么意思,他的解决方案是等价的,只是语法不同。

标签: bash


【解决方案1】:

因为 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

/cpu[0-9] .*/ 的意思是“对匹配此表达式的每一行执行”。 $1 之类的变量符合您的预期,但第一个字段的索引为 1,而不是 0:$0 表示 awk 中的整行。

【讨论】:

  • awk 没有 bash 的舍入问题吗?这就是为什么我使用(1000 * (total - idle) / total + 5) / 10 而不仅仅是100*(total-idle)/total
  • @user30588 我不知道。 Bash 有哪些舍入问题?无论如何,我在 printf 中输入了错误的格式字母,现在已修复。
  • bash 没有舍入问题,因为它只执行整数除法:a/b 始终是整数,余数被丢弃。
  • 那么不,awk 没有这样的限制。例如,试试awk 'BEGIN { print 1/2; }' /dev/null
【解决方案2】:

在循环中间更改这一行:

IFS=' ' cpu=($cpu)

你需要这个,因为在你的循环之外你设置 IFS=$'\n', but with that settingcpu($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

【讨论】:

  • 如果你删除换行符,一切都是单行的。
  • 不知道你为什么这么说。我很自然地把这个写在一行上,我并不是说最好写在一行上。我看到您写的内容几乎相同,但扩展为多行,更具可读性。
猜你喜欢
  • 2021-01-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-05
  • 2012-10-28
相关资源
最近更新 更多