ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Bash内置命令compgen:命令检索、补全与脚本检测实战

Bash内置命令compgen:命令检索、补全与脚本检测实战 在Linux下待久了总会遇到这种场景突然想知道这台机器上到底能跑哪些命令或者想确认某个名字开头的所有可执行程序你第一反应是打开终端对着/usr/bin一顿ls加grep。说实话不是不行只是既慢又费眼神查到的还只是外部命令函数、别名、内建命令完全不沾边。后来我发现了 Bash 内置的compgen它像一个“命令复印机”能把当前 Shell 环境里所有能叫上名的东西一股脑列给你而且用起来就一行字。这篇文章就围绕compgen这个命令展开把它的定位、动作参数、搜索玩法、补全机制、脚本用法和踩坑经验全部拆开讲。适合刚接触 Linux 命令行的新手也适合想写更优雅 Shell 脚本的运维和开发。它不是什么冷门秘籍而是真正被低估的 Bash 内置神器。1. 一条命令把当前Shell里的命令“全部”翻出来1.1 compgen是谁补全生成器兼命令检索器compgen是 Bash 自带的补全生成命令全称是“completion generation”。你在终端里按 Tab 时Bash 之所以能弹出候选列表背后就是它在工作。换句话说compgen不只是给你敲着玩的工具而是 Bash 可编程补全系统里的核心引擎之一。对普通用户来说它最实用的能力有两个一是把系统所有命令列出来二是按前缀过滤命令。对比一下ls /usr/bin这条命令只是查看/usr/bin目录下的文件你看到的并不等于“所有可执行命令”。因为命令还可能来自/bin、/usr/local/bin甚至用户自定义目录而且它看不到别名、函数、内建命令这些 Shell 层面的东西。compgen -c这条命令列出的是当前 Shell 环境下“能当作命令调用”的所有名字包括外部程序、内建命令、别名、函数。它不看目录而是直接查可执行路径和 Shell 内部命名空间信息量和效率都高出不少。1.2 基本用法长什么样裸敲compgen会返回空因为它必须配合选项使用。最常用的写法和帮助信息如下compgen -c help compgenhelp compgen是 Bash 内置帮助输出内容是英文的但结构很清楚。核心语法是compgen [-abcdefgjksuv] [-o option] [-A action] [-G globpat] [-W wordlist] [-F function] [-C command] [-X filterpattern] [-P prefix] [-S suffix] [word]其中[word]是可选的筛选前缀放在最后。举个例子compgen -c sys它会列出所有以sys开头的可调用命令名比如systemctl、sysctl、sysdig这类。这个“按前缀过滤”的特性非常实用等于给终端加了一个命令速查器。2. 真正值钱的是动作参数名单可以按类别任意切换2.1 一张表看懂所有可用actioncompgen的精髓在-A参数也就是 action。它决定你要列出哪一类名字我做了张速查表基本覆盖了日常能用到的所有类型。action说明典型用法alias已定义的别名compgen -A aliasarrayvar数组变量名compgen -A arrayvarbindingReadline 按键绑定名compgen -A bindingbuiltin内建命令compgen -A builtincommand所有可执行命令compgen -cdirectory目录名compgen -A directorydisabled被禁用的内建命令compgen -A disabledenabled当前启用的内建命令compgen -A enabledexport导出的环境变量compgen -A exportfile文件名compgen -A filefunctionShell 函数名compgen -A functiongroup系统用户组compgen -A grouphelptopichelp 帮助主题compgen -A helptopichostname/etc/hosts 中的主机名compgen -A hostnamejob后台作业compgen -A jobkeywordShell 关键字if、case等compgen -A keywordrunning正在运行的作业compgen -A runningservice/etc/services 中的服务名compgen -A servicesetoptset -o 可选项compgen -A setoptshoptshopt 可选项compgen -A shoptsignal信号名compgen -A signalstopped已停止的作业compgen -A stoppeduser系统用户名compgen -A uservariable全部变量名compgen -A variable-c其实是-A command的简写。想快速查某类名字时这张表基本能覆盖九成需求。2.2 几个实际场景里的宝藏action有几个 action 看起来冷门实战里非常救命。compgen -A signal列出的信号名在写服务脚本、排查进程异常时很有参考价值。配合trap做信号处理或者检查/验证信号是否存在都很顺手compgen -A signal | grep -i term # TERMcompgen -A job能列出当前 Shell 的后台任务配合kill %或用作业编号管理任务比单纯jobs多了筛选能力。比如列出所有正在运行的后台任务compgen -A runningcompgen -A service会把/etc/services里注册的标准服务名列出来。排查端口占用、写防火墙规则、看系统预定义服务时不用去翻那个大文件了。配合/etc/services查端口效率高很多。compgen -A user比getent passwd更轻量不需要额外命令直接就能列系统用户。写登录提示、做权限校验、批量检查用户时都很实用。compgen -A export可以看当前导出了哪些环境变量结合env对比能很快排查变量是否生效。调试环境变量问题的时候这个命令能省不少事。3. 组合一下就是搜索器和统计器3.1 一行命令做成“命令搜索引擎”compgen自带前缀过滤但配合标准命令组合才是它的完全体。比如你想找所有和git相关的命令compgen -c | grep gitcompgen -c列全量命令grep做包含匹配。这里补个细节compgen -c git只匹配以git开头的命令而compgen -c | grep git会匹配名称中间或结尾带git的。两者语义完全不同按需选择。搜索和“打包、解压”相关的工具时可以这样写compgen -A command | grep -Ei (tar|zip|gzip|xz|bzip)输出结果会包含tar、gzip、bzip2、unzip这些用来快速盘点系统里有哪些压包类工具比一个个which强太多。3.2 统计维度这台机器到底有多少命令想知道一台 Linux 机器上能调用的命令总数直接用compgen -c | wc -l我试过的几台干净系统这个数字通常在 1500 到 3000 之间取决于安装的软件包数量、PATH 配置和 Shell 自定义函数。想分别看内建命令和函数数量compgen -A builtin | wc -l compgen -A function | wc -l这个统计结果挺能反映一台机器的“软件存量”也是摸清环境的第一步。新接手一台服务器先跑这几个数字心里就有底了。3.3 进阶玩法一个findcmd函数把前面的思路收拢可以写一个findcmd函数放进~/.bashrc以后随时用findcmd() { local pattern$1 if [[ -z $pattern ]]; then compgen -A command | sort -u else compgen -A command | grep -- $pattern | sort -u fi }用法findcmd python findcmd ^k.*n$第一个列所有名字里含python的命令第二个用正则过滤出以k开头、以n结尾的命令。因为grep支持正则这个函数相当于一个小型命令搜索引擎比单纯compgen -c pattern灵活得多。4. Tab补齐的秘密compgen其实是补全系统的引擎4.1 complete/compgen/compopt三兄弟怎么分工很多人自定义命令行补全时被complete、compgen、compopt三个命令绕晕其实分工很清晰complete负责定义“对哪个命令用哪个补全规则”是注册中心和规则表。compgen负责在规则执行时生成候选词是数据生成器。compopt负责微调补全表现比如控制是否追加空格、是否按文件名排序。可以看到compgen处在数据层负责把候选内容算出来所以单独敲它也能验证补全候选是否正确。我调试补全函数时经常先跑一遍compgen -W start stop restart s看输出是不是预期的再挂到complete上去。4.2 手写一个补全函数感受一下以最常见的进程命令补全为例写一个给kill用的补全函数_kill_completion() { local cur${COMP_WORDS[COMP_CWORD]} COMPREPLY($(compgen -W $(ps -eo comm | sort -u) -- $cur)) } complete -F _kill_completion kill把这个放进~/.bashrc重新加载敲kill再按 Tab就能弹出进程名候选。这里有两层ps -eo comm拿到进程名列表compgen -W负责把当前输入作为前缀过滤只返回匹配项。你当然可以手写 if/case 循环去过滤但compgen一步到位不仅省代码还能统一处理空值、空格等边界。4.3 什么时候绕不开compgen -Wcompgen -W是最常用的筛选魔法它从一个空格分隔的单词列表里按前缀筛出候选。自定义补全里几乎离不开它。比如我给一个deploy.sh脚本配补全关键词_deploy_completion() { local cur${COMP_WORDS[COMP_CWORD]} COMPREPLY($(compgen -W build test deploy rollback status -- $cur)) } complete -F _deploy_completion ./deploy.sh它和complete -W的直接区别在于complete -W把候选词写死在规则里值不能动态变化compgen -W可以在函数里动态组装列表比如从文件、命令输出、环境变量中取候选词。写复杂补全时compgen -W几乎无法绕开。5. 脚本里别再依赖which了用compgen做命令存在性检测5.1 三种写法的对比Shell 脚本里检查一个命令是否存在最常见的三种写法是which、command -v和compgen。直接用表格对比最清晰方式优点缺点which cmd简单直观是外部命令部分精简系统没装只查 PATH 外部程序看不到函数和别名command -v cmdPOSIX 标准兼容好输出内容格式不统一遇到别名和函数时输出一串定义脚本里直接当布尔值用容易翻车compgen -A command -- cmd纯 Bash 内置速度快涵盖外部命令、内建、函数、别名只适用于 Bash不兼容 sh/zsh脚本如果明确跑在 Bash 环境下compgen是更可靠的选择。它不产生多余输出判断逻辑落地。5.2 一个可靠的command_exists实现注意compgen -A command -- $cmd做的是前缀匹配存在“输出非空但命令并非精确存在”的情况所以完整的检测函数要再加一层精确匹配command_exists() { local cmd$1 [[ -n $cmd ]] compgen -A command -- $cmd | grep -qxF $cmd }用法if command_exists docker; then echo docker ready else echo docker missing, install it first figrep -qxF的含义-q安静模式-x精确匹配整行-F把模式当固定字符串而非正则。这样就能绕过前缀匹配的坑。这个坑下面还会重点说5.3 顺带判断命令类型是外部程序还是函数有时候光知道命令存在还不够还想区分它是函数、内建还是外部程序。用compgen分类型判断非常直观is_function() { compgen -A function -- $1 | grep -qxF $1 } is_builtin() { compgen -A builtin -- $1 | grep -qxF $1 }对比type -t的输出解析这种写法更可读也更容易在脚本里维护。排查“命令被函数覆盖”这类玄学问题第一反应就应该是查函数表。6. 实测踩坑记录用到深处才明白的细节6.1 shebang决定能不能用compgen是 Bash 内置命令在sh环境里不一定存在。Ubuntu 上/bin/sh指向dash脚本写#!/bin/sh再调compgen会直接报command not found。所以脚本里要用compgenshebang 必须是#!/usr/bin/env bash这也算一条踩坑经验明明代码逻辑没问题换了 shebang 就报错检查完才发现是解释器不对。同样的坑还会出现在source (...)、进程替换等 Bash 特性上这些问题共同点是“只存在于 Bash”。6.2 prefix匹配的坑输出非空不等于命令存在这是compgen最容易踩的坑。举例子compgen -A command -- docker如果系统里同时有docker和dockerd这条命令输出的是docker dockerd输出非空但你并不能据此断定docker精确存在因为也可能是dockerd命中的前缀。所以第 5 节的检测函数才要用grep -qxF收尾。如果只是简单判断“机器上有没有任何一项以这个前缀开头的命令”那compgen直接判空就够了语义要先搞清楚。6.3 重复输出、大小写、排序和localecompgen -c在某些环境下会输出重复项。比如一个名字既是函数又是 PATH 里的外部程序两个来源都会列出来。所以脚本里对输出做sort -u是顺手的事能规避很多怪问题compgen -A command | sort -u大小写方面compgen的前缀匹配是严格区分大小写的。它不会像zsh那样智能忽略大小写。需要做不敏感匹配时靠grep -i处理排序和 locale 也有影响。不同 locale 下sort的排序结果不一致如果脚本跨机器跑就固定排序环境LC_ALLC compgen -A command | sort -u6.4 大量调用时要避免循环内反复执行compgen如果需要批量判断几十个命令是否存在不要写在 for 循环里一次次跑compgen。因为每次调用都会遍历 PATH、构建命名空间几十上百次下来也有明显开销。更高效的做法是先取一次快照然后查表mapfile -t cmdlist (compgen -A command | sort -u) for cmd in docker kubectl terraform git; do if printf %s\n ${cmdlist[]} | grep -qxF $cmd; then printf %-10s [OK]\n $cmd else printf %-10s [MISS]\n $cmd fi done这样只执行一次compgen剩下的都是内存匹配。写工具脚本、启动检查、环境预检场景下这个习惯能明显改善性能。最后分享一个我一直在用的收尾技巧既然compgen能随时列出当前所有命令我用它给机器做了一份“命令基线快照”。新机器装完环境后执行一次compgen -A command | sort -u ~/.cmd_baseline.$(date %Y%m%d)几个月后再生成一份对比新旧两份就能清楚地看到这台机器新增了哪些命令、装过哪些软件、可能引入了哪些工具链。对比命令也很简单comm -13 (cat ~/.cmd_baseline.20250301) (compgen -A command | sort -u)comm -13输出第二个文件里独有的行也就是“新增命令列表”。这个用法帮我复盘过好几次开发环境的变更过程比翻安装日志直观多了。compgen就是这样单独看是一条命令组合起来就是一个顺手的小工具箱。
返回列表