当前位置: 首页 > news >正文

利用 Python 脚本批量创建空白 Markdown 笔记

文章目录

  • 利用 Python 脚本批量创建空白 Markdown 笔记
    • 1 背景介绍
    • 2 需求描述
    • 3 明确思路
    • 4 具体实现
      • 4.1. 遍历 toc.md 文件,收集文件名和对应的文件内容
      • 4.2. 实现文件批量生成逻辑
      • 4.3. 补全缺失的工具函数
      • 4.4. 进一步补全工具函数中的工具函数
    • 5 脚本运行
    • 6 注意事项

利用 Python 脚本批量创建空白 Markdown 笔记

1 背景介绍

我学视频课有个习惯:每学完一节课,就相应创建一个 Markdown 格式的纯文本自学笔记,方便日后查阅。久而久之,要创建的笔记文件越来越多,如果再要求这些文件都具备统一的格式(例如文件名的命名风格、统一的标题模板等等),无形中就会多出不少工作量。之前我都是用 PowerShell + ChatGPT 来批量生成这些文件夹和文件名,虽然脚本编写比较繁琐,但可以一劳永逸,倒也划算;前段时间入坑 Python,正好可以练练文件操作和正则表达式,才发现 Python 处理起这类问题来比 PowerShell 轻松太多了。

2 需求描述

这里随便找了一个视频课,原始的课程目录长这样:

图 1:原始课程目录结构(节选)

【图 1:原始课程目录结构(节选)】

复制到一个临时文件 toc.md 后长这样:

图 2:复制到临时文件 toc.md 后的课程目录(节选)

【图 2:复制到临时文件 toc.md 后的课程目录(节选)】

而我想要的效果,是用 Python 脚本批量生成这样的文件结构:

图 3:最终希望实现的笔记目录结构与内容模板

【图 3:最终希望实现的笔记目录结构与内容模板】

3 明确思路

