一个机器上跑了几十个容器之后你一定会遇到这样一个时刻docker ps刷出来的输出密密麻麻你只想找到某个镜像启动的服务或者把所有异常退出的容器一次性清理掉。默认的输出既塞不下你要的信息也筛不出你要的范围。这时候--filter和--format这两个参数就派上用场了。前者让 Docker 在服务端先按条件过滤后者让你按自己的排版方式输出需要的信息。这篇文章我把这两个参数的细节、组合用法和我在实际环境里踩过的坑一次性讲清楚适合正在做容器管理、或者被docker ps刷屏困扰的朋友。1. 为什么要把 docker ps “加工”一下再用1.1 真实场景几十个容器堆在屏幕上刚接触 Docker 的时候docker ps很简单一条命令就能看懂当前有哪些容器在跑。但当你在生产环境、CI 环境或者一台开发机上同时运行几十个容器时这个命令的原始输出会变得非常难用。容器名是随机拼接的、镜像名有长有短、状态栏五花八门你一屏滚下来根本分不清哪些是前端服务哪些是数据库哪些已经处于半死不活的退出状态。我以前在某公司负责一套微服务环境的日常维护单机上的容器稳定在三十个以上。每天早上到岗第一件事就是打开终端输入docker ps -a然后对着满屏输出找“昨天哪个服务挂了”。这个过程靠肉眼扫效率极低还容易漏。后来我把筛选和格式化参数用起来之后整个排查过程从“看十几行输出再人肉对比”变成了“一条命令直接给出答案”。1.2 两个参数的分工过滤在服务端排版在客户端很多人误以为--filter和--format都是在本地做数据加工其实它们的执行位置完全不同。--filter不是简单的管道筛选它在发请求给 Docker 服务端时会把过滤条件编码到 API 请求里。换句话说Docker 守护进程在返回容器列表之前就已经在内部把不匹配的容器剔除掉了。客户端接到的列表本身就是过滤后的结果。这种做法的好处是如果一台机器上有上千个容器你不需要把全部数据拉到本地再筛网络开销和本地处理开销都小得多。--format则发生在客户端拿到数据之后。这是一个用 Go 模板语法渲染输出的过程你可以从返回结果里挑字段、拼字符串、做条件判断、甚至输出成 JSON 结构直接丢给jq做二次处理。它的本质是“为机器或人定制输出格式”不是数据筛选。想清楚这两者的分工你才能避免犯“用 format 做过滤”这种新手错误。2. --filter让服务端先把脏活干了2.1 filter 的机制拼错值不报错只是返回空--filter的短参是-f可以多次使用。一个很有意思的坑是当你把过滤值写错了Docker 并不会报错而是直接返回一个空列表。原因很简单——这个参数会被编码成服务端的查询条件服务端只是拿条件去匹配它不校验你的值是否“合法”。比如你写docker ps --filter statusexite少了一个 d服务端会认为你在要求“状态是 exite 的容器”然后老老实实返回空列表。这个行为非常容易误导人尤其是写脚本的时候明明容器就在那里但脚本拿到的列表是空的最后排查了半天发现是单词拼错了。所以如果你发现自己加了 filter 之后什么都查不到第一步不是怀疑环境、不是怀疑权限而是检查你的过滤值本身。多写几个状态值做交叉验证比如docker ps -f statusrunning -f statusexited -a看看是不是 filter 字段本身有问题。2.2 常用过滤维度速查Docker 官方支持和实操中常用的过滤 key 大致有下面这些我把它们整理成了表格方便你查。过滤 key作用说明示例name按容器名筛支持子串和正则-f namewebid按容器 ID 前缀筛-f id1a2b3cstatus按运行状态筛-f statusexitedlabel按标签键或键值对筛-f labelenvprodancestor按镜像名、镜像 tag 或镜像 ID 筛-f ancestornginxbefore筛选在该容器之前创建的容器-f beforeweb-oldsince筛选在该容器之后创建的容器-f sinceweb-oldvolume按挂载的命名卷筛-f volumemy-volumenetwork按接入的网络筛-f networkmy-netexpose按容器暴露端口筛-f expose8080publish按发布到宿主机的端口筛-f publish8080health按健康检查状态筛-f healthunhealthystatus可取的值包括created、restarting、running、removing、paused、exited、dead。初学者最容易搞混的是exited和stopped——Docker 里没有stopped状态停止的容器统一是exited理解这一点对写自动清理脚本很关键。label的使用也要留意。-f labelenv是筛“有 env 这个键的容器不看值”-f labelenvprod则是筛“键值都匹配的容器”。这个区分在做批量标签管理时非常实用。2.3 多条件组合同 key 是 OR不同 key 是 AND--filter可以传多次但组合规则很多人说不清楚。其实规则很简单不同 key 之间是 AND同一个 key 的多个值是 OR。举个例子docker ps -f statusrunning -f statusexited -a上面这种写法会同时返回 running 和 exited 的容器因为这两个条件都是status是或的关系。再看docker ps -f statusrunning -f namenginx这个则是“状态是 running 且名字包含 nginx”的容器条件之间是与的关系。这个语义差异决定了你写筛选命令的方式。如果你想表达“名字含 a 或名字含 b”不能直接写-f namea -f nameb那样反而会把条件收窄。正确做法是用 Docker 支持的正则写成-f namea|b。2.4 正则和隐式行为两个容易忽略的点name过滤器是支持正则的这个特性在做批量匹配的时候非常省事。比如你想查所有名字以api开头、后面跟数字的服务容器可以写docker ps --filter name^api-[0-9]注意官方容器名在部分场景下会有斜杠前缀例如早期版本匹配到的是/api-1。所以更稳妥的正则写法是允许斜杠存在name^/?api-[0-9]。还有一个隐性行为必须说清楚docker ps默认只看 running 状态的容器不加-a是看不到exited容器的。但当你使用-f statusexited这类非 running 状态条件时Docker 会隐含地跳出“只看 running”的限制直接按你的条件去所有容器里找。这就是为什么docker ps -f statusexited不加-a也能列出退出容器。如果你用-f namexxx去筛一个已经退出的容器那不加-a是看不到的。3. --format用 Go 模板定制输出3.1 模板字段与默认输出的坑--format接受的是一段 Go 模板字符串核心语法是{{.字段名}}。但这里的字段名和你在docker ps默认输出里看到的列名不是一一对应的。下面这张表是常用的字段我标出了容易踩坑的地方。模板字段输出内容注意事项{{.ID}}容器完整 ID不是 12 位短 ID而是一长串 64 位哈希{{.Names}}容器名本质是字符串数组直接打印会出现方括号{{.Image}}镜像名和 tag如nginx:latest{{.Command}}启动命令可能带双引号需要再处理{{.Status}}状态和时长如Up 2 hours或Exited (1) 10 minutes ago{{.Ports}}端口映射多个端口时是拼接在一起的{{.Labels}}标签 map要取值必须用index{{.Networks}}网络名字符串数组多个网络类似[a b]{{.Mounts}}挂载信息用表格模式可读性更好其中最容易劝退新人的是{{.Names}}。你在模板里写.Names输出却可能是[web-prod-1 web-prod-2]带着方括号和空格。原因是这个字段在 Docker API 返回的数据结构中是字符串数组Go 模板在渲染数组时默认加了[ ]。想要干净名字要么在模板里用join函数要么在管道后面用awk做二次清洗。再说 ID。{{.ID}}输出的是完整哈希如果你想让它和默认输出的 12 位短 ID 保持一致必须用trunc函数截断。docker ps --format {{.ID | trunc 12}} {{.Names}}这个细节很容易被忽视我第一次写格式化脚本时直接把 ID 打到日志里一行能有六十多个字符严重干扰阅读。3.2 模板函数的实测用法Docker 的模板引擎内置了一批函数常用到的有三类第一类是字符串处理函数。trunc截断、upper转大写、lower转小写、title首字母大写。比如docker ps --format {{.Image | upper}}第二类是格式化函数。printf可以在模板里做列宽对齐docker ps --format {{printf %-20s %s .Names .Status}}这里的%-20s是 Go 的格式化语法表示左对齐、占 20 个字符宽度。输出会比默认格式整齐不少。第三类是 JSON 输出函数。{{json .}}会输出整个容器信息的 JSON 结构这个在调试时非常好用docker ps --format {{json .}} | jq .Names有了它你就不用打开docker inspect去查字段结构先把当前语法能拿到的字段全部 dump 出来再人肉决定要哪几个。3.3 table 模式带表头又不费事如果想快速生成一个带列标题的报表用table前缀就行docker ps --format table {{.Names}}\t{{.Image}}\t{{.Status}}输出会自带NAMES、IMAGE、STATUS三个表头列与列之间用 Tab 分隔。这个格式在终端下阅读很舒服也能直接保存成 CSV 给后续表格工具处理。注意table模式下的表头是根据字段名自动生成的想看中文表头只能自己打印一行比如echo -e 容器名\t镜像\t状态 docker ps --format {{.Names}}\t{{.Image}}\t{{.Status}}3.4 Labels 取值的正确姿势{{.Labels}}是一个 map 类型直接打印会把所有键值对一起打出来既难读也不好在脚本里取某个值。正确方式是用index函数docker ps --format {{.Names}} env{{index .Labels env}}这个写法可以拿到容器指定 label 的值。如果 label 键不存在会输出no value。想更干净一点可以配合条件判断docker ps --format {{if .Labels}}有标签{{else}}无标签{{end}}条件判断在写监控脚本时很有用比如根据 env 标签决定打什么颜色标记或者根据 labels 是否存在做后续处理。4. 组合实战三条可以直接抄走的命令4.1 一次性清理 exited 僵尸容器我见过很多人手动清理退出容器一条条docker rm效率低且容易误删。配合status过滤和format拿 ID可以做一条安全又干净的清理命令。docker ps -a --filter statusexited --format {{.ID}} | xargs -r docker rm这里的-r是 GNU xargs 的选项表示“输入为空时不执行后面的命令”。如果你的环境是 macOS 自带的 BSD xargs不需要这个参数。整段命令的原理很简单先筛出所有 exited 状态的容器用--format {{.ID}}只输出容器 ID再交给xargs docker rm逐个删除。实际使用中我还会在正式执行前先打印一遍要删的容器名确认没有误伤docker ps -a --filter statusexited --format {{.Names}}确认无误后再执行删除。这个习惯在批量操作里永远值得保留。4.2 按项目标签生成运行状态报表如果容器在启动时都打了project标签按标签生成一张运行状态表非常简单docker ps --filter labelprojectweb --format table {{.Names}}\t{{.Status}}\t{{.Ports}}我通常还会在这个基础上加一个状态统计脚本这样每天巡检不用一条条数容器直接看汇总for s in created restarting running removing paused exited dead; do n$(docker ps -a --filter status$s -q | wc -l | tr -d ) echo $s: $n done这个脚本的本质是对前面说的status过滤做遍历汇总。跑完之后哪一类容器数量异常多一眼就能看出来。我把它存成了/usr/local/bin/container-stats巡检的时候就跑一下比docker stats在全局视角上清晰得多。4.3 批量重启/停止跑飞的服务容器服务的健康检查挂了、容器状态变成 unhealthy但还在 running这时候需要批量重启。用 label 加 status 组合筛出目标容器再重启docker ps --filter labelservicegateway --filter statusrunning --format {{.ID}} | xargs -r docker restart如果希望更精准还可以再加上health条件docker ps --filter labelservicegateway --filter healthunhealthy --format {{.ID}} | xargs -r docker restart这套命令的价值在于把“人肉找容器名”变成“机器按标签筛容器”。容器名是随机变化无所谓只要标签固定操作逻辑就是稳定的。这也是我强烈建议每个容器启动时都打统一标签的原因。5. 排错实录filter 和 format 的常见坑5.1 filter 没生效先检查这几件事第一条已经提过值拼错了不报错返回空。这个坑占比最大。第二条是状态过滤和-a的纠缠。想筛非 running 容器时建议统一加-a不要依赖 Docker 的隐式行为不同版本的 Docker 在边界场景处理上不完全一致显式加上最稳妥。第三条是条件语义搞反。区分“同 key OR不同 key AND”这个规则后很多看似奇怪的结果都能解释。比如你想匹配“服务 A 或服务 B”写成-f namea -f nameb结果为空这不奇怪因为这是“名字同时含 a 和 b”的容器根本不可能有。还有一类隐蔽问题filter 的 key 是大小写敏感的。-f Namefoo不是-f namefoo写错了会被当作未知过滤条件处理返回空。5.2 format 字段写错了会怎样--format对字段名的校验很宽松。写了一个不存在的字段它不会直接报错而是悄悄输出no value。比如有些人记不住Status的大小写写成{{.status}}结果整行输出全是no value还不知道问题出在哪。应对方法有两个一是先跑一遍docker ps --format {{json .}}把字段的真实拼写和类型 dump 出来对照着看二是写模板后用小范围容器做线下验证不要直接在生产环境刷屏。5.3 其他值得记住的小坑docker ps --format {{.Names}}输出的是数组形式如果要写入监控系统或日志建议先用awk清洗docker ps --format {{.Names}} | awk -F[][] {print $2}这样能把[web-prod-1 web-prod-2]里的内容抽出来。如果名字之间有空格后续可以再接tr , 统一分隔符。再一个是-q和--format的组合问题。-q明确要求“只输出 ID”很多版本下它会忽略--format的字段定制所以别指望docker ps -q --format {{.Names}}能输出名字。要么用-q要么用--format二者不要混着来。最后提醒一下模板里用printf做对齐时如果容器名超过设定的宽度Go 的格式化不会截断只会把你设定的宽度撑破输出看起来依然参差不齐。解决方式是先trunc再printfdocker ps --format {{printf %-20s (.Names | trunc 20)}}这种做法在生成对齐报表时特别有用。我在实际环境里踩过几次坑之后已经把这套 filter 和 format 的组合当成自己容器管理工具箱里的默认配置特别是把常用的几条存成了 shell 函数比如ps-running、ps-exited、ps-clean每次只要敲一个简单的别名就能拿到想要的视图。这个做法你也可以试试先花十分钟根据自己手头容器的标签和命名规则定制几条命令然后固化下来长期受益远比你想的大。
阅读完成 · 觉得有帮助?