惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LangChain Blog
Martin Fowler
Martin Fowler
P
Palo Alto Networks Blog
MongoDB | Blog
MongoDB | Blog
A
About on SuperTechFans
Google DeepMind News
Google DeepMind News
博客园_首页
量子位
小众软件
小众软件
F
Full Disclosure
Vercel News
Vercel News
爱范儿
爱范儿
Engineering at Meta
Engineering at Meta
F
Fortinet All Blogs
博客园 - 聂微东
V
V2EX
Blog — PlanetScale
Blog — PlanetScale
罗磊的独立博客
WordPress大学
WordPress大学
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tor Project blog
Google DeepMind News
Google DeepMind News
M
MIT News - Artificial intelligence
L
Lohrmann on Cybersecurity
H
Hacker News: Front Page
Spread Privacy
Spread Privacy
AI
AI
C
Cyber Attacks, Cyber Crime and Cyber Security
C
CERT Recently Published Vulnerability Notes
D
Docker
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Recorded Future
Recorded Future
L
LINUX DO - 热门话题
Microsoft Azure Blog
Microsoft Azure Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Latest news
Latest news
W
WeLiveSecurity
Application and Cybersecurity Blog
Application and Cybersecurity Blog
博客园 - 司徒正美
博客园 - 叶小钗
T
Threat Research - Cisco Blogs
P
Privacy International News Feed
O
OpenAI News
Help Net Security
Help Net Security
aimingoo的专栏
aimingoo的专栏
宝玉的分享
宝玉的分享
博客园 - Franky

依云's Blog

Wayfire支持不缩放Xwayland啦 - 依云's Blog 使用wayvnc远程访问无头Wayfire会话 - 依云's Blog pacfiles: 高速的 pacman -F 替代品 用 Android 手机当电脑的话筒 - 依云's Blog 使用 ffmpeg 对音频文件进行响度归一化 - 依云's Blog 使用 nftables 屏蔽大量 IP - 依云's Blog YubiKey 初体验 - 依云's Blog fcitx5 码表同步方案 - 依云's Blog 我正在使用的火狐扩展(2024年版) - 依云's Blog 使用 PipeWire 实现自动应用均衡器 如果你发现你的 OOM Killer 在乱杀进程 使用 atuin 管理 shell 命令历史 btrfs 元数据满了怎么办 btrfs 翻车记 在 nspawn 里运行 docker Linux 上的字体配置与故障排除 新的 PaddleOCR 部署方案 使用 EasyEffects 调整 Bose 音箱的体验 让离线软件真正离线 我所讨厌的网页行为 tmux 状态栏优化 Google Chrome 中的字体设置 从 getmail6 到 offlineimap 微信消息通知的困扰 Qt 的字体渲染问题 Wayfire 迁移进展(四):不那么 high 的 DPI Wayfire 迁移进展(二):Xwayland HiDPI 以及 waybar Wayfire 迁移进展 不同情况下的图形效果 Wayland 初体验 纯 CSS 实现倒三角箭头 倾听蓝牙耳机的按键事件 使用 bwrap 沙盒 一次失败的 KDE 尝试 i3 的 scratchpad 处理逻辑 HiDPI 配置记录 让 QEMU 使用 SPICE 协议 Python 小版本升级是怎么 break 已有项目的 让 Arch Linux 系统和最新的镜像同步,从最快的镜像下载 tar 归档的权限问题 Linux 的环境变量怎么设 终端色彩总结 桥接无线网卡! Linux 的进程优先级与 nice 值 Intel GVT-g 初体验 自制大上 Paperlike HD「驱动」 Python 3.8 升级记录 Poker II 键盘调教记 红黑树到底是个什么树 gdb 不肯加载调试信息怎么办? NVIDIA PRIME 配置笔记 寻找最快的 GitHub IP 火狐远程调试火狐 fcitx 扩展:使用键盘粘贴选区(以及X选区原理科普) T470p 使用N卡运行 Xorg 系统在解析哪些域名呢? 正确的隐藏挂载点的方法 迁移系统到 SSD 使用 cgroups net_cls 来让 docker 走代理 使用 cgroups 限制指定进程的内存使用 在 Linux 下整理磁盘碎片 docker 里几个基本概念的简单类比 解析 zxinc IPv6 数据库 Ant Design 彩蛋事件之我见 通过 Cloudflare DNS 验证来申请 Let's Encrypt 证书 正确地上传至 PyPI 并展示文档 与 Android 进行 WLAN Direct 连接 获得高精度环形镜子一枚 每次修 Python 代码的 bug 的时候总会想念 Rust 永远不要 tail -f 管道 人生苦短,我用 skim XZ2C: 没有 root 的日子(也还过得去) 使用 iptables 透明代理 TCP 与 UDP Linux 下获取文件的创建时间 递归遍历目录:Python vs Go vs Rust 这个博客要死了 Windows 10 中配置网络共享 小米 Note 3 令人失望地方 小米 Note 3 入手体验 使用 VirtualBox 启动本地磁盘上的其它系统 大上 Paperlike HD 电子墨水屏开箱体验 加固 systemd 服务 嗨 Win10,这是我的浏览器 在 Linux 下设置录音笔的时间 我正在使用的火狐扩展 使用 Python 读取火狐的 cookies WireGuard: 简单好用的 VPN To hup or not to hup 书签搜索:藏在书签里的搜索引擎 使用 Prince 转换 HTML 文档给 Kindle 阅读 放弃 you-get,转投 youtube-dl 等连上互联网之后再来找我吧 改了一下 GTK 3 的默认主题 新的火狐,新的旅程 师者不师,学生不学 谁又用掉了我的磁盘空间?——魔改 ncdu 来对比文件树大小变化 NeWifi 3.2.1.5900 root swapview 更新 nodejs 子进程的正确用法(你应该忽视函数名) 电脑被盗事件
Haskell 实战:获取ArchLinux已安装的所有架构相关的软件包名
依云 · 2012-01-07 · via 依云's Blog
学而不用则惘。

任务内容

通过读取 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.LazyData.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 的强大之外,编写逻辑简单、没有状态变量也是正确率高的重要原因之一。

疑问

如果我想同时统计这些软件包的总大小(包描述信息里有),怎么才能只读一遍这些文件就同时做到这两件事呢?