再复杂的需求也可以拆分成若干个可以轻松实现的单元。多行的批量处理可以先简化为某一行的处理:

  1. 读取 toc.md 的某一行,赋给一个变量 line
  2. 判定 line 是按文件夹处理,还是按 .md 文件处理(通过正则表达式判定);
    1. 是文件夹:
      1. 创建该文件夹;
      2. 提取章节编号(如 Ch01),以便本章笔记文件使用;
      3. 重置笔记文件的二级目录编号(比如上一行编号为 Ch01.2,这一行需要重置为 Ch2.1
    2. 是文件:
      1. 获取当前章节编号,生成对应的文件名
      2. 获取当前章节编号,生成对应的 Markdown 文本内容
      3. 二级目录编号递增 1
  3. 遍历完 toc.md 后,根据文件名是否以 .md 结尾,可以判定生成的是文件夹还是 Markdown 文件:
    1. 是文件夹:创建该文件夹,并更新当前路径(curr_path,以便后续笔记文件引用);
    2. 是文件:根据当前路径生成对应的笔记文件,同时写入文本内容。

4 具体实现

4.1. 遍历 toc.md 文件,收集文件名和对应的文件内容

创建 Python 脚本文件 generate_files.py,确定信息采集逻辑:

def make_chapter(line):pass
def make_section(line, chp_num, sec_num):pass# 参数初始化
chp_num = '0'  # 章节号
sec_num = 1    # 小节号
(contents, file_names) = ([], []) # 初始化章节内容、文件名# 读取课程目录文件
with open('./toc.md', 'r') as f:# 读取文件的每一行,去掉行尾的换行符,然后存入列表 lineslines = [l.strip() for l in f.readlines()] for line in lines:# 判断行首是否是数字,如果是,说明是章节标题starts_with_digit = re.match(r'^(\d+)\.', line)if starts_with_digit:# 如果是章节标题,则生成该章节的 文件名 和 章节号(file_name, chapter_num) = make_chapter(line)# 添加本章的章节内容(仅占位用,文件夹没有文本内容)contents.append('placeholder')# 添加本章的文件名file_names.append(file_name)# 更新章节号chp_num = chapter_numelse:# 如果不是章节标题,则生成该小节的 文本内容 和 文件名(sec_file_name, sec_content) = make_section(line, chp_num, sec_num)# 添加该小节的文本内容file_names.append(sec_file_name)# 添加该小节的文件名contents.append(sec_content)# 按本行的实际作用动态更新小节编号sec_num = 1 if starts_with_digit else (sec_num + 1)

注意:make_chapter 函数(L1、L22)和 make_section 函数(L3、L34)暂不实现,先确定大流程。

4.2. 实现文件批量生成逻辑

利用 4.1 的信息采集逻辑,就可以批量生成文件/文件夹了:

# 批量生成文件
curr_path = ''
for index, (file_or_path, cont) in enumerate(zip(file_names, contents)):if file_or_path.endswith('.md'):file = file_or_pathwith open(f'./{curr_path}/{file}', 'w') as f:f.write(cont + '\n')else:path = file_or_pathos.makedirs(path, exist_ok=True)curr_path = path

4.3. 补全缺失的工具函数

大流程确定后,再来实现 make_chapter 函数和 make_section 函数:

#!/usr/bin/env pythonimport re
import osdef dashed(s, sep=' '):passdef sanitize_filename(s):passdef repl(match):passdef make_chapter(line):"""Generates the specific file name and the corresponding chapter number for this chapter.Args:line (string): The original line of text to be processed.Returns:tuple: A tuple containing the file name and the chapter number.Example:Input: line: '1. Getting Started with GitHub Actions'Output: ('Ch01_Getting_Started_with_GitHub_Actions.md', '1')"""pttn = r'(\d+)\.(.*)'re.findall(pttn, line)file_name = re.sub(pttn, repl, line)chapter_num = re.sub(pttn, r'\1', line)   return (file_name, chapter_num)def make_section(line, chapter_num, section_num):"""Generates the specific file name and corresponding content for this section.Args:line (string): The original line of text to be processed.chapter_num (string): The current chapter number.section_num (int): The current section number.Returns:tuple: A tuple containing the file name and the content of the section.Example:Input: line: '   - Welcome to the Course!'chapter_num: '1'section_num: 1Output: ('Ch01_1_Introduction_to_the_Course.md', '## Ch01.1 Introduction to the Course')"""line = line.strip('- ')header_part = f'Ch{chapter_num.zfill(2)}_{section_num}'content_part = dashed(line, '_').replace('---', '_')file_name = f'{header_part}_{content_part}'title_content = f'## Ch{chapter_num.zfill(2)}.{section_num} {line}'return (f'{sanitize_filename(file_name)}.md', title_content)

4.4. 进一步补全工具函数中的工具函数

为了方便管理,再对工具函数中出现的几个工具函数做进一步实现:

def dashed(s, sep=' '):"""Removes leading and trailing whitespaces and replaces all other whitespaces with the specified separator.Args:s (str): The original string to be processedsep (str, optional): The separator string. Defaults to ' '.Returns:string: The processed string."""return sep.join(s.strip().split())def sanitize_filename(s):"""Sanitizes the file name by removing all characters except letters, numbers, underscores, and dots.Args:s (string): The original file name to be sanitized.Returns:string: The sanitized file name."""# 只保留字母、数字、下划线和点,其他字符都删除pttn = r'[^\w -]'re.findall(pttn, s)return re.sub(pttn, '', s)def repl(match):"""Generates the specific file name for this chapter.Args:match (Match[string]): The matched object from the regular expression.Returns:string: The file name for this chapter."""num = match.group(1)content = match.group(2)file_name = f'Ch{num.zfill(2)}_{dashed(content, "_")}'return sanitize_filename(file_name)

5 脚本运行

使用以下命令运行脚本:

python generate_files.py

不到两秒,就生成了所有的文件夹和 Markdown 空白文件:

图 4:检查最终的批量生成结果(符合预期)

【图 4:检查最终的批量生成结果(符合预期)】

6 注意事项

由于是第一次尝试 Python 脚本,调试过程中走了不少弯路,这里集中梳理一下:

  1. 一定要小步走,多迭代;
  2. 按抽象层次依次实现每一层的函数逻辑,方便后续管理;
  3. 读取每一行文本都会包含一个 \n 换行符,需要用 str.strip() 处理掉;
  4. 先用 re.findall 捕获匹配结果,然后再用 re.sub 执行替换,否则后者始终输出空字符串;
  5. 访问捕获的结果时,注意 f-stringr-string 中反斜杠 \ 的不同写法,前者要写成 \\,而后者直接写成 \ 即可;
  6. 脚本运行前提前做好备份(以避免 rm -Recurse -Force *Python 脚本文件本身也删没了的杯具。。);
  7. 尽量避免引用全局变量,多用参数传参(本例必须使用全局变量,以实时获取当前的文件夹名称和路径);
  8. 主体框架确定后,今后只需要修改文件夹和文件的处理逻辑,就可以适应不同的原始数据。
  9. os.makedirs(path, exist_ok=True) 中的 exist_ok 参数用于控制在指定路径已经存在时的行为:
  10. exist_ok=True 时,如果目标目录已经存在,makedirs 不会抛出异常(本例暂不涉及);
  11. exist_ok=False(默认值)时,如果目标目录已存在,则会抛出 FileExistsError 异常。
  12. 重视代码注释和函数文档(docstring)的书写,既方便别人,也方便自己(主要是写给自己看的)

相关文章:

利用 Python 脚本批量创建空白 Markdown 笔记

文章目录 利用 Python 脚本批量创建空白 Markdown 笔记1 背景介绍2 需求描述3 明确思路4 具体实现4.1. 遍历 toc.md 文件,收集文件名和对应的文件内容4.2. 实现文件批量生成逻辑4.3. 补全缺失的工具函数4.4. 进一步补全工具函数中的工具函数 5 脚本运行6 注意事项 利…...

【Qt】C++11 Lambda表达式

1. 举例 connect(ui->pushButton, &QPushButton::clicked, [](bool checked){//具体代码qDebug() << "Hello" << checked;}); 2. 详情 //完整形式 [ capture ] ( params ) opt -> ret { body; }; capture 是捕获列表params 是参数表opt 是函数…...

怎样提高服务器中的数据传输速度?

服务器中的数据传输速度会影响着用户的体验感&#xff0c;当企业中的数据传输速度出现卡顿或者是过慢时&#xff0c;用户不能及时浏览到所需的内容&#xff0c;给用户造成不好的体验感&#xff0c;那么企业该怎样才能提高服务器中的数据传输速度呢&#xff1f; 服务器之间如何传…...

Vue 封装公告滚动

文章目录 需求分析1. 创建公告组件Notice.vue2. 注册全局组件3. 使用 需求 系统中需要有一个公告展示&#xff0c;且这个公告位于页面上方&#xff0c;每个页面都要看到 分析 1. 创建公告组件Notice.vue 第一种 在你的项目的合适组件目录下&#xff08;比如components目录&a…...

JVM实战—12.OOM的定位和解决

大纲 1.如何对系统的OOM异常进行监控和报警 2.如何在JVM内存溢出时自动dump内存快照 3.Metaspace区域内存溢出时应如何解决(OutOfMemoryError: Metaspace) 4.JVM栈内存溢出时应如何解决(StackOverflowError) 5.JVM堆内存溢出时应该如何解决(OutOfMemoryError: Java heap s…...

【python翻译软件V1.0】

如果不想使用密钥的形式&#xff0c;且需要一个直接可用的中英文翻译功能&#xff0c;可以使用一些免费的公共 API&#xff0c;如 opencc 或其他无需密钥的库&#xff0c;或直接用 requests 获取翻译结果。 其中&#xff0c;我可以给你一个简单的代码示例&#xff0c;使用 tra…...

Spring Boot中的依赖注入是如何工作

Spring Boot 中的依赖注入&#xff08;Dependency Injection&#xff0c;简称 DI&#xff09;是通过 Spring 框架的核心机制——控制反转&#xff08;Inversion of Control&#xff0c;IOC&#xff09;容器来实现的。Spring Boot 基于 Spring Framework&#xff0c;在应用中自动…...

ubuntu22.04 编译安装libvirt 10.x

环境安装 sudo apt-get update -y sudo apt-get install qemu-system-x86 bridge-utils libyajl-dev -y sudo apt-get install build-essential autoconf automake libtool -y sudo apt-get install libxml2-dev libxslt1-dev libgnutls28-dev libpciaccess-dev libnl-3-de…...

[fastadmin] 第三十四篇 FastAdmin 商城模块标签使用详解

FastAdmin 商城模块标签使用详解 一、标签基本语法 1.1 基础语法格式 {shop:goodslist flag"参数值" id"变量名" row"数量"}<!-- 循环内容 --> {/shop:goodslist}1.2 常用参数说明 flag: 商品标记筛选id: 循环变量名row: 显示数量 1.…...

(2024,LLaVA-Bench (Wilder),LLaVA-NeXT,LLaMA3,Qwen-1.5,语言模型扩展)

LLaVA-NeXT: Stronger LLMs Supercharge Multimodal Capabilities in the Wild 目录 1. 简介 2. 探索大规模语言模型的能力极限 3. LLaVA-Bench (Wilder)&#xff1a;日常生活视觉聊天基准 4. Benchmark 结果 1. 简介 我们通过引入近期更强大的开源大语言模型&#xff08;…...

IPEX-LLM开发项目过程中的技术总结和心得

IPEX-LLM开发项目过程中的技术总结和心得 在人工智能快速发展的时代&#xff0c;高效地开发和部署大语言模型&#xff08;LLM&#xff09;已成为技术人员的必备技能。在我们的项目中&#xff0c;我们采用了 Intel Extension for PyTorch&#xff08;简称 IPEX&#xff09;和 L…...

HTTP/HTTPS ②-Cookie || Session || HTTP报头

这里是Themberfue 上篇文章介绍了HTTP报头的首行信息 本篇我们将更进一步讲解HTTP报头键值对的含义~~~ ❤️❤️❤️❤️ 报头Header ✨再上一篇的学习中&#xff0c;我们了解了HTTP的报头主要是通过键值对的结构存储和表达信息的&#xff1b;我们已经了解了首行的HTTP方法和UR…...

【软考】软件设计师

「学习路线」&#xff08;推荐该顺序学习&#xff0c;按照先易后难排序&#xff09; 1、上午题—计算机系统&#xff08;5~6分&#xff09;[1.8; ] 2、上午题—程序设计语言&#xff08;固定6分&#xff09;[1.9; ] 3、下午题—试题一&#xff08;15分&#xff09; 4、上午题—…...

K8s Pod OOMKilled,监控却显示内存资源并未打满

1. 问题现象 pod一直重启&#xff0c;通过grafana查看&#xff0c;发现内存使用率并没有100%。 2. 排查过程 2.1 describe查看pod最新一次的状态 可以明显看到&#xff0c;最近一次的重启就是因为内存不足导致的。 2.2 describe 查看node节点状态 找到原因了&#xff0c;原来…...

C++ 原子变量

C 原子变量 文章目录 C 原子变量1. 原子变量是什么&#xff1f;2. 原子操作的特点3. 原子变量的作用1. 多线程安全的共享数据访问2. 替代锁机制3. 实现低级同步算法 4. 原子变量的常见操作5. 内存顺序&#xff08;Memory Ordering&#xff09;内存顺序控制在原子变量中的作用如…...

linux网络 | http结尾、理解长连接短链接与cookie

前言&#xff1a;本节是http章节的最后一部分&#xff0c;主要解释一些小概念。讲解到了HTTP的方法&#xff0c;表单&#xff0c; 重定向等等。 现在废话不多说&#xff0c; 开始我们的学习吧。 ps&#xff1a;本节内容都是概念&#xff0c; 知道就行&#xff0c; 友友们放心观…...

金融项目实战 02|接口测试分析、设计以及实现

目录 ⼀、接口相关理论 二、接口测试 1、待测接口&#xff1a;投资业务 2、接口测试流程 3、设计用例理论 1️⃣设计方法 2️⃣工具 4、测试点提取 5、测试用例 ⼀、接口相关理论 1、ui功能测试和接⼝测试那个先执⾏&#xff1f;为什么&#xff1f; 结论&#xff1a…...

二、智能体强化学习——深度强化学习核心算法

2.1 DQN 系列及其改进 2.1.1 背景与动机 在经典强化学习中&#xff08;如 Q-Learning&#xff09;&#xff0c;如果状态空间或动作空间非常大乃至连续&#xff0c;那么用一个表格来存储 Q ( s , a ) Q(s,a) Q(s,a) 不再可行。为了解决该问题&#xff0c;可以使用神经网络来逼…...

Mysql--架构篇--存储引擎InnoDB(内存结构,磁盘结构,存储结构,日志管理,锁机制,事务并发控制等)

MySQL是一个多存储引擎的数据库管理系统&#xff0c;支持多种不同的存储引擎。每种存储引擎都有其独特的特性、优势和适用场景。选择合适的存储引擎对于优化数据库性能、确保数据完整性和满足业务需求至关重要。 注&#xff1a;在同一个Mysql的数据库中&#xff0c;对于不同的表…...

JVM实战—13.OOM的生产案例

大纲 1.每秒仅上百请求的系统为何会OOM(RPC超时时间设置过长导致QPS翻几倍) 2.Jetty服务器的NIO机制如何导致堆外内存溢出(S区太小 禁NIO的显式GC) 3.一次微服务架构下的RPC调用引发的OOM故障排查实践(MAT案例) 4.一次没有WHERE条件的SQL语句引发的OOM问题排查实践(使用MA…...

使用VSCode开发Django指南

使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架&#xff0c;专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用&#xff0c;其中包含三个使用通用基本模板的页面。在此…...

服务器硬防的应用场景都有哪些?

服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式&#xff0c;避免服务器受到各种恶意攻击和网络威胁&#xff0c;那么&#xff0c;服务器硬防通常都会应用在哪些场景当中呢&#xff1f; 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战

在现代战争中&#xff0c;电磁频谱已成为继陆、海、空、天之后的 “第五维战场”&#xff0c;雷达作为电磁频谱领域的关键装备&#xff0c;其干扰与抗干扰能力的较量&#xff0c;直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器&#xff0c;凭借数字射…...

QT: `long long` 类型转换为 `QString` 2025.6.5

在 Qt 中&#xff0c;将 long long 类型转换为 QString 可以通过以下两种常用方法实现&#xff1a; 方法 1&#xff1a;使用 QString::number() 直接调用 QString 的静态方法 number()&#xff0c;将数值转换为字符串&#xff1a; long long value 1234567890123456789LL; …...

重启Eureka集群中的节点,对已经注册的服务有什么影响

先看答案&#xff0c;如果正确地操作&#xff0c;重启Eureka集群中的节点&#xff0c;对已经注册的服务影响非常小&#xff0c;甚至可以做到无感知。 但如果操作不当&#xff0c;可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...

Razor编程中@Html的方法使用大全

文章目录 1. 基础HTML辅助方法1.1 Html.ActionLink()1.2 Html.RouteLink()1.3 Html.Display() / Html.DisplayFor()1.4 Html.Editor() / Html.EditorFor()1.5 Html.Label() / Html.LabelFor()1.6 Html.TextBox() / Html.TextBoxFor() 2. 表单相关辅助方法2.1 Html.BeginForm() …...

实战三:开发网页端界面完成黑白视频转为彩色视频

​一、需求描述 设计一个简单的视频上色应用&#xff0c;用户可以通过网页界面上传黑白视频&#xff0c;系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观&#xff0c;不需要了解技术细节。 效果图 ​二、实现思路 总体思路&#xff1a; 用户通过Gradio界面上…...

【实施指南】Android客户端HTTPS双向认证实施指南

&#x1f510; 一、所需准备材料 证书文件&#xff08;6类核心文件&#xff09; 类型 格式 作用 Android端要求 CA根证书 .crt/.pem 验证服务器/客户端证书合法性 需预置到Android信任库 服务器证书 .crt 服务器身份证明 客户端需持有以验证服务器 客户端证书 .crt 客户端身份…...

在Spring Boot中集成RabbitMQ的完整指南

前言 在现代微服务架构中&#xff0c;消息队列&#xff08;Message Queue&#xff09;是实现异步通信、解耦系统组件的重要工具。RabbitMQ 是一个流行的消息中间件&#xff0c;支持多种消息协议&#xff0c;具有高可靠性和可扩展性。 本博客将详细介绍如何在 Spring Boot 项目…...

用 FFmpeg 实现 RTMP 推流直播

RTMP&#xff08;Real-Time Messaging Protocol&#xff09; 是直播行业中常用的传输协议。 一般来说&#xff0c;直播服务商会给你&#xff1a; ✅ 一个 RTMP 推流地址&#xff08;你推视频上去&#xff09; ✅ 一个 HLS 或 FLV 拉流地址&#xff08;观众观看用&#xff09;…...