首页 / 资讯中心 / 文章详情

用 LLM 批量生成 dmenu 脚本:从“AI 幻觉软件”到日常效率工具

用 LLM 批量生成 dmenu 脚本:从“AI 幻觉软件”到日常效率工具 ★ FEATURED ARTICLE
1. 先解释一下“dmenu slops”这个标题如果你最近混欧美极客圈或者刷一些技术视频的评论区应该见过“slop”这个词。它指的是一类由AI生成、看着像模像样但骨子里少点灵魂的内容有时候是文章有时候是代码有时候是毫无意义的插画。我这人平时没少折腾平铺窗口管理器桌面上永远开着一堆标签页干活全靠键盘那么把一些重复性的小菜单功能交给 dmenu 来处理是最自然的思路。于是某天我突发奇想干脆让 LLM 给我批量写几个 dmenu 脚本算了。写完之后回看这一批产物确实挺“slop”的——有的能用有的需要我改半天有的生成完一看就是幻觉代码。但用下来之后我又觉得这批脚本里至少有四五个成了我每天离不开的工具。所以标题里的“my dmenu slops”不是贬义更多的是一种自嘲这些脚本确实没一个是我一行一行敲出来的都是大模型“哗啦”一下生成的质量参差不齐可它们确实在干活。这篇文章就是想把这段经历完整记录下来我让 LLM 写了哪些 dmenu 脚本、踩了哪些坑、最后怎么把这些“AI 代码”打磨成真正能用的工具。如果你也喜欢 dwm、i3 这类键盘驱动的工作流或者你正在犹豫要不要让 AI 帮你写日常小工具这篇应该能给你一点参考。先简单介绍一下 dmenu 到底是什么。它是一个极简的 X11 下的动态菜单程序没有配置文件、没有插件系统甚至没有图形界面设置面板。你把一份列表用管道喂给它的标准输入它在屏幕上显示出一个可过滤的选择框你输入关键字过滤、回车选中选中的那一行内容就从标准输出出来。就这么一个三无程序却是无数人桌面工作流里最重要的一环。我用的环境是 dwm 加一堆自写脚本快捷键打开终端、切换窗口、锁屏全部靠键盘完成。dmenu 在这个体系里扮演的角色是“万能选择器”需要打开某个网址需要切换窗口需要粘贴一条历史剪贴板都可以用 dmenu 弹一个列表出来让你选。它本身不干活它就是帮你做选择。2. 为什么选 dmenu 和 LLM 这对组合2.1 dmenu 的“管道哲学”比想象中更简单dmenu 的核心设计特别朴素就两步读完标准输入然后等你在列表里挑一行再把这一行吐出来。整个过程没有任何“业务逻辑”。所以如果你要写一个基于 dmenu 的工具真正的代码基本上都花在“怎么生成一个列表”和“选中之后怎么处理”这两件事上。举个例子一个应用启动器本质就是把 PATH 里所有可执行文件的名字拉出来排序去重后塞给 dmenu等用户选完再用 shell 去执行一个剪贴板管理器本质就是把历史记录从某个文件读出来塞给 dmenu选完再用 xclip 把内容放回剪贴板。这一切都太适合管道组合了因为 dmenu 的输入输出都是纯文本你完全可以用任何语言写它的上下游。用生活化一点的话来说dmenu 就像一个非常听话的接线员你把候选名单递给它它帮你筛选然后告诉你用户选了什么。至于名单是谁整理的、用户选完以后发生了什么它一概不管。这种“各司其职”的风格正是 Unix 工具链最迷人的地方。也正因为这么简单LLM 在这类场景里的发挥空间反而特别大。因为它不需要理解复杂的业务逻辑不需要做架构设计只需要照着“stdin 进来一个列表dmenu 挑一个stdout 出去”这个套路写点胶水代码就行。2.2 LLM 写这种胶水代码是真的快我最初尝试让 LLM 写 dmenu 脚本的原因挺现实的我每天要写不少这类一次性小工具但我的 shell 脚本水平只能算中游awk 和 sed 经常要现查手册。一个简单的启动器让我手写从打开编辑器到调通可能要二十分钟让 LLM 写我只需要把需求描述得具体一点三十秒就能出一版。更关键的是这类脚本的训练语料非常充足。dmenu 已经存在了快二十年全世界的窗口管理器爱好者贡献了无数启动器、剪贴板管理、书签选择、Wi-Fi 连接菜单等花样百出的脚本。LLM 在训练的时候见过太多这类东西所以你让它写一个带历史记录的 dmenu 启动器它给出的结构大概率是对的历史文件放前面可执行文件列表放后面用 awk 去重命令执行完把选中项插回历史。我实测下来的感觉是凡是“用 dmenu 从一批东西里选一个然后对这个选项做点什么”的需求LLM 第一次给出的代码通常能达到七成的完成度。剩下三成需要你自己补主要是一些边界情况比如路径里有空格、历史文件不存在、用户取消了选择等等。另外还有一个显而易见的优势你可以在对话里快速迭代。觉得列表顺序不对直接说“把最近的十条记录放最上面”或者“按使用频率排序”它马上给你新版本不用像手写那样推倒重来。2.3 该泼的冷水一点也不能少但“快”不代表“能直接跑”。这一批脚本我几乎每一个都修改过LLM 犯的错误五花八门。最典型的问题是“参数幻觉”。dmenu 的常用参数是-i忽略大小写、-l指定列表行数、-b在屏幕底部显示、-p设置提示文字、-fn设置字体但 LLM 有时候会把 rofi 或者其他菜单工具的参数混进来比如给你写一个-dmenu这个参数在 rofi 里才有dmenu 根本不认识。如果你不检查就扔进终端跑它只会报一条错误然后默默退出。另一个常见问题是安全处理。LLM 很喜欢写eval或者在sh -c后面直接拼接用户输入。如果脚本是给陌生网友用的这就是一个命令注入的隐患即使是自用你手滑输入了一条带分号的字符串也够让你头疼的。所以我的结论是LLM 可以当辅助但不能当成不会犯错的同事。它给你的是“初稿”代码审查这一步必须你自己来。3. 实操五个能直接抄的 dmenu 脚本这一节我挑五个我觉得最实用、也是我用 LLM 生成后打磨出来的脚本。代码都比较短基本上都符合“从 stdin 读列表dmenu 挑选然后处理选中结果”的套路。你可以直接用也可以当作改造成自己需求的基础模板。3.1 应用启动器带历史记录版dwm 自带的 dmenu_run 是一个极简启动器但它没有历史记录功能每次都要从头敲。我让 LLM 写的第一个脚本就是这个带历史记录的启动器。#!/bin/bash # dmenu 启动器带使用历史 cache$HOME/.cache/dmenu_launcher_history # 历史记录排前面系统命令排后面用 awk 去重保留先出现的顺序 { cat $cache 2/dev/null compgen -c | sort -u } | awk !seen[$0] | dmenu -i -l 10 -p run: | while read -r cmd; do if [ -n $cmd ]; then # 更新历史去掉重复项把最新选项放最前面 grep -vx $cmd $cache 2/dev/null | head -n 100 $cache.tmp echo $cmd $cache.tmp mv $cache.tmp $cache # 后台启动脱离当前会话避免随终端一起退出 setsid sh -c $cmd /dev/null 21 fi done解释一下关键点。compgen -c会把当前 PATH 下所有可执行命令列出来注意它是 bash 的内置命令所以脚本开头必须是#!/bin/bash如果你用#!/bin/sh就会立刻翻车。历史记录文件里的命令排在前面这样每次打开 dmenu 时你最近用过的命令就在最上方选起来快很多。为什么用awk !seen[$0]去重因为它保留了第一次出现的顺序。由于历史记录是先写入的所以去重后历史记录里的顺序不会被打乱如果反过来先写命令列表历史记录就被挤到后面去了这个顺序是很讲究的。执行命令时用setsid加是为了让启动的程序脱离当前 dmenu 进程组。如果你不这么做从 dwm 里按热点键启动的终端关闭时可能会把里面启动的图形程序一并带走这个问题在平铺窗口管理器里特别容易碰到。3.2 剪贴板管理器剪贴板管理器是“真香”项目。X11 下的剪贴板有个毛病只要复制的新内容旧内容就没了。尤其是我这种经常要同时复制几段代码的人没有剪贴板历史简直没法干活。#!/bin/bash # dmenu 剪贴板管理器配合 xclip history$HOME/.cache/clipboard_history touch $history # 先把当前剪贴板内容记录进历史去重 current$(xclip -o -selection clipboard 2/dev/null) if [ -n $current ] ! grep -Fqx $current $history; then echo $current $history.tmp cat $history $history.tmp mv $history.tmp $history fi # 从历史列表中选择支持模糊搜索 selected$(awk !seen[$0] $history | dmenu -i -l 10 -p clip: ) if [ -n $selected ]; then printf %s $selected | xclip -selection clipboard printf %s $selected | xclip -selection primary notify-send 已复制到剪贴板 fi这个脚本的思路是“按下快捷键时先把当前剪贴板里的内容归档再弹出历史列表让你选择”。它会有个小问题如果你连续复制了两段内容但中途一次都没打开过这个菜单那第一段就来不及存档。我用 LLM 生成的第一版就是这么个方案后来我改成用clipnotify监听剪贴板变化再配合这个菜单来读取历史才做到真正完整的记录。但即使只用这个简化版日常也够用了。复制了重要内容后随手按一下快捷键把它归档后面想找旧内容就不至于抓瞎。注意用printf %s而不是echo $selected因为剪贴板内容可能以-n或者带反斜杠开头echo 会做一些转义处理和参数解析用 printf 更稳。3.3 Emoji 快速选择器这个脚本是我觉得 LLM 做得最好的一个因为它的核心是“把 Emoji 列表处理成 dmenu 能读的文本”这正好是 LLM 最擅长的文本处理场景。#!/bin/bash # dmenu Emoji 选择器选中后自动复制到剪贴板 list$(python3 - EOF import emoji for e in emoji.EMOJI_DATA: print(e emoji.demojize(e).replace(:, ).replace(_, ).strip()) EOF ) selected$(printf %s\n $list | dmenu -i -l 15 -p emoji: | awk {print $1}) if [ -n $selected ]; then printf %s $selected | xclip -selection clipboard notify-send 已复制: $selected fi前提是你装了 Python 的 emoji 库同时系统里得有能显示 Emoji 的字体。运行之后 dmenu 列表里会显示每个 Emoji 和它的文字描述你输入“fire”或者“星”这类关键字就能过滤回车后它会把 Emoji 本身复制到剪贴板。我之前是直接用 rofi 的 Emoji 插件切到 dmenu 之后发现这玩意儿比想象中更香因为 dmenu 输入过滤是即时响应的而且对中文描述也有效只要你给列表里加上对应的中文关键词。这个脚本也给了我一个启发任何“内容很多、需要快速检索”的素材都可以变成 dmenu 的输入文件、颜色、特殊字符、常用回复话术全都行。3.4 书签跳转器书签跳转器解决的是浏览器里那一大堆收藏夹的问题。我习惯把常用网站以纯文本形式保存在一个文件里每行一条格式是“标题::网址”。#!/bin/bash # dmenu 书签跳转器 bookmark$HOME/.config/bookmarks url$(awk -F:: {print $1} $bookmark \ | dmenu -i -l 15 -p bookmark: \ | while read -r title; do grep -F $title:: $bookmark | head -n 1 | cut -d:: -f2- done) if [ -n $url ]; then xdg-open $url /dev/null 21 fi这里的坑主要在“标题里有特殊字符”的时候。一开始 LLM 给我的版本是直接grep $title bookmarks结果标题里如果带着方括号或者星号grep 会把它们当成正则表达式解析匹配结果完全错乱。我改成了grep -F也就是把模式当成固定字符串处理然后就清静了。cut -d:: -f2-的意思是以::作为分隔符截取第二段到最后一段。注意这里用-f2-而不是-f2因为网址本身可能还包含::比如带端口号的 IPv6 地址就会是这个情况。这种细节靠 LLM 想不出来我是在真实使用中踩到以后补上的。3.5 窗口切换器平铺窗口管理器不像桌面环境有任务栏预览窗口一多真的会迷路。虽然 dwm 有标签视图但用 dmenu 来切换是另一种高效的思路列出所有窗口的标题输入关键字过滤回车直接切过去。#!/bin/bash # dmenu 窗口切换器基于 wmctrl target$(wmctrl -l | awk {$1$2; sub(/^ */, ); print} \ | dmenu -i -l 10 -p switch: ) if [ -n $target ]; then window_id$(wmctrl -l | awk -v t$target \ {$1$2; sub(/^ */, ); if ($0 t) print $1}) wmctrl -i -a $window_id fiwmctrl -l输出的每一行包含窗口 ID、桌面编号、主机名、窗口标题四部分我让 awk 把前两段扔掉只保留标题部分给 dmenu 显示。这里有个细节如果两个窗口标题完全一样wmctrl -i -a就不知道你要切哪一个了所以更讲究的版本会把窗口 ID 也带到 dmenu 列表里但那样屏幕上就会显示一长串十六进制数字不美观。我实际用下来这个脚本在 i3 这类多工作区环境里体验很好因为wmctrl -a会自动跨越工作区切换。在 dwm 里也够用只是 dwm 本身的标签视图已经很强了这个脚本更多是当一个“窗口模糊搜索器”来用。4. 怎么样让 LLM 产出能用的脚本4.1 一段靠谱的提示词模板让 LLM 生成 dmenu 脚本这件事得到什么质量的输出很大程度上取决于你怎么描述需求。我踩了不少次坑之后总结出一个比较固定的描述模板。核心原则是把“输入是什么、输出是什么、边界情况怎么处理、界面参数用什么”这四件事说清楚。一般我会这样写提示词以剪贴板管理器为例 写一个 bash 脚本基于 dmenu 实现剪贴板历史管理。需求历史记录保存在 ~/.cache/clipboard_history启动脚本时先把当前剪贴板内容追加到历史记录按时间倒序排列自动去重用 dmenu 显示历史列表参数用 -i -l 10 -p clip: 选中后复制回剪贴板并发送通知如果用户取消选择不执行任何操作使用 xclip 作为剪贴板工具注意处理包含空格、特殊字符的内容不要使用 eval 这个模板里最管用的其实是最后一条“不要使用 eval”和第 5 条“取消时不执行任何操作”。前者能避免一大部分注入风险后者能让交互逻辑更干净。如果你不提这些LLM 经常会写出一个用户按 Esc 之后照样执行了$selected的脚本。另外我还会明确要求“脚本头部注释写明依赖项”这样生成完以后我一眼就知道要不要先装wmctrl或者xclip。省得脚本跑起来报错才发现缺依赖。4.2 拿到代码后必须做的审查LLM 生成代码之后千万别直接往配置文件里塞。我会按照一个固定的清单过一遍每一条都是实际踩过的坑总结出来的。第一看有没有eval、有没有把变量直接拼进sh -c。如果看到这两样要么手动改成安全写法要么让 LLM 重写。自用脚本也一样要防因为 dmenu 输入框是允许你打字过滤的你完全可能在过滤的时候敲出了什么敏感字符串结果不小心被当成命令执行了。第二检查 dmenu 参数是否合理。-i是忽略大小写-l是列表行数-b是底部栏模式-fn指定字体。如果代码里出现-dmenu、-theme之类的参数那就是把 rofi 的语法混进来了。第三看管道上下游的类型是否匹配。比如wmctrl -l输出的标题里如果有前导空格不处理的话 dmenu 会显示出一堆不齐整的条目。awk 清洗字段是常见做法要确认清洗逻辑覆盖全面。第四看历史文件的操作有没有原子性。LLM 经常直接写echo $cmd $cache如果中途脚本被中断文件会残留半行数据下次读取就出问题。我一般改成“写临时文件再 mv”的方式也就是前面脚本里那个写法。第五确认脚本里有对“取消选择”的处理。dmenu 用户按 Esc 时退出码是 1$selected是空字符串。如果脚本没有if [ -n $selected ]保护很可能会把空串当成合法输入然后执行一些莫名其妙的行为比如 xdg-open 打开了一个空地址。4.3 集成进 dwm 和 i3 的细节脚本写好了还得让它们能通过快捷键唤起来。dwm 的做法是在config.h里加一个快捷键定义。比如把所有 dmenu 脚本放在~/.local/bin下然后在config.h里给其中一个绑定MODKEY p{ MODKEY, XK_p, spawn, SHCMD(~/.local/bin/dmenu-launcher) }, { MODKEY | ShiftMask, XK_p, spawn, SHCMD(~/.local/bin/dmenu-clipboard) },SHCMD这个宏在 dwm 的源码里已经定义好了它会把字符串包一层sh -c执行。如果你不想每次改config.h都要重新编译 dwm也可以直接把脚本放到 PATH 里然后用别的快捷键触发。i3 下更简单在配置文件里写bindsym $modp exec ~/.local/bin/dmenu-launcher bindsym $modShiftp exec ~/.local/bin/dmenu-clipboard这里最容易被忽略的是 PATH。如果你的窗口管理器是通过 systemd user session 启动的PATH 可能非常“干净”连~/.local/bin都没包含。脚本里执行的compgen、xclip、wmctrl都会找不到。保险的做法是在每个脚本开头显式声明export PATH/usr/local/bin:/usr/bin:/bin:$HOME/.local/bin:$PATH或者在你启动窗口管理器的配置文件比如.xinitrc里统一设置好 PATH。这一步不做你会遇到一个特别诡异的场景终端里手动跑脚本一切正常一用快捷键就报“command not found”。5. 排查实录这些坑我全踩过5.1 中文和 Emoji 显示成方块如果你在 dmenu 里看到中文、日文或者 Emoji 全是小方块十有八九是字体问题。dmenu 默认用的是 X 核心字体XLFD描述说实话又老又难用我强烈建议直接在现代发行版上用 fontconfig 的描述方式。我的做法是在调 dmenu 的每个脚本里都加上字体参数写成dmenu -fn monospace:size11:antialiastrue -i -l 10 -p run: 如果列表里要显示中文你得确保系统里有中文字体比如 Noto Sans CJK 或者文泉驿。如果显示 Emoji那字体必须是有彩色 Emoji 的字体比如 Noto Color Emoji。字体配置和 Linux 桌面环境的 fontconfig 是关联的所以命令行里甚至可以直接写font-family:Noto Sans CJK SC这种描述前提是系统里确实装了对应字体。这个坑之所以值得单独说是因为 LLM 生成的脚本里几乎不会主动加-fn生成的版本在你的机器上一跑界面全是豆腐块你会下意识以为脚本写错了其实是字体没配。5.2 dmenu 参数被我反复搞错dmenu 的参数不算多但我发现 LLM 和人类一样容易搞混。最典型的是-l与-b。-l 10表示“显示 10 行列表”这个参数一旦加上dmenu 就从顶部的单行输入模式切换成带列表的菜单模式。-b则是让菜单显示在屏幕底部。两者一起用效果是“底部弹出个带 10 行列表的菜单”很多现代 dmenu 皮肤都是这么做的。让我用最容易理解的方式总结一遍参数作用常见误解-i忽略大小写过滤有人以为它能搜索子串其实 dmenu 默认就是子串匹配-l N显示 N 行列表启用列表模式有人以为它控制行高-b菜单显示在屏幕底部有人以为它是“底部对齐”的意思-p设置输入框提示文字有人以为它是密码模式-fn设置字体有人写-f或-font都会报错换一种更直观的说法-l是让 dmenu 从一个“单行输入框”变成“可滚动列表菜单”的关键开关不加这个参数时你看到的只有一个窄窄的输入条加了才有那种下拉菜单的样子。如果你觉得“我明明调用了 dmenu 但屏幕没反应”先确认是不是把-l忘了。5.3 脚本里出现了奇怪的路径和命令LLM 在写脚本时很喜欢使用绝对路径或者说它会给出一个它“认为”存在的路径。最常见的问题是/usr/local/bin、/snap/bin这类路径在不同系统上不一定存在。比如它生成的启动器里可能直接用/home/xxx/.local/bin/program这种带用户名的路径你在自己机器上跑自然就是 command not found。我遇到过最离谱的一次是LLM 在脚本里写了一个/opt/scripts/dmenu-wifi作为依赖说这个文件会在菜单的某个分支里被调用。问题是这台机器上根本没有这个目录相当于写了一个“薛定谔的外部调用”。遇到这种代码直接删掉重写就好不用保留。所以拿到 LLM 生成的脚本后把里面的长路径全部扫一遍凡是你能确认不是自己机器上路径的一律改成$HOME/.local/bin或者command -v探测。这也是一个很实用的审查动作。5.4 Wayland 环境怎么办dmenu 是 X11 时代的产物如果你已经全面迁移到 Wayland 桌面纯 dmenu 脚本会有一堆不兼容问题。最常见的表现是XWayland 里能显示但缩放比例不对、输入法切不过来、有时候还会出现焦点问题。如果你主力是 Wayland 环境可以试试这么几个替代方案。第一个是 wmenu它的命令行参数和 dmenu 基本一致很多 dmenu 脚本只需要把命令从dmenu改成wmenu就能跑甚至还能支持触摸屏手势。第二个是 fuzzel它在 Wayland 原生环境下的性能和显示效果都很不错启动器的使用体验接近 rofi。第三个是 rofi 的 Wayland 分支配置丰富但学习成本稍微高一点。我自己的做法是留了两套脚本X11 会话里继续用 dmenuWayland 会话里通过一个变量切换到 wmenu。具体就是在脚本里先探测当前会话类型if [ $XDG_SESSION_TYPE wayland ]; then menuwmenu menu_args-i -l 10 else menudmenu menu_args-i -l 10 fi然后所有调用统一用$menu $menu_args这样同样的逻辑在两套显示服务器下都能用。这个改造思路每次都会多花十分钟但可以省掉以后一整套脚本维护的麻烦。6. 写在最后我目前的工作流这些脚本折腾下来我最大的心得其实是LLM 生成的东西再快也只是个起点真正让它变成你趁手工具的还是你自己对工作流的理解。以我现在的习惯为例我已经很少打开编辑器从头写脚本了。每天早上想清楚今天要做什么或者某个操作让我觉得“太慢了、想按一个键就完成”我就会把这个想法拆成一两句话发给 LLM。等它出初稿我先花一分钟审一下结构改掉安全问题再扔进~/.local/bin/dmenu-xxx。然后实际用两天哪里不舒服再回头改。一个脚本从想法到稳定使用通常不会超过半小时。有一件事倒是值得提醒不要因为 LLM 能生成就一次性生成二十个脚本全塞进系统。脚本越多你已经忘了它们是干嘛的概率越大。我给自己定的规则是一个 dmenu 脚本在快捷键列表上停留的时间至少要有两周如果一个月内没有使用记录就删掉。毕竟 dmenu 脚本这个东西本质上是在帮助你减少键盘敲击而不是为了让你桌面看起来很酷。最后再分享一个小技巧把所有这类小脚本统一命名成dmenu-xxx的格式放在同一个目录里然后把你的窗口管理器快捷键像索引一样列一张速查表。这样时间一长你会发现 dmenu 不只是“一个应用启动器”它变成了你桌面环境的统一入口。想切歌dmenu-mpd想传文件dmenu-scp想查字典dmenu-translate。每个脚本可能都只有二三十行单个来看都不起眼合在一起就让你的键盘操作行云流水。说到底LLM 生成这些 dmenu 脚本的过程很像我在跟一个非常聪明但偶尔犯迷糊的助手合作它负责把我脑子里的想法快速翻译成代码我负责验证这个翻译有没有偏离原意再把它打磨成真正埋在肌肉记忆里的操作方式。这批“slops”最后不 slop 了大概就是因为这个原因。
阅读完成 · 觉得有帮助?
咨询建站