link_path_walk()对于路径名最后一个分量的处理

转:

http://blog.chinaunix.net/uid-12567959-id-160997.html

此时,除了最后一个分量,原路径名的所有分量都已被解析。再来了解一下这时的环境,局部变量inode保存有父路径的目录的inode地址,nd->flags被设置了LOOKUP_CONTINUE标志,已经调用exec_permission(inode)对于父路径目录的执行权限进行了检查并通过。局部变量this中保存有最后一个分量的文件名信息,包括哈希值等,lookup_flags被设置了合适的标志值。link_path_walk()对于路径名最后一个分量的处理执行如下操作:
1、如果路径名是一反斜线“/”结尾的,则设置lookup_flags标志的LOOKUP_FOLLOW和LOOKUP_DIRECTORY位,以说明要查找的一定要是一个目录。
2、清除nd->flags中的LOOKUP_CONTINUE标志。
3、检查lookup_flags变量中LOOKUP_PARENT标志的值。如果没有设置此值,则说明它不是一个父路径名查找。此时执行如下操作:
a. 检查路径名最后一个分量的文件名字,如果最后一个分量名是“.”(单个圆点),这是对当前目录的引用,所要查找的值此时已经保存在nd中了。则检查文件系统的FS_REVAL_DOT标志(始终在目录项高速缓存中使“.”和“..”路径重新生效(针对网络文件系统)),若没有设置,则终止执行并返回值0(无错误);若设置了该标志,则调用方法nd-> path.dentry-> d_op-> d_revalidate(nd->path.dentry, nd) ,这个方法在把目录项对象转换为一个文件路径名之前,判定该目录项对象是否仍然有效。大多数文件系统将它设置为NULL,而网络文件系统可以指定自己的函数,若仍然有效,则link_path_walk()返回值0(无错误),若无效,则返回ESTALE。
回忆一下,link_path_walk()的调用者path_walk()对于这个返回值的处理是设置nd->path(保存有要查找的路径名的基路径)为之前保存的路径值,重新设置current->total_link_count为0,设置nd->flags的LOOKUP_REVAL标志(link_path_walk()更底层的do_lookup会适当处理这个标志),重新调用link_path_walk()。
b. 如果最后一个分量名是“..”(两个圆点),则做跟前面说明的相似的工作,尝试回到父目录。之后检查文件系统的FS_REVAL_DOT标志(始终在目录项高速缓存中使“.”和“..”路径重新生效(针对网络文件系统)),若没有设置,则终止执行并返回值0(无错误);若设置了该标志,则调用方法nd-> path.dentry-> d_op-> d_revalidate(nd->path.dentry, nd) ,这个方法在把目录项对象转换为一个文件路径名之前,判定该目录项对象是否仍然有效。大多数文件系统将它设置为NULL,而网络文件系统可以指定自己的函数,若仍然有效,则link_path_walk()返回值0(无错误),若无效,则返回ESTALE。与上一种情况的最后处理一样。
c. 最后的分量名既不是“.”,也不是“..”,调用do_lookup(nd, &this, &next)(878行),得到与给定的父目录(nd->path)和文件名(要解析的路径名分量&this)相关的目录项对象,存放在结果参数next中。后面“do_lookup()路径名查找”会有更详细的说明。 
d.检查刚解析的分量是否指向一个符号链接(next.dentry->d_inode具有一个i_op->follow_link方法)。如果是则调用do_follow_link(&next, nd)做相应的处理。将在后面“符号链接的查找”有更详细的说明。
e. 刚解析的分量不是指向一个符号链接调用path_to_nameidata(&next, nd),把nd->path.dentry和nd->path.mnt分别置为next.dentry和next.mnt,然后继续路径名的下一个分量。 
f.检查lookup_flags的LOOKUP_DIRECTORY标志,若设置了该标志,则检查最后返回的路径指向的是否是一个目录,若是,则返回0;若不是则返回ENOENT。
lookup_flags的LOOKUP_DIRECTORY标志则直接返回0。 
4、在很多情况下,查找操作的真正目的并不是路径名的最后一个分量,而是最后一个分量的前一个分量。例如,当文件被创建时,最后一个分量表示还不存在的文件的 文件名,而路径名中的其余路径指定新链接必须插入的目录。因此,查找操作应当取回最后分量的前一个分量的目录项对象。另举一个例子,我们执行rm -f /foo/bar,这里路径名/foo/bar表示的文件bar拆分出来就包含从其目录foo中移去bar。因此,内核真正的兴趣在于访问文件目录foo 而不是bar。
当查找操作必须解析的是包含路径名最后一个分量的目录而不是最后一个分量本身时,就使用LOOKUP_PARENT标志。当LOOKUP_PARENT标志被设置时,link_path_walk()函数也在nameidata数据结构中建立last和last_type字段。last字段存放路径名中的最后一个分量名。
 
