说实话看到一大撮“AI 生成”的 dmenu 脚本躺在我的~/bin目录里我第一反应是先自嘲一句这不就是大家说的 slop 嘛。批量产出、风格统一、看着都能跑但离“自己动手写”的水平总差着半口气偶尔还会给你整出个哭笑不得的活。先给不熟悉的朋友交代一下 dmenu 是什么。它本质上就是一个极简文本菜单从标准输入读进来一堆选项键盘上下选完回车它把你选中的那一行原样输出到标准输出。窗口切换、应用启动、剪贴板历史、电源菜单、快捷命令入口……只要你想把“一堆选项里挑一个”变成交互操作dmenu 都能接。正因为它足够简单社区里配 dmenu 的人几乎都攒了一堆个人的小脚本而这正是大模型最擅长产出的内容类型。这篇文章就聊聊我拿大模型批量产 dmenu 脚本的经历产出来的东西踩了哪些坑、怎么一步步改到能日常用、最后沉淀下来哪几个脚本是真的在服役。看完你可以直接抄我工作流也能避掉大部分翻车点。1. 先搞清楚 dmenu 脚本到底难在哪1.1 dmenu 的心智模型一个过滤器dmenu 的模型简单到有点反直觉它不关心你给的是什么也不管你选完要干什么。屏幕上一个白条你键盘敲两下它把你选中的一行吐出来剩下的事全交给你的脚本。selection$(printf 锁屏\n休眠\n关机\n重启\n | dmenu -i -l 10)这一行就是 dmenu 脚本的最小骨架printf负责准备选项dmenu -i弹出可模糊搜索的菜单-i是忽略大小写-l 10是纵向列表显示 10 条最后的selection变量拿到用户选中的那一行。麻烦在于如果用户按下 ESC 或者鼠标移开点了空白处dmenu会直接退出并且什么都不输出selection就是空字符串。后面接case或者if的时候空值的处理才是各种崩坏的开始。1.2 真正的复杂度全在输入准备和结果处理给新手的错觉是dmenu 脚本就是“把几个选项喂进去”。实际上选项的准备往往比菜单本身复杂十倍。一个典型的例子是剪贴板历史。你要监听系统剪贴板的变化、去重、按时间排序、再喂给 dmenu。这里每一件事都涉及外部依赖xclip 或者别的剪贴板工具、临时文件读写、循环的退出条件。选项本身可能还带换行喂给 dmenu 之前得想办法把多行内容压成一行展示。结果处理也一样。你选中一个颜色名称下一步是要把它转成十六进制码复制到剪贴板你选中一条历史命令下一步是判断它是否需要 sudo、是否要回显、是否要写进日志。这些边界逻辑才是脚本真正值钱的地方也是大模型最容易糊弄过去的地方。2. 大模型生成的翻车现场我都替你踩过了2.1 训练数据里什么都有就是没有你的系统我第一次让大模型写 dmenu 脚本需求很朴素做一个快捷启动器输入关键词弹出匹配的应用列表。结果它给我返回了一个依赖rofi的实现。用没用过的人可能不知道rofi是另一个功能更重的菜单程序参数体系和 dmenu 完全两回事。我明明说的是 dmenu它却默认我装了 rofi——因为在它见到的海量配置文件里rofi 的出现频率太高了。这个教训后来反复出现大模型写出来的东西默认环境是训练集里的“平均环境”而不是你的机器。类似的坑还有默认你装了i3lock、slock之类的锁屏工具直接拿来当命令用默认你的系统是 systemd电源管理一律systemctl完全不考虑你用的是别的 init默认你有一个成熟的字体和配色配置生成的 dmenu 外观参数-fn、-nb这类照着别人的xresources抄。这些问题单独看都不致命但合在一起生成的脚本能直接跑的概率很低。2.2 输入输出里的分隔符陷阱第二个高频翻车点是分隔符。dmenu 的输入输出都是按行处理的所以“行”就是它的基本单位。问题出在很多数据源天然不是按行组织的。最典型的就是列目录。你让大模型写一个“列出可执行文件”的脚本它大概率会写apps$(ls /usr/bin | dmenu -l 20)这在没有空格文件名的系统上能用但一旦某个文件名带空格dmenu拿到的就是一个断成两行的假选项选中之后复制回去还是个错的路径。我踩过一次后凡是涉及文件名的地方都要求它走find ... -print0加tr转换或者干脆在准备阶段就把空格处理掉。表情选择器也是重灾区。网上拿到的表情清单通常是“emoji 说明文字”的表格直接cat给 dmenu选中的是整行你还得再切一刀才能拿出真正要粘贴的符号。大模型经常意识不到这一步它默认“你选中什么就粘贴什么”。2.3 取消操作和空输入是重灾区dmenu 脚本里最容易被忽略的路径是用户什么都没选就退出。大模型写的脚本十次里有七八次会这样处理choice$(printf 重启\n关机\n | dmenu -i) case $choice in 重启) systemctl reboot ;; 关机) systemctl poweroff ;; esac表面上没问题但用户如果按 ESC 退出choice为空case进不了任何分支脚本就“静默成功”了。对电源菜单来说这可能是好事但对剪贴板历史来说就是另一回事你辛辛苦苦选了半天结果手一抖按了 ESC脚本既不报错也不退出留你在原地怀疑人生。正确的姿势是显式处理空值[ -z $choice ] exit 0这句话现在是我检查生成脚本的第一项。凡是没有空值保护的一律打回重写。2.4 带状态的脚本最容易在循环细节上翻车我让大模型写过剪贴板历史监控器这个任务最考验细节。第一次生成的版本是个while true循环里面每次拷贝都往历史文件追加一行没有任何去重。用了一天后文件里全是重复的。第二次我在提示词里加了“需要去重”它改成了先grep再追加。问题又来了如果剪切板里是一段多行文本grep -Fx是按行匹配的多行内容会被拆开跟文件里的行比永远匹配不上于是多行内容反复入库。这事的本质是循环里对“变化”的检测方式跟人对“变化”的理解不一致。人不希望历史里塞满同一个 URL 拷贝二十遍但程序看到的是二十次剪贴板更新事件它无法判断哪两次是同一个内容。正确做法是拿当前内容和文件最后一条记录比较而不是全文件去重。这种对“状态”的敏感恰恰是大模型最薄弱的地方——它会把一个看起来合理的思路写得很顺但不会真的代入你的使用场景去推演状态流转。3. 从“看着像”到“真能用”我的修正工作流踩了足够多的坑之后我总结出一套固定流程。现在生成 dmenu 脚本我基本按这几步走翻车率从八成降到了两成以内。3.1 把环境写进提示词别让它瞎猜大模型不是没法生成好脚本是没法猜出你的环境。所以我现在写提示词的第一段永远是环境清单而且刻意用“不允许”来缩小范围请写一个 bash 脚本功能是 - 从 ~/.cache/ 读取历史文件用 dmenu 列出最近 20 条 - 选中后复制到剪贴板 环境约束 - 只允许依赖系统里已有的 dmenu 和 xclip不要再引入其他工具 - 不要使用 rofi、zenity、notify-send 等任何你没在本机验证过的程序 - 用户按 ESC 取消时脚本必须静默退出且不写任何文件 - 不需要外观美化参数用 dmenu 默认外观 - 如果某段逻辑有边界情况请用注释标出来最后一条是我后来加的效果出奇地好。它逼着大模型在它自己没把握的地方打上标记而这些标记正好就是我接下来要人工检查的坐标。3.2 语法检查加本地桩测试不直接上真 dmenu拿到生成脚本我从来不会直接扔进~/bin去跑。先过两道静态检查bash -n script.sh # 语法检查 shellcheck script.sh # 静态分析抓未引用变量等问题shellcheck会抓出大部分真实 bug变量没加引号、循环里用了可能不存在的命令、cat的误用之类的。它给出的警告级别比大模型自己解释代码靠谱得多。然后我再用“dmenu 桩”替代真实 dmenu 测试逻辑。方法很简单写一个假的dmenu函数让它固定返回某个值这样就不用每次都手动去点菜单了。dmenu() { printf 测试选项\n } export -f dmenu bash script.sh这样整个脚本走通一遍我只看结果分支对不对。反复换几种模拟输入覆盖 ESC 空值、选中某项、输入不存在项这三种情况脚本才算过了半关。3.3 让大模型自己逐行解释反向揪 bug最后一步最花时间但效果也最明显把脚本原文贴回去要求它逐行解释并且强制它指出“这段代码在什么情况下会出错”。我第一次试的时候它自己主动招了一个我根本没发现的问题——某个awk正则在我本地的awk版本里语法不兼容。这个方法的原理是大模型自己生成的代码它其实“知道”哪里有隐患只是写的时候偷懒没处理。你直接问它哪里不对它会把这些隐患一条条列出来。反复两三轮下来脚本质量能接近一个普通工程师手写的水平。4. 实测跑通、现在还在用的三个脚本下面这三个脚本是经历了上述流程、现在真的躺在我的~/bin里每天在用的。代码我做了简化但核心逻辑保留你可以直接抄。4.1 剪贴板历史最值得写的 dmenu 脚本剪贴板历史是我觉得收益最高的小工具没有之一。核心思路就一句话后台循环监听剪贴板变化变化了就存进文件需要的时候用 dmenu 列出文件内容选中回填到剪贴板。#!/bin/bash # 剪贴板历史依赖 xclip HIST_FILE$HOME/.cache/cliphist mkdir -p $(dirname $HIST_FILE) touch $HIST_FILE # 监听模式把当前剪贴板内容追加到历史与最后一条相同则跳过 if [ $1 monitor ]; then while true; do current$(xclip -o -selection clipboard 2/dev/null) last$(tail -n 1 $HIST_FILE 2/dev/null) if [ -n $current ] [ $current ! $last ]; then printf %s\n $current $HIST_FILE fi sleep 2 done fi # 选择模式列出历史选中后放回剪贴板 choice$(dmenu -i -l 15 $HIST_FILE) [ -z $choice ] exit 0 printf %s $choice | xclip -i -selection clipboard使用方式是开机自动后台跑一个cliphist monitor实例之后任何时候按快捷键调起cliphist选一条历史就直接粘贴。注意去重用的是“跟最后一条比”代价是连续复制同一个内容只会保留一条但好处是处理多行内容时不会误判。这个取舍我觉得值。4.2 电源菜单给关机加一层防呆平时我喜欢直接敲命令关机但给家里其他人用电脑的时候一个可交互的电源菜单能省掉很多解释成本。#!/bin/bash choice$(printf 锁屏\n注销\n重启\n关机\n睡眠\n | dmenu -i -p power: ) [ -z $choice ] exit 0 case $choice in 锁屏) 换成你常用的锁屏命令 ;; 注销) pkill -KILL -u $USER ;; 重启) systemctl reboot ;; 关机) systemctl poweroff ;; 睡眠) systemctl suspend ;; esac唯一要你自己替换的是锁屏那一行系统不同、用的锁屏工具不同命令也不一样。这个脚本最值钱的地方反而是开头那个[ -z $choice ] exit 0——它就是我从翻车现场捡回来的教训。4.3 表情选择器一行命令改造成菜单表情选择器看着花哨实现起来特别简单。核心是准备一份“表情 说明”的清单然后用 awk 切出真正要复制的部分。#!/bin/bash EMOJI_FILE$HOME/.local/share/emoji.tsv [ -f $EMOJI_FILE ] || { echo 缺少表情清单文件; exit 1; } # 展示说明文字选中后只取第一列的 emoji 符号 choice$(awk -F \t { print $2 \t $1 } $EMOJI_FILE | dmenu -i -l 15) [ -z $choice ] exit 0 printf %s $choice | cut -f2 | xclip -i -selection clipboard我的emoji.tsv长这样第一列是符号第二列是说明。菜单里给人看的是说明文字选完复制的是符号两列用 tab 隔开awk 和 cut 各取所需。这算是把 2.2 里说的分隔符教训用到了正道上。5. 关于 AI 写脚本这件事我的最终态度5.1 大模型适合写的和不适合写的折腾了这么多我的结论很明确大模型特别适合写那种“单次交互、状态简单、边界清晰”的 dmenu 脚本。菜单选项从哪里来、选中后做什么这两件事都在同一个脚本里闭环它写出来的质量完全可以接受。它不适合的是带长期状态的东西。比如那个剪贴板历史状态跨越多次运行、涉及文件持久化和去重策略大模型每次都能给你一个看似合理但细节欠考虑的版本。这类脚本适合你自己想清楚状态机再让大模型帮你填具体代码。一句话总结让大模型写“函数”不写“系统”。5.2 我的底线每行代码都要看懂这是最重要的一条也是我踩了无数次坑之后才守住的规矩。dmenu 脚本短小但它是你每天肌肉记忆的一部分。一个偷偷多写了个rm -rf的脚本可能永远不会触发但一旦触发就是不可逆的损失。所以我现在每拿到一段生成代码都会强制自己逐行过一遍把看不懂的部分贴回去问清楚直到整个脚本的逻辑在脑子里能跑通为止。这个习惯多花五分钟但换来的是把“AI 产出的可疑脚本”变成了“我自己维护的可靠工具”。最后分享一个小技巧在~/bin里给每个 dmenu 脚本建一个同名.md说明文件里面写清依赖、用法和当时踩过的坑。方便自己三个月后回看也方便大模型下次改进。我现在的做法是把这些说明文件也丢给它做上下文生成的脚本明显更贴合我的环境了。
阅读完成 · 觉得有帮助?