跳到主要内容

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求最大值 + 1next_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


六、实践建议

  1. 避免链式使用 regex_search + map('int')
    → 改用 select('match') 过滤非匹配项。

  2. 善用 ternarydefault 处理空列表
    确保无匹配时返回默认值,避免 max() 报错。

  3. 调试阶段使用 debug: var=
    查看变量实际值,有助于确认模板逻辑。

  4. 编写模板时遵循 “先过滤后转换” 原则
    避免在 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)
空结果处理使用 ternarydefault 提供安全默认值
正则提取数字regex_replace + 分组捕获是最稳妥方式
自动编号逻辑max()+1,或用循环检测空位(按需)
调试技巧- debug: var=变量名 查看模板渲染结果

九、扩展阅读


🔚 十、总结

概念错误原因修复思路
regex_search未匹配返回 Noneselect('match') 先过滤
max() 空输入列表为空时报错加上默认 [0]ternary
字符串转数字None 无法转 int先过滤后转换
模板健壮性模板不应假设输入总正确加默认值与类型安全

最佳实践公式:

过滤 → 提取 → 类型转换 → 聚合 → 拼接
保证任何输入都不会触发异常。