2025-10-23
:::tip 一言 落日心犹壮,秋风病欲疏。 --- 《江汉》 · 唐代 杜甫 :::
Details
全文
江汉思归客,乾坤一腐儒。片云天共远,永夜月同孤。
落日心犹壮,秋风病欲疏。
古来存老马,不必取长途。
tmux退出
在 tmux 中退出会话有几种方式,取决于你想关闭整个会话还是仅退出当前窗口/面板:
🧱 一、退出当前面板(Pane)
如果你只想离开当前 shell(比如 bash/zsh),可以直接输入:
exit
或使用快捷键:
Ctrl + D
👉 若当前窗口的所有面板都关闭,该窗口会自动消失;
若整个会话所有窗口都关闭,tmux 会自动结束。
🪟 二、关闭整个窗口(Window)
在 tmux 窗口中执行:
exit
或按下:
Ctrl + D
多次直到该窗口消失。
或者使用快捷键强制关闭:
Ctrl + B & → 确认 y
这会关闭当前窗口。
🧩 三、关闭整个会话(Session)
如果你想彻底退出整个 tmux 会话(比如叫 my-session):
tmux kill-session -t my-session
或者在会话内部按:
Ctrl + B :kill-session
也可以在会话外列出并关闭:
tmux ls # 查看会话列表
tmux kill-session -t 0 # 杀掉第0个会话
💡 四、暂时退出(分离而不关闭)
如果你只是想“挂起”tmux稍后再回来(保留程序运行):
Ctrl + B D
会提示:
[detached (from session 0)]
稍后可重新进入:
tmux attach -t 0
或:
tmux a
🚀 五、彻底杀掉所有 tmux 会话
如果想“一键清空”所有 tmux 实例:
tmux kill-server
这会终止所有会话和进程。
当然可以 ✅
下面是调整为从 ## 开始标题层级的版本,所有标题层级整体下移一级,便于直接放入项目文档体系(如放到 docs/ansible/ 下)。
🧩 Ansible Jinja2 模板正则处理与异常防护实践
一、问题背景
在编写 Ansible Playbook 时,我们常常需要根据已有文件名自动计算下一个可用的序号,例如:
2025-10-22.tar.gz
2025-10-22-1.tar.gz
2025-10-22-2.tar.gz
目标是:
如果文件存在,就自动在文件名后加序号(如
-1、-2、-3)直到不重复。
原始思路:
set_fact:
nums: >-
{{
_found.files
| map(attribute='path')
| map('basename')
| map('regex_search', '^' ~ _date ~ '(?:-(\\d+))?\\.tar\\.gz$', '\\1')
| map('default', '0', true)
| map('int')
| list
}}
next_num: "{{ (nums | max) + 1 if (_found.matched | int) > 0 else 0 }}"
但执行时报错:
'NoneType' object has no attribute 'group'
二、错误原因分析
Jinja2 的 regex_search 在 未匹配时返回 None。
后续 map('int') 等操作期望输入为字符串或数字,而不是 None,从而触发了异常:
AttributeError: 'NoneType' object has no attribute 'group'
即便使用 default('0', true),它仅在变量未定义时生效,对 None 值不生效,因此依旧会报错。
三、正确写法:安全的正则与过滤逻辑
使用 select('match') + regex_replace 组合可以安全处理。
- name: Compute next available dest name (safe)
ansible.builtin.set_fact:
archive_dest: "{{ base_dir }}/{{ _date }}{{ suffix }}.tar.gz"
vars:
basenames: >-
{{ _found.files | map(attribute='path') | map('basename') | list }}
# 是否存在无序号文件(_date.tar.gz)
base_exists: >-
{{ basenames | select('equalto', _date ~ '.tar.gz') | list | length > 0 }}
# 仅筛选匹配 “-数字.tar.gz” 的文件
numbers: >-
{{
basenames
| select('match', '^' ~ _date ~ '-\\d+\\.tar\\.gz$')
| map('regex_replace', '^' ~ _date ~ '-(\\d+)\\.tar\\.gz$', '\\1')
| map('int')
| list
}}
# 把无序号文件看作编号0
taken: >-
{{
(base_exists | ternary([0], [])) + numbers
}}
# 求最大值 + 1
next_num: >-
{{ 0 if (taken | length) == 0 else (taken | max) + 1 }}
# 拼接最终文件名后缀
suffix: "{{ '' if next_num == 0 else '-' ~ next_num }}"
✅ 关键改进:
-
select('match')确保只对匹配的字符串执行正则; -
regex_replace安全地提取数字部分; -
用
ternary合并基本文件编号 0; -
不依赖
regex_search,完全避免NoneType错误。
四、执行流程解析
| 步骤 | 动作 | 结果 |
|---|---|---|
| 1 | 读取所有已有 .tar.gz 文件名 | ['2025-10-22.tar.gz', '2025-10-22-1.tar.gz'] |
| 2 | 判断是否有“无序号”文件 | base_exists = true |
| 3 | 提取所有有序号的数字部分 | numbers = [1] |
| 4 | 合并编号列表 | taken = [0,1] |
| 5 | 求最大值 + 1 | next_num = 2 |
| 6 | 拼接文件名后缀 | suffix = -2 |
| 7 | 生成新文件名 | 2025-10-22-2.tar.gz |
五、Jinja2 正则函数说明表
| 函数 | 说明 | 返回值 |
|---|---|---|
regex_search(pattern, [group]) | 查找匹配并返回分组 | 未匹配时返回 None |
regex_replace(pattern, replace) | 替换匹配部分 | 始终返回字符串 |
select('match', pattern) | 过滤出匹配正则的元素 | 安全过滤列表 |
map('int') | 将字符串转换为数字 | 仅当输入可解析为数字 |
🚨 注意:
regex_search容易在未匹配时返回None导致错误,推荐使用select('match')过滤后再regex_replace。
六、实践建议
-
避免链式使用
regex_search+map('int')
→ 改用select('match')过滤非匹配项。 -
善用
ternary和default处理空列表
确保无匹配时返回默认值,避免max()报错。 -
调试阶段使用
debug: var=
查看变量实际值,有助于确认模板逻辑。 -
编写模板时遵循 “先过滤后转换” 原则
避免在None上调用字符串方法。
七、完整任务上下文示例
- name: Find existing archives for this date
find:
paths: "{{ base_dir }}"
patterns: "{{ _date }}*.tar.gz"
file_type: file
register: _found
- name: Compute next available dest name (safe)
set_fact:
archive_dest: "{{ base_dir }}/{{ _date }}{{ suffix }}.tar.gz"
vars:
basenames: "{{ _found.files | map(attribute='path') | map('basename') | list }}"
base_exists: "{{ basenames | select('equalto', _date ~ '.tar.gz') | list | length > 0 }}"
numbers: >-
{{
basenames
| select('match', '^' ~ _date ~ '-\\d+\\.tar\\.gz$')
| map('regex_replace', '^' ~ _date ~ '-(\\d+)\\.tar\\.gz$', '\\1')
| map('int')
| list
}}
taken: "{{ (base_exists | ternary([0], [])) + numbers }}"
next_num: "{{ 0 if (taken | length) == 0 else (taken | max) + 1 }}"
suffix: "{{ '' if next_num == 0 else '-' ~ next_num }}"
- name: Compress the files
archive:
path: "{{ base_dir }}/{{ _date }}"
dest: "{{ archive_dest }}"
format: gz
remove: true
八、知识点速查表
| 主题 | 关键要点 |
|---|---|
| Jinja2 模板异常 | regex_search 返回 None 时链式操作会报错 |
| 解决思路 | 先过滤 (select('match')) 再替换 (regex_replace) |
| 空结果处理 | 使用 ternary 或 default 提供安全默认值 |
| 正则提取数字 | regex_replace + 分组 捕获是最稳妥方式 |
| 自动编号逻辑 | 取 max()+1,或用循环检测空位(按需) |
| 调试技巧 | - debug: var=变量名 查看模板渲染结果 |
九、扩展阅读
🔚 十、总结
| 概念 | 错误原因 | 修复思路 |
|---|---|---|
regex_search | 未匹配返回 None | 用 select('match') 先过滤 |
max() 空输入 | 列表为空时报错 | 加上默认 [0] 或 ternary |
| 字符串转数字 | None 无法转 int | 先过滤后转换 |
| 模板健壮性 | 模板不应假设输入总正确 | 加默认值与类型安全 |
✅ 最佳实践 公式:
过滤 → 提取 → 类型转换 → 聚合 → 拼接
保证任何输入都不会触发异常。