我正在尝试制作一个简单的磁盘使用 shell 脚本(用破折号表示),但不确定如何解析该字符串,因为我不熟悉 AWK 的语法。
我使用这个命令:df -P -BG /
,例如,它给我一个像这样的输出:
Filesystem xxxxxxxxxx-blocks Used Available Capacity Mounted on
/dev/mapper/xxx_crypt 500G 200G 100G xx% /
我想在“已使用”(最终“可用”)下提取下面的列,但不确定应该如何做。我知道 AWK 有一个这样的搜索命令:awk '/Used/ {print}'
但我不知道如何获得它下面的行。有小费吗?对我来说,它作为一行上的管道工作非常重要,例如df -P -BG / | awk '...'
因为我将它存储在 shell 变量中,但我认为这并不是绝对必要的,因为可以使用多行 AWK。
答案1
如果您的df
实现支持它,您可以使用--output
而不是解析。来自man df
我的 Arch Linux:
--output[=FIELD_LIST]
use the output format defined by FIELD_LIST, or print all fields if
FIELD_LIST is omitted.
[...]
FIELD_LIST is a comma-separated list of columns to be included.
Valid field names are: 'source', 'fstype', 'itotal', 'iused',
'iavail', 'ipcent', 'size', 'used', 'avail', 'pcent',
'file' and 'target' (see info page).
因此,就您而言,您只需这样做:
$ df -BG --output="used" /
Used
200G
如果您确实需要解析它,我根本不会为模式匹配而烦恼。您控制输入,它不会改变,所以您可以只打印第三个字段(从我的系统输出):
$ df -P -BG / | awk '{print $3}'
Used
44G
如果你真的需要让它工作,即使字段顺序发生变化,所以你需要找到哪个字段包含 string Used
,你可以做一些复杂的事情,如下所示:
$ df -P -BG / |
awk '{ if(NR==1){for(i=1; i<=NF; i++){ if($i ~ /Used/){want=i}}} print $want}'
Used
44G
答案2
将 GNU awk 用于 FPAT、FIELDIDTHS、\s、\S 和 gensub():
$ cat tst.awk
BEGIN { FPAT="\\S+|\\s+"; OFS="\t" }
NR == 1 {
sub(/Mounted on/,"Mounted_on")
for ( i=1; i<=NF; i+=2 ) {
tags[++numTags] = $i
wids = wids " " length($i $(i+1))
}
FIELDWIDTHS = wids
$0 = $0
}
{
for ( i=1; i<=NF; i++ ) {
f[tags[i]] = gensub(/^\s+|\s+$/,"","g",$i)
}
print f["Filesystem"], f["Available"], f["Used"]
}
$ cat file | awk -f tst.awk | column -s $'\t' -t
Filesystem Available Used
/dev/mapper/xxx_crypt 100G 200G
换成我没有的cat file
。df -P -BG /
我只是通过column
管道使列对齐,这是没有必要的。
通过这种方法,您可以按名称打印、比较、重新排序、对您喜欢的任何列的值进行算术运算等。我唯一需要在代码中修改的是“安装于”,将空格更改为下划线。
这是一个适用于任何 awk 的版本:
$ cat tst.awk
BEGIN { OFS="\t" }
NR == 1 {
sub(/Mounted on/,"Mounted_on")
while ( match(substr($0,totWid+1),/[^ \t]+[ \t]*/) ) {
tag = substr($0,totWid+1,RLENGTH)
sub(/[ \t]+$/,"",tag)
tags[++numTags] = tag
begs[numTags] = totWid + 1
wids[numTags] = RLENGTH
totWid += RLENGTH
}
}
{
for ( i=1; i<=numTags; i++ ) {
val = substr($0,begs[i],wids[i])
gsub(/^[ \t]+|[ \t]+$/,"",val)
f[tags[i]] = val
}
print f["Filesystem"], f["Available"], f["Used"]
}
$ awk -f tst.awk file | column -s $'\t' -t
Filesystem Available Used
/dev/mapper/xxx_crypt 100G 200G
答案3
使用乐(以前称为 Perl_6)
~$ raku -e 'my $target = "Used";
my @table = lines.map: *.split(/ \h+ /).List;
my $col = @table.head.grep(/ $target /, :k).cache;
.put for @table.map: *.[ $col.flat ];' file
上面是用 Raku(Perl 编程语言家族的成员)编写的答案。简而言之,$
声明了 -sigiled 标量并为其分配了字符串“Used”。然后lines
读入,每个map
ped 进入split
水平\h
空白。通常,这样的返回将保存在Seq
Raku 中称为 a 的轻量级数据结构中,但这里我们强制将List
数据保存在@table
数组中。
现在来看正确的列。我们获取@table.head
表格的原始标题行(即行)。我们grep
通过这些元素来找到 的匹配项$target
,然后我们获取:k
键,即列索引号,将它们存储在 中$col
。最后,我们输出put
数据@a.table
,只返回$col
所需的列。
输入示例:
Filesystem xxxxxxxxxx-blocks Used Available Capacity Mounted on
/dev/mapper/xxx_crypt 500G 200G 100G xx% /
示例输出:
Used
200G
OP 表达了希望有一个接受 shell 变量的脚本。 Raku 有一个特殊的关联数组%*ENV
,可用于访问 shell 变量。因此,下面colName
从环境(即 shell)中获取 shell 变量:
~$ env colName="Used" raku -e 'my $target = %*ENV<colName>; \
my @table = lines.map: *.split(/ \h+ /).List; \
my $col = @table.head.grep(/$target/, :k).cache; \
.put for @table.map: *.[ $col.flat ];' file
Used
200G
您可以类似地返回两列等,只需稍作调整,如下所示:
~$ env colNames="Used Available" raku -e ' \
my @target = %*ENV<colNames>.split(/ \h+ /).List; \
my @table = lines.map: *.split(/ \h+ /).List; \
my @col = @table.head.grep(/@target/, :k); \
.put for @table.map: *.[ @col.flat ];' file
Used Available
200G 100G