测量单位正则表达式操作

3

目标

Linux上,我正在尝试获取一个用户友好的字符串来表示可用系统内存。

例如:

Your computer has 4 GB of memory.

成功标准

我认为以下方面更加易于最终用户理解(您可能不同意):

  • 1G1.0G 更易读(1 vs 1.0

  • 1GB1G 更易读(GB vs G

  • 1 GB1GB 更易读(以空格分隔的 计量单位)

  • memoryRAMDDRDDR3 更易读(无行话)

起点

free 实用程序来自 procps-ng,其中有一个选项是为人类设计的:

-h, --human
    Show all output fields automatically scaled to shortest three digit unit
    and display the units of print out.  Following units are used.
        B = bytes
        K = kilos
        M = megas
        G = gigas
        T = teras
    If unit is missing, and you have petabyte of RAM or swap, the number is
    in terabytes and columns might not be aligned with header.

所以我决定从那里开始:

> free -h
             total       used       free     shared    buffers     cached
Mem:          3.8G       1.4G       2.4G         0B       159M       841M
-/+ buffers/cache:       472M       3.4G
Swap:         4.9G         0B       3.9G

3.8G听起来很有前途,现在我所要做的就是...

必要步骤

  • 过滤输出以获取包含人类可读字符串(即Mem:)的行

  • 从该行中间提取出内存总量(即3.8G

  • 解析数字和度量单位(即3.8G

  • 格式化并显示我更喜欢的字符串(例如GGB,...)

我的尝试

free -h | \
  awk  '/^Mem:/{print $2}' | \
    perl -ne '/(\d+(?:\.\d+)?)(B|K|M|G|T)/ && printf "%g %sB\n", $1, $2'

输出:

3.8 GB

所需解决方案

  • 我更倾向于仅使用gawk,但我不知道如何操作

  • 使用更好的方法,即使有标准的方法从字符串中解析出“浮点数”

  • 我不介意一丝不苟地匹配“只有被识别为幅度字母”的字母(B|K|M|G|T),即使这会不必要地破坏与新尺寸的引入的匹配

  • 我使用%g4.0输出为4,这是你可能会反对的,具体取决于你对这些评论的感觉:https://unix.stackexchange.com/a/70553/10283

我的问题总结

  • 你能只用 awk 来完成上面的任务吗?
  • 保持代码严谨性,是否有更优雅的方式编写我的 perl 代码?

记住:

I am a beginner robot. Here to learn. :]

我从安迪·莱斯特那里学到了什么

在此总结出来,以加深我的学习。

例如,这个gawk

echo foo bar baz | awk '{print $2}'

可以这样用Perl编写:

echo foo bar baz | perl -ane 'print "$F[1]\n";'

除非有类似于 gawk --field-separator,否则我认为我还是更喜欢 gawk ,尽管当然在 perl 中完成所有操作既更清洁又更高效。(是否有相应的功能?)


编辑:实际上,这证明了有一个选项-F,就像在gawk中一样:

echo ooxoooxoooo | perl -Fx -ane 'print join "\n", @F'

输出:

oo
ooo
oooo

  • Perl有一个-l选项,非常棒:可以将其视为Pythonstr.rstrip(如果您不是Python专家,请查看链接以了解$_的有效性),但它会自动重新添加\n到输出中。

谢谢,Andy!



我认为提出有组织的问题是值得肯定的。但你可能会在codereview.stackexchange.com上获得更好的反馈。 - squiguy
1
谢谢。我非常犹豫是否要在名为“代码审查”的网站上发布一个“shell one-liner”!感觉有点过分了.. :) - Robottinosino
我同意Stephane的观点。仅仅因为输出恰好落在偶数上,就没有理由改变精度。 - jordanm
从计算机科学的角度来看,我们需要逻辑严密和精确。我认为即使是我的“奶奶”,在向店员询问时也会说“4 GB”,而不是“4.0G”。无论如何,关于正则表达式的问题仍然存在吗? - Robottinosino
3个回答

3

是的,我相信你可以只使用awk来做到这一点,但我是Perl人,所以以下是如何仅使用Perl来完成。

使用[BKMGT]代替(B|K|M|G|T)

使用Perl的-l自动从输入中删除换行符并在输出中添加它们。

我不认为有任何理由让awk做一些剥离工作,而Perl做其余的工作。您可以使用Perl的-a进行字段的自动分割。

我不知道free -h的输出确切内容是什么(我的free没有-h选项),所以我猜测如下:

free -h | \
perl -alne'/^Mem:/ && ($F[1]=~/(\d+(?:\.\d+)?)[BKMGT]/) && printf( "%g %sB", $1, $2)'

1
哇塞..我喜欢这个答案!该死!你在一行代码中教会了我这么多!太棒了!!谢谢! - Robottinosino
1
很高兴你喜欢它。请继续并接受它。我几年前做的这个演示可能会给你更多的提示。https://speakerdeck.com/petdance/a-field-guide-to-the-perl-command-line - Andy Lester
我非常喜欢你的回答,但是我会再等一会儿看看有没有 awk 的解决方案出现。这是我的明确偏好。不过从像您这样的大师那里学习一些 Perl 也很好。如果没有 awk 的解决方案出现,我会接受您的回答。 - Robottinosino
顺便说一句,安迪,我注意到这个单行命令中的“-l”开关似乎不起作用。free -h | perl -lane '/^Mem:/ && ($F[1] =~ /(\d+(?:\.\d+)?)([BKMGT])/) && printf "%g %sB", $1, $2' - Robottinosino
-l 应该不会添加 \n 吗?在那个一行代码中它没有添加,所以我称之为“不起作用”。我错了吗?(请注意,在我上面的补充问题中,我感谢您的回答)我认为这是因为我们使用的是 printf 而不是普通的 print - Robottinosino
1
有趣的是,使用 -l 参数在 print 命令后会添加 \n 换行符,但在 printf 后不会。从手册 perldoc -f print 可以看到:"等价于 print FILEHANDLE sprintf(FORMAT, LIST),不过 $\(输出记录分隔符)不会追加。" - Andy Lester

2
一个 awk(实际上是 gawk)的解决方案。
free -h | awk 'FNR == 2 {if (match($2,"[BKMGT]$",a)) r=sprintf("%.0f %sB",substr($2,0,RSTART-1), a[0]); else r=$2 " B";print "Your computer has " r " of memory."}'

或者为了易读性而分解
free -h | awk 'FNR == 2 {if (match($2,"[BKMGT]$",a)) r=sprintf("%.0f %sB",
          substr($2,0,RSTART-1), a[0]); else r=$2 " B";
          print "Your computer has " r " of memory."}'

何时使用以下技术:

  • FNR 表示第 nth 行(如果 2 出现在 {} 命令中)
  • $2 表示第 2 个字段
  • if (条件)命令;else 命令;
  • match(字符串,正则表达式,匹配数组)。正则表达式表示“必须以 BKMGT 中的一个结尾”
  • r=sprintf 将变量 r 设置为带有 %.0f 的不包含小数的浮点数
  • RSTART 告诉匹配发生的位置,a [0] 是第一个匹配项

以上内容对应的输出结果如下:

Your computer has 4 GB of memory.

颁奖,因为它完全是 awk。Andy的解决方案看起来更干净,我可能更容易记住,但既然都是 awk,那么“奖品”就归你了!;) - Robottinosino

0

另一个冗长的 Perl 回答:

free -b | 
perl -lane 'if(/Mem/){ @u=("B","KB","MB","GB"); $F[2]/=1024, shift @u while ($F[2]>1024); printf("%.2f %s", $F[2],$u[0])}'

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