























学而不用则惘。
通过读取 pacman 数据库,获取本机已安装软件包中所有架构相关的软件包名。pacman 的数据库中,包描述文件位于/var/lib/pacman/local/*/desc,其中星号部分为软件包名加版本号。该文件中,%NAME%的下一行为软件包名,%ARCH%的下一行为架构,我这里是i686或者any。任务就是找出所有 i686 的软件包名。
先写个纯函数,通过一块描述文本(Data.Text)判断这个包是否是架构相关的。类型声明为:
import qualified Data.Text as T isArchDependent :: T.Text -> Bool
然后看看我们怎么才能办到这点。首先,用T.lines把这「块」文本解析成行的列表。然后我们来找为%ARCH%的这一行。怎么找呢,把前边的行丢掉好了:
(dropWhile (/= archstart)) . T.lines where archstart = T.pack "%ARCH%"
现在列表的第二项就是我们要的架构类别。先取两行,最后一行就是了:
last . (take 2) . (dropWhile (/= archstart)) . T.lines
然后做比较,得到最终的结果:
isArchDependent = (/= anyarch) . last . (take 2) . (dropWhile (/= archstart)) . T.lines
where archstart = T.pack "%ARCH%"
anyarch = T.pack "any"
知道一个包是不是我们要的了,但我们还不知道它的名字。此信息我可以肯定在第二行,就不慢慢 drop 了:
getPackageName :: T.Text -> T.Text getPackageName = last . (take 2) . T.lines
再来个筛选函数,把将要显示的包描述信息找出来:
filterArchDependent :: [T.Text] -> [T.Text] filterArchDependent = filter isArchDependent
接下来,是程序中「不纯」的部分。我们需要列出目录/var/lib/pacman/local下的所有目录,然后读取其中的desc文件。
getPackagePaths :: IO [FilePath] getPackagePaths = (filter ((/= '.') . head)) `fmap` getDirectoryContents "." getPackageDesc :: FilePath -> IO T.Text getPackageDesc = TIO.readFile . (++ "/desc")
最后,把以上这些函数组合起来:
topDir = "/var/lib/pacman/local" main = do setCurrentDirectory topDir getPackagePaths >>= mapM getPackageDesc >>= ((mapM TIO.putStrLn) . (map getPackageName) . filterArchDependent)
首先为了避免一大堆的路径拼接,进入topDir里边来。然后(main的第三行)写到:获取所有软件包的路径;对于每个路径,获取对应软件包的描述信息并处理;怎么处理呢?先过滤filterArchDependent,再逐个获取包名,最后把它打印出来。
完整的代码如下:
import qualified Data.Text as T
import qualified Data.Text.IO as TIO
import System.Directory (getDirectoryContents, setCurrentDirectory)
import Control.Monad
isArchDependent :: T.Text -> Bool
isArchDependent = (/= anyarch) . last . (take 2) . (dropWhile (/= archstart)) . T.lines
where archstart = T.pack "%ARCH%"
anyarch = T.pack "any"
filterArchDependent :: [T.Text] -> [T.Text]
filterArchDependent = filter isArchDependent
getPackageName :: T.Text -> T.Text
getPackageName = last . (take 2) . T.lines
topDir = "/var/lib/pacman/local"
getPackagePaths :: IO [FilePath]
getPackagePaths = (filter ((/= '.') . head)) `fmap` getDirectoryContents "."
getPackageDesc :: FilePath -> IO T.Text
getPackageDesc = TIO.readFile . (++ "/desc")
main = do
setCurrentDirectory topDir
getPackagePaths >>= mapM getPackageDesc >>= ((mapM TIO.putStrLn) . (map getPackageName) . filterArchDependent)
我使用这个 Perl 脚本来计时,跑 20 次取平均时间。Shell 算起算术来太麻烦了 :-(
#!/usr/bin/perl
use Time::HiRes qw(gettimeofday);
sub gettime {
my ($sec, $usec) = gettimeofday;
$sec * 1000_100 + $usec;
}
my $times = 20;
my $start = gettime;
for(my $var = 0; $var < $times; $var++){
`$ARGV[0]`;
}
my $end = gettime;
printf "%lfus\n", ($end - $start) / $times;
作为对照的是个 Python 脚本:
#!/usr/bin/env python3
import os
topDir = "/var/lib/pacman/local"
def checkPackage(file):
for l in open(file):
l = l.rstrip()
if l == '%NAME%':
next = 'name'
elif l == '%ARCH%':
next = 'arch'
else:
if next == 'name':
name = l
elif next == 'arch':
return name, l != 'any'
next = ''
def main():
for name in os.listdir(topDir):
if name.startswith('.'):
continue
file = '%s/%s/desc' % (topDir, name)
name, show = checkPackage(file)
if show:
print(name)
if __name__ == '__main__':
main()
这两个脚本长度都差不多,但效率相差挺显著的:
>>> ~tmp/t.pl './packagestat > /dev/null' 86055.100000us >>> ~tmp/t.pl './packagestat.py > /dev/null' 248090.450000us
最开始,我用的是Data.Text.Lazy和Data.Text.Lazy.IO这个包里的 Lazy 文本类型,结果是——
>>> ./packagestat packagestat: glpng-1.45-4/desc: openFile: resource exhausted (Too many open files)
写完这两个脚本,我体会到了Real World Haskell里说的,Even with years of experience, we remain astonished and pleased by how often our Haskell programs simply work on the first try, once we fix those compilation errors.
Haskell 程序基本上编译通过后就能正确运行——只是要先修正各种编译错误。Python 那个跑了几遍才得到正确的结果。不过我觉得,除了 GHC 的强大之外,编写逻辑简单、没有状态变量也是正确率高的重要原因之一。
如果我想同时统计这些软件包的总大小(包描述信息里有),怎么才能只读一遍这些文件就同时做到这两件事呢?
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。