哦, 列, 你在哪里?
當(dāng) ramiroencinas 給 Perl 6 的生態(tài)系統(tǒng)增加 FileSystem::Capacity::VolumesInfo模塊的時(shí)候, 我說(shuō)他沒(méi)有添加 macOS 支持递胧。而當(dāng)我嘗試為這個(gè)模塊貢獻(xiàn)源代碼時(shí)我才發(fā)現(xiàn)知道一丟丟 Perl 6 的特性就能節(jié)省很多時(shí)間馍管。 FileSystem::Capacity::VolumesInfo
這個(gè)模塊所做的就是解析 df
命令的輸出, 它看起來(lái)長(zhǎng)這樣:
$ df -k -P
Filesystem 1024-blocks Used Available Capacity Mounted on
/dev/disk3 1219749248 341555644 877937604 29% /
devfs 343 343 0 100% /dev
/dev/disk1s4 133638140 101950628 31687512 77% /Volumes/Untitled
map -hosts 0 0 0 100% /net
map auto_home 0 0 0 100% /home
map -fstab 0 0 0 100% /Network/Servers
//Pawel%20Pabian@biala-skrzynka.local./Data 1951417392 1837064992 114352400 95% /Volumes/Data
/dev/disk5s2 1951081480 1836761848 114319632 95% /Volumes/Time Machine Backups
bbkr@localhost:/Users/bbkr/foo 123 1219749248 341555644 877937604 29% /Volumes/osxfuse
(如果你看到了折行的或者截?cái)嗟妮敵稣?qǐng)?jiān)谶@兒核對(duì)一下原始數(shù)據(jù))
雖然這對(duì)人類(lèi)來(lái)說(shuō)看起來(lái)不錯(cuò), 但是對(duì)于解析器來(lái)說(shuō)十一個(gè)棘手的任務(wù)。
- 列的寬度是動(dòng)態(tài)的 - 所以每列的值不能使用帶有硬編碼位置的子字符串來(lái)提取椅贱。
- 列和列之間是通過(guò)空白分割的, 空白被填充到列與列之間, 并且它們的值也能包含空白 - 所以不能通過(guò)空白使用 split 函數(shù)來(lái)提取值恨狈。
- 文件系統(tǒng)的名字擁有不同的轉(zhuǎn)義模式妆棒。
- 有些列是左對(duì)齊的, 有些列是右對(duì)齊的, 有一列是居中對(duì)齊的幻锁。
所以讓我們來(lái)使用 Perl 6 中的特性來(lái)處理這混雜的東西。
捕獲命令行輸出
my ($header, @volumes) = run('df', '-k', '-P', :out).out.lines;
方法 run
執(zhí)行 shell 命令并返回 Proc對(duì)象辐真。方法 out
創(chuàng)建一個(gè) 管道對(duì)象以接收 shell 命令的輸出须尚。方法 lines
把該輸出按行分割, 第一行保存到 $header
變量中, 剩下的行保存到 @volumes
數(shù)組中。
解析 header
my $parsed_header = $header ~~ /^
('Filesystem')
\s+
('1024-blocks')
\s+
('Used')
\s+
('Available')
\s+
('Capacity')
\s+
('Mounted on')
$/;
我們這樣做是因?yàn)槠ヅ鋵?duì)象保存了每個(gè)捕獲, 并且每個(gè)捕獲都知道它所匹配的開(kāi)始位置和結(jié)束位置, 舉個(gè)例子:
say $parsed_header[1].Str;
say $parsed_header[1].from;
say $parsed_header[1].to;
會(huì)返回:
1024-blocks
44
55
那會(huì)在動(dòng)態(tài)列寬問(wèn)題上幫助我們很多!
提取每行的值
首先我們必須要查看 FileSystem 和 1024-blocks 這兩列之間的邊界侍咱。因?yàn)?FileSystem 是左對(duì)齊的而 1024-blocks 是右對(duì)齊的, 所以?xún)闪兄械臄?shù)據(jù)都占據(jù)那些 headers 之間的空白, 舉個(gè)例子:
Filesystem 1024-blocks
/dev/someverybigdisk 111111111111111
me@host:/some directory 123 222222222222
| |
|<----- possible ----->|
|<--- border space --->|
我們不能簡(jiǎn)單地按空白分割耐床。但是我們知道 1024-blocks 這一列在哪里結(jié)束, 所以結(jié)束在和 1024-blocks 同一位置的那個(gè)數(shù)字就是我們的容量大小(volume size)。要提取它, 我們可以使用另外一個(gè)有用的 Perl 6 特性 - 正則表達(dá)式位置錨點(diǎn)(regexp position anchor)楔脯。
for @volumes -> $volume {
$volume ~~ / (\d+) <.at($parsed_header[1].to)> /;
say 'Volume size is ' ~ $/[0] ~ 'KB';
}
它查找對(duì)齊于 header 末端位置的數(shù)字序列撩轰。每個(gè)其它的列都能使用這個(gè)花招來(lái)提取, 如果我們知道那個(gè)數(shù)據(jù)準(zhǔn)線的話。
$volume ~~ /
# first column is not used by module, skip it
\s+
# 1024-blocks, 右對(duì)齊
(\d+) <.at($parsed_header[1].to)>
\s+
# Used, 右對(duì)齊
(\d+) <.at($parsed_header[2].to)>
\s+
# Available, 右對(duì)齊
(\d+) <.at($parsed_header[3].to)>
\s+
# Capacity, 居中對(duì)齊, 不會(huì)比 header 還長(zhǎng)
<.at($parsed_header[4].from)>
\s* (\d+ '%') \s*
<.at($parsed_header[4].to)>
\s+
# Mounted on, 左對(duì)齊
<.at($parsed_header[5].from)>(.*)
$/;
益處!
通過(guò)在正則表達(dá)式中使用 header 名字的位置和位置錨點(diǎn)我們?cè)?macOS 上得到了防炸的 df 解析器, 它能工作在普通的磁盤(pán), 隨身存儲(chǔ)器, NFS / AFS / FUSE 共享, 古怪的目錄名和不同的轉(zhuǎn)義模式中。