在 Unix 上计算一列输出总和的最短命令?
Posted
技术标签:
【中文标题】在 Unix 上计算一列输出总和的最短命令?【英文标题】:Shortest command to calculate the sum of a column of output on Unix? 【发布时间】:2010-09-22 16:28:11 【问题描述】:我确信有一种快速简便的方法可以在 Unix 系统上计算一列值的总和(可能使用 awk
或 xargs
之类的东西),但是编写一个 shell 脚本来解析行按行是目前唯一想到的。
例如,修改以下命令以计算和显示 SEGSZ 列 (70300) 的总数的最简单方法是什么?
ipcs -mb | head -6
IPC status from /dev/kmem as of Mon Nov 17 08:58:17 2008
T ID KEY MODE OWNER GROUP SEGSZ
Shared Memory:
m 0 0x411c322e --rw-rw-rw- root root 348
m 1 0x4e0c0002 --rw-rw-rw- root root 61760
m 2 0x412013f5 --rw-rw-rw- root root 8192
【问题讨论】:
【参考方案1】:无法想象 perl 没有被举例说明!
请参阅perldoc perlrun
以获得 -a(这意味着 -n)。
和perldoc perlvar
了解 $。和朋友。
$ df |perl -aE'$.<2or$u+=$F[2]say"Used: $u"'
Used: 129016836
如果你真的想发疯:
$ df -h |perl -anE'$|=1;
BEGIN%M=(""=>1,k=>1e3,K=>2**10,M=>2**20,G=>2**30,T=>2**40);%D=reverse%M
print;
if($.<2)@V=map length(),/\s*+[^a-z]\S*(?:\s+[a-z]+)*/g;next # parse header
($w=($_==$#V)+length($F[$_])-$V[$_])>0 and do$V[$_]+=$w;$_<$#V and $V[$_+1]-=$w for 0..$#F; # optimize column widths
$S[$_]+=($F[$_]=~/^(\d+(?:[.]\d*)?)([kKMGT])?$/aa?$1*$M($D||=$2)&&$2:-Inf)for 0..$#F; # scale numeric values
# show results
say join("",map+("-"x($V[$_]-1)).($S[$_]<0?"^":"+"),0..$#V);
$V[$_]+=$V[$_-1]for 1..$#V;
if($D)for$s(@S)@s=sort$b<=>$agrep$_<$skeys%D and$s=sprintf"%.1f%s",$s/$s[0],$D$s[0]
say sprintf+("%s%*s"x@S),map((!$p||($_>0 and length($S[$_])>=($w=($V[$_]-$V[$_-1])))?(($q?"\n":(($p=$q=1)&&"")),$V[$_]):("",0+$w)),$S[$_])grep$S[$_]!=-Inf0..$#S;
'
【讨论】:
【参考方案2】:如果您要对特定的多列求和,可以使用:
input_command | awk 's1+=$1;s2+=$2;s3+=$3;s4+=$4;s5+=$5ENDprint s1,s2,s3,s4,s5'
如果您想对第 1-5 列求和,这将起作用。
【讨论】:
【参考方案3】:要对列中的值求和,您可以使用 GNU datamash。由于前四行不包含您要汇总的值,因此我们将其删除为 tail +4
。
ipcs -mb | tail +4 | datamash -W sum 7
-W
选项将字段分隔符设置为(可能是多个)空格。
【讨论】:
【参考方案4】:我知道这个问题有点过时了,但我在这里看不到“我的”答案,所以我还是决定发布。我会选择结合
tail(获取所需的行) tr(将多个连续空格缩小为一个) cut(只获取所需的列) 粘贴(用+
符号连接每一行)
bc(进行实际计算)
ipcs
没有在我的系统上提供输出,所以我将使用df
进行演示:
# df
Filesystem 1K-blocks Used Available Use% Mounted on
rootfs 33027952 4037420 27312812 13% /
udev 10240 0 10240 0% /dev
tmpfs 102108 108 102000 1% /run
/dev/xvda1 33027952 4037420 27312812 13% /
tmpfs 5120 0 5120 0% /run/lock
tmpfs 204200 0 204200 0% /run/shm
/dev/xvda1 33027952 4037420 27312812 13% /var/www/clients/client1/web1/log
/dev/xvda1 33027952 4037420 27312812 13% /var/www/clients/client1/web2/log
/dev/xvda1 33027952 4037420 27312812 13% /var/www/clients/client1/web3/log
/dev/xvda1 33027952 4037420 27312812 13% /var/www/clients/client1/web4/log
/dev/xvda1 33027952 4037420 27312812 13% /var/www/clients/client2/web5/log
/dev/xvda1 33027952 4037420 27312812 13% /var/www/clients/client2/web6/log
# df | tail -n +2 | tr -s ' ' | cut -d ' ' -f 2 | paste -s -d+ | bc
264545284
我知道在我的系统上做这个特殊的计算并没有什么意义,但它显示了这个概念。
此解决方案的所有部分都已在其他答案中显示,但从未在该组合中显示。
【讨论】:
【参考方案5】:我会尝试构造一个计算字符串并将其提供给 bc,如下所示:
-
grep 包含数字的行
sed 去掉每行数字之前(和之后)的所有字符
xargs 结果(得到由空格分隔的一串数字)
tr 将空格转换为 '+' 字符
胃口好bc!
ipcs -mb | grep -w '^m ' | sed 's/^.*\s//' | xargs | tr ' ' + | bc
看起来这比 awk 解决方案略长,但对于无法阅读(和理解)奇怪 awk 代码的每个人来说,这可能更容易掌握... :-)
如果没有安装bc,你可以在上面的步骤5中使用双括号来计算结果:
echo $(( $(ipcs -mb | grep -w '^m ' | sed 's/^.*\s//' | xargs | tr ' ' +) ))
或
SUM=$(( $(ipcs -mb | grep -w '^m ' | sed 's/^.*\s//' | xargs | tr ' ' +) ))
或
(( SUM=$(ipcs -mb | grep -w '^m ' | sed 's/^.*\s//' | xargs | tr ' ' +) ))
双括号前后的间距是可选的。
【讨论】:
【参考方案6】:感谢上面的 Python 单行代码!。它帮助我轻松检查驱动器上的已用空间。 这是一个混合的 shell / Python 单线,它执行此操作 - 以兆字节为单位计算设备 /dev/sda 上的已用空间。我花了一些时间才发现它,所以,也许有人也觉得这很有用。
df -h -B 1M | grep dev/sda | tr -s ' '| cut -d' ' -f3 |python -c "import sys; print sum([int(num) for num in sys.stdin.readlines()])"
或更多 Python / 更少 shell:
df -h -B 1M | python -c "import sys; print sum([int(l.split()[2]) for l in sys.stdin.readlines() if '/dev/sda' in l])"
再次感谢!
【讨论】:
【参考方案7】:我有一个实用程序脚本,它只是将 all 列相加。从单行输出中获取所需的内容通常很容易。作为奖励,可以识别一些 SI 后缀。
#!/usr/bin/awk -f
# Sum up numerical values by column (white-space separated)
#
# Usage: $0 [file ...]
#
# stern, 1999-2005
for(i = 1; i <= NF; ++i)
scale = 1
if ($i ~ /[kK]$/) scale = 1000
if ($i ~ /[mM]$/) scale = 1000*1000
if ($i ~ /[gG]$/) scale = 1000*1000*1000
col[i] += scale * $i;
if (NF > maxnf) maxnf = NF;
END
for(i = 1; i <= maxnf; ++i) printf " %.10g", col[i]
print "";
使用自定义字段分隔符的示例:
$ head /etc/passwd | addcol -F:
0 0 45 39 0 0 0
【讨论】:
# Usage: $0 [file ...] 【参考方案8】:ipcs -mb | tail +4 | awk ' sum += $7 END print sum '
或者没有尾巴:
ipcs -mb | awk 'NR > 3 sum += $7 END print sum '
将 awk 与 bc 一起使用以获得任意长的结果(感谢 Jouni K.
):
ipcs -mb | awk 'NR > 3 print $7 ' | paste -sd+ | bc
【讨论】:
谢谢,很有帮助!运行该命令,我得到以下结果:6.59246e+08。有什么方法可以强制 awk 显示准确的值(而不是科学记数法)? 安德鲁,awk 有一个 printf 函数:gnu.org/software/gawk/manual/gawk.html#Printf printf "%d\n", sum 应该这样做。 (我猜不是 %f。不知道为什么我认为它是一个浮点数:p) 另外,如果您知道它始终是最后一个字段但不想计算字段数(或者如果字段数不同),您可以使用 print $NF。 这令人不安!但很酷......我[几乎]想取消我的提议:D【参考方案9】:您可以在任何在线 awk 参考资料中查找它:
ipcs | awk '
BEGIN sum = 0
/0x000000/ sum = sum + $2
END print sum'
【讨论】:
【参考方案10】:Python 解决方案
#!/usr/bin/env python
text= file("the_file","r")
total= 0
for line in text:
data = line.split()
if data[0] in ('T', 'Shared', 'IPC'): continue
print line
segsize= int(data[6])
total += segsize
print total
大多数 Linux 发行版都有 Python。
如果您想将标准输入作为管道的一部分进行处理,请使用
import sys
total = 0
for line in sys.stdin:
...etc...
如果你想假设总是有 3 个标题行:
import sys
total = 0
for line in sys.stdin.readlines()[3:]:
total += int(line.split()[6])
print total
单线:
import sys; print sum( [int(line.split()[6]) for line in sys.stdin.splitlines()[3:]] )
【讨论】:
【参考方案11】:您可以从通过cut
运行数据开始 - 这至少会减少列。
然后您应该能够将其通过管道传输到 grep
,去除非数字。
然后……好吧,那我不确定。可以将其通过管道传送到bc
。如果没有,它当然可以交给一个 shell 脚本来添加每个项目。
如果您使用tr
将换行符 (\n
) 更改为空格 (),并通过 xargs 将其传送到循环直到没有更多输入的脚本中,添加每个输入,您可能有一个答案。
所以,类似于以下内容:
cat <whatever> | cut -d'\t` -f7 | grep -v <appropriate-character-class> | tr '\n' ' ' | xargs script-that-adds-arguments
我的 cut
标志可能有点错误 - 但 man
是你的朋友 :)
【讨论】:
以上是关于在 Unix 上计算一列输出总和的最短命令?的主要内容,如果未能解决你的问题,请参考以下文章
算法进阶面试题01——KMP算法详解输出含两次原子串的最短串判断T1是否包含T2子树Manacher算法详解使字符串成为最短回文串