如何用Elixir脚本将S3桶子文件夹文件移至主目录并忽略已存在文件
解决S3三级文件夹文件移动至主目录并忽略已存在文件的问题
针对你需要将my_bucket/files/id/file.txt这类三级路径文件移动到my_bucket/files/file.txt、且跳过目标已存在文件的需求,下面是修改后的完整实现:
核心实现思路
- 过滤仅处理三级目录下的文件,排除文件夹对象
- 先检查目标路径是否已有文件,避免重复操作
- S3无原生"移动"操作,采用复制+删除源文件的组合逻辑
- 处理分页场景,支持桶内超过1000个对象的情况
修改后的完整代码
defmodule Script.Elixir do alias ExAws.S3 def move_third_level_files do "my_bucket" |> list_all_objects() |> Enum.filter(&third_level_file?/1) |> Enum.each(&process_move/1) end # 递归列出桶内所有对象(处理分页) defp list_all_objects(bucket, next_marker \\ nil, acc \\ []) do opts = if next_marker, do: [max_keys: 1000, marker: next_marker], else: [max_keys: 1000] case S3.list_objects(bucket, opts) |> ExAws.request() do {:ok, %{body: %{contents: contents, is_truncated: true, next_marker: marker}}} -> list_all_objects(bucket, marker, acc ++ contents) {:ok, %{body: %{contents: contents}}} -> acc ++ contents {:error, reason} -> IO.puts("列出对象失败: #{inspect(reason)}") acc end end # 判断是否为三级目录的文件(匹配 files/id/file.txt 格式) defp third_level_file?(%{key: key}) do parts = String.split(key, "/") length(parts) == 3 && not String.ends_with?(key, "/") end # 单个文件的移动流程 defp process_move(%{key: source_key}) do # 从源路径提取目标键:files/id/file.txt → files/file.txt [top_dir, _id, filename] = String.split(source_key, "/") target_key = "#{top_dir}/#{filename}" bucket = "my_bucket" case check_object_exists(bucket, target_key) do true -> IO.puts("目标文件 #{target_key} 已存在,跳过") false -> case copy_object(bucket, source_key, target_key) do :ok -> case delete_object(bucket, source_key) do :ok -> IO.puts("成功移动 #{source_key} 到 #{target_key}") {:error, reason} -> IO.puts("复制成功但删除源文件失败 #{source_key}: #{inspect(reason)}") end {:error, reason} -> IO.puts("复制失败 #{source_key}: #{inspect(reason)}") end end end # 复制源对象到目标路径 defp copy_object(bucket, source_key, target_key) do case S3.put_object_copy(bucket, target_key, bucket, source_key) |> ExAws.request() do {:ok, _} -> :ok {:error, reason} -> {:error, reason} end end # 删除源对象 defp delete_object(bucket, key) do case S3.delete_object(bucket, key) |> ExAws.request() do {:ok, _} -> :ok {:error, reason} -> {:error, reason} end end # 检查目标对象是否存在(404代表不存在) defp check_object_exists(bucket, key) do case S3.head_object(bucket, key) |> ExAws.request() do {:ok, _} -> true {:error, {:http_error, 404, _}} -> false {:error, reason} -> IO.puts("检查文件存在性失败 #{key}: #{inspect(reason)}") false end end end
关键修改说明
- 修复原复制函数的参数错误:原
put_object_copy的参数顺序和格式有误,正确参数为「目标桶、目标键、源桶、源键」 - 路径过滤逻辑:通过
third_level_file?确保只处理三级目录的文件,排除以/结尾的文件夹对象 - 存在性校验:用
head_object替代重复复制,避免覆盖已存在文件 - 分页兼容:递归遍历所有对象,解决原代码仅处理前1000个对象的限制
- 错误处理:每个步骤增加错误提示,方便排查问题
使用方式
直接调用入口函数即可执行移动操作:
Script.Elixir.move_third_level_files()
内容的提问来源于stack exchange,提问作者Ana
相关产品推荐
相关产品推荐