ast_type字段标识最后一个分量的类型;可以把它置为如下所示的值之一:
LAST_NORM:最后一个分量是普通文件名
LAST_ROOT:最后一个分量是“/”(也就是整个路径名为“/”)
LAST_DOT:最后一个分量是“.”
LAST_DOTDOT:最后一个分量是“..”
LAST_BIND:最后一个分量是链接到特殊文件系统的符号链接
 
当整个路径名的查找操作开始时,LAST_ROOT标志是由path_lookup()设置的缺省值。如果路径名正好是“/”,则内核不改变last_type字段的初始值。last_type字段的其他值在LOOKUP_PARENT标志置位时由link_path_walk()设置


LOOKUP_PARENT标志的值,如果被置位则执行如下操作:
a. 把nd->last置为最后一个分量名。 
b. 把nd->last_type初始化为LAST_NORM。 
c. 如果最后一个分量名为“.”(一个圆点),则把nd->last_type置为LAST_DOT。 
d. 如果最后一个分量名为“..”(两个圆点),则把nd->last_type置为LAST_DOTDOT 
e. 通过返回值0(无错误)终止。 
可以看到,最后一个分量根本就没有被解释。因此,当函数终止时,nameidata数据结构的dentry和mnt字段指向最后一个分量所在目录对应的对象。

import os import shutil import re from collections import defaultdict def move_and_update_md_files(root_dir): # 确保根目录存在 if not os.path.isdir(root_dir): print(f"错误:目录不存在 - {root_dir}") return # 创建目标images目录 target_image_dir = os.path.join(root_dir, "images") os.makedirs(target_image_dir, exist_ok=True) # 存储文件名冲突解决方案 md_name_mapping = {} image_name_mapping = defaultdict(dict) # 第一轮:收集所有MD文件并计划重命名 for foldername, _, filenames in os.walk(root_dir): # 跳过根目录和目标images目录 if foldername == root_dir or foldername == target_image_dir: continue for filename in filenames: if filename.lower().endswith('.md'): orig_path = os.path.join(foldername, filename) # 处理MD文件名冲突 new_name = filename if filename in md_name_mapping: base, ext = os.path.splitext(filename) counter = 1 while f"{base}_{counter}{ext}" in md_name_mapping.values(): counter += 1 new_name = f"{base}_{counter}{ext}" md_name_mapping[orig_path] = new_name # 第二轮:处理图片和MD文件 for foldername, subfolders, filenames in os.walk(root_dir): # 跳过根目录和目标images目录 if foldername == root_dir or foldername == target_image_dir: continue # 处理MD文件 for filename in filenames: if not filename.lower().endswith('.md'): continue orig_md_path = os.path.join(foldername, filename) new_md_name = md_name_mapping.get(orig_md_path, filename) new_md_path = os.path.join(root_dir, new_md_name) # 读取并更新MD内容 with open(orig_md_path, 'r', encoding='utf-8') as f: content = f.read() # 更新图片引用 updated_content = re.sub( r'!\[.*?\]\((.*?)\)', lambda m: update_image_ref(m, foldername, target_image_dir, image_name_mapping), content ) # 写入新位置 with open(new_md_path, 'w', encoding='utf-8') as f: f.write(updated_content) # 删除原MD文件 os.remove(orig_md_path) # 处理图片文件 image_dir = os.path.join(foldername, "images") if os.path.isdir(image_dir): for img_name in os.listdir(image_dir): src_path = os.path.join(image_dir, img_name) if not os.path.isfile(src_path): continue # 处理图片名冲突 new_img_name = img_name if img_name in image_name_mapping: base, ext = os.path.splitext(img_name) counter = 1 while f"{base}_{counter}{ext}" in os.listdir(target_image_dir): counter += 1 new_img_name = f"{base}_{counter}{ext}" dest_path = os.path.join(target_image_dir, new_img_name) shutil.move(src_path, dest_path) image_name_mapping[img_name] = new_img_name # 删除空子文件夹 for foldername, subfolders, filenames in os.walk(root_dir, topdown=False): if foldername != root_dir and not os.listdir(foldername): os.rmdir(foldername) def update_image_ref(match, orig_folder, target_image_dir, name_mapping): orig_ref = match.group(1) img_path = os.path.normpath(os.path.join(orig_folder, orig_ref)) # 如果图片已移动 if os.path.isfile(img_path): img_name = os.path.basename(img_path) new_name = name_mapping.get(img_name, img_name) return match.group(0).replace(orig_ref, f"images/{new_name}") return match.group(0) if __name__ == "__main__": # 使用当前目录作为根目录,或替换为您的路径 target_directory = os.getcwd() print("开始整理文件和图片...") move_and_update_md_files(target_directory) print("操作完成!") print(f"所有MD文件已移动到: {target_directory}") print(f"所有图片已移动到: {os.path.join(target_directory, 'images')}") 修改代码,这个代码运行后md文件与图片的对应关系发生变化,对每一个文件夹内部进行处理时,应该创建一个字典,保存原文件名和新文件名的对应关系,再重命名图片,根据字典修改引用,最后再将所有文件移到最外面。
最新发布
07-29
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值