如何递归删除Amazon S3指定子文件夹?
我明白你现在的困扰——本来只想删bucket/do-not-delete-folder/delete-this-folder-recursively下的内容,结果要么误删了上层文件夹,要么碰到权限报错。我来帮你拆解问题,再给你一个靠谱的实现方案。
先分析你遇到的两个问题
1. 第一个函数误删上层文件夹
S3是扁平存储,所谓的“文件夹”只是前缀的一部分。你传入的前缀是do-not-delete-folder/delete-this-folder/,但如果函数里的前缀匹配逻辑有问题(比如没正确处理结尾的斜杠,或者在拼接前缀时出错),就可能会匹配到do-not-delete-folder/开头的所有对象,导致误删。比如如果函数里不小心把前缀写成了do-not-delete-folder/(少了后面的部分),或者用了不带结尾斜杠的前缀,S3会匹配所有以该字符串开头的对象,包括上层的内容。
2. 第二个函数的Access Denied问题
同一Lambda同一账号却报错,大概率是权限策略的资源范围不对,或者函数里的操作试图访问超出权限的资源。比如如果你的权限策略只给了delete-this-folder/下的删除权限,但函数里在列对象时需要访问整个bucket的list权限,或者不小心访问了其他路径的资源,就会触发拒绝。另外,如果存储桶开启了版本控制,而你的权限里没有s3:DeleteObjectVersion权限,也会报错。
正确的递归删除实现(Python + Boto3)
下面这个函数会严格限定只删除指定前缀下的所有对象(包括嵌套子“文件夹”对应的零字节对象),不会碰上层内容。如果你的存储桶开启了版本控制,也会一并处理所有版本和删除标记:
import boto3 def delete_s3_prefix_recursively(bucket_name, prefix): # 确保前缀以斜杠结尾,避免匹配到类似delete-this-folder123的前缀 if not prefix.endswith('/'): prefix += '/' s3 = boto3.client('s3') paginator = s3.get_paginator('list_objects_v2') # 分页列出所有指定前缀下的对象 page_iterator = paginator.paginate( Bucket=bucket_name, Prefix=prefix, PaginatorConfig={'PageSize': 1000} ) # 批量删除对象,每次最多1000个 for page in page_iterator: if 'Contents' not in page: continue objects_to_delete = [{'Key': obj['Key']} for obj in page['Contents']] # 如果存储桶开启了版本控制,取消下面注释处理版本和删除标记 # if 'Versions' in page: # objects_to_delete = [{'Key': obj['Key'], 'VersionId': obj['VersionId']} for obj in page['Versions']] # if 'DeleteMarkers' in page: # objects_to_delete.extend([{'Key': obj['Key'], 'VersionId': obj['VersionId']} for obj in page['DeleteMarkers']]) response = s3.delete_objects( Bucket=bucket_name, Delete={'Objects': objects_to_delete} ) # 可选:打印删除结果用于排查 if 'Deleted' in response: print(f"Deleted {len(response['Deleted'])} objects") if 'Errors' in response: print(f"Errors deleting objects: {response['Errors']}") # 调用示例:精准删除目标路径下的所有内容 delete_s3_prefix_recursively('my-bucket', 'do-not-delete-folder/delete-this-folder-recursively')
关键注意事项
- 前缀结尾的斜杠:一定要确保前缀以
/结尾,比如do-not-delete-folder/delete-this-folder-recursively/,这样S3只会匹配以这个完整前缀开头的对象,不会误匹配类似do-not-delete-folder/delete-this-folder-recursively-extra的路径。 - 权限策略配置:给Lambda的执行角色添加以下权限,确保只允许访问目标路径:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "s3:ListBucket", "Resource": "arn:aws:s3:::my-bucket", "Condition": { "StringLike": { "s3:prefix": "do-not-delete-folder/delete-this-folder-recursively/*" } } }, { "Effect": "Allow", "Action": [ "s3:DeleteObject", "s3:DeleteObjectVersion" // 存储桶开启版本控制时需要此权限 ], "Resource": "arn:aws:s3:::my-bucket/do-not-delete-folder/delete-this-folder-recursively/*" } ] } - 版本控制处理:如果你的存储桶开启了版本控制,记得取消代码中对应部分的注释,否则即使删除了当前版本,旧版本依然会留在存储桶里。
验证方法
在正式运行前,可以先把delete_objects换成print,打印所有要删除的对象Key,确认没有包含do-not-delete-folder/下的其他内容,再执行删除操作。
内容的提问来源于stack exchange,提问作者bhuvanrk

