AWS CLIでS3バケット上の指定したパスのオブジェクトを移動し、ついでにタグを付けるシェルスクリプトを作成してみた

aws s3 mvコマンドでオブジェクトを移動させた後、aws s3api put-object-taggingコマンドでタグを付けるスクリプトを作成しました。
2023.11.20

データアナリティクス事業本部 機械学習チームの鈴木です。

AWS CLIでS3バケット上の指定したパスのオブジェクトを移動し、ついでにタグを付けるという一連の操作をプログラムから実行したいことがありました。

この処理が可能なのか確認するため、自分でシェルスクリプトを作成してみたのでご紹介します。

前提

やりたいこと

以下のように複数のオブジェクトがS3バケット上にあったとします。

移動前

これらを指定した別のディレクトリに移動させます。

移動後

このとき、指定したタグを付与します。今回は1つのタグを付与することとします。

付与されたタグ

初回の移動でできた階層(この例だとdone/)については、再度スクリプトを実行した際に、この階層まで対象にならないように除外する設定もできるようにしたいです。

AWS CLIのバージョン

ローカルからbashスクリプトよりAWS CLIを使いS3オブジェクトの操作を行いました。

AWS CLIのバージョンは以下の通りでした。

aws --version
# aws-cli/2.13.28 Python/3.11.6 Darwin/22.6.0 exe/x86_64 prompt/off

作成したシェルスクリプト

以下のシェルスクリプトを作成しました。

move_put_tagging.sh

#!/bin/bash
#
# Move S3 Objects and put them tags.

err() {
  echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2
}

SOURCE=$1
DESTINATION=$2
EXCLUDE_FOLDER=$3
OBJECT_TAG_KEY=$4
OBJECT_TAG_VALUE=$5
PROFILE=$6


echo "S3 objects source: $SOURCE"
echo "S3 objects destination: $DESTINATION"
echo "S3 objects will be put tagging: key is $OBJECT_TAG_KEY, value is $OBJECT_TAG_VALUE"

# ファイル移動の情報だけ取得できるよう、--no-progressオプションをつける
# --recursiveを指定しているので、移動先が含まれている場合は除外する。
MOVED_OBJECT_URIS="$(aws s3 mv $SOURCE $DESTINATION  --exclude "${EXCLUDE_FOLDER}*" \
                               --recursive  --no-progress --output text \
                               --profile $PROFILE | cut -d ' ' -f 4)"

if [ $? -ne 0 ]; then
  err "Error: failed to move files from $SOURCE to $DESTINATION"
  exit 1
fi

echo "S3 objects have been successfully moved to: $DESTINATION"

# 改行を区切りとして配列に変える
MOVED_OBJECT_URIS_ARR=$(IFS='\n'; (echo "$MOVED_OBJECT_URIS"))

for uri in $MOVED_OBJECT_URIS_ARR; do
  bucket_name="$(echo $uri | cut -d '/' -f 3)"
  object_key="$(echo $uri | cut -d '/' -f 4-)"
  echo "Put tagging to: Bucket_name is $bucket_name, Object key is $object_key"
  aws s3api put-object-tagging --bucket $bucket_name --key $object_key --profile $PROFILE \
                               --tagging "{\"TagSet\": [{ \"Key\": \"$OBJECT_TAG_KEY\", \"Value\": \"$OBJECT_TAG_VALUE\" }]}"
  if [ $? -ne 0 ]; then
    err "Error: failed to put tag to $uri"
    exit 1
  fi
done

例えば、以下のように実行しました。

./move_put_tagging.sh s3://バケット名/sample/ s3://バケット名/sample/done/ done/ test_tag true my_profile

引数は以下の通りです。

  • 第1引数: 移動元のS3オブジェクトプレフィクス
  • 第2引数: 移動先のS3オブジェクトプレフィクス
  • 第3引数: 第1引数で指定したプレフィクスのパスから除外したい値
  • 第4引数: 付与したいタグのキー
  • 第5引数: 付与したいタグのバリュー
  • 第6引数: API実行に使用するプロファイル名 ※プロファイルで認証情報を管理している前提

今回は第3引数で--excludeオプションを使った除外を入れています。除外したいものがない場合は存在しないオブジェクトの値を入れる想定であることにご注意ください。これは単に除外が必要なケースを想定した場合に、これ以上複雑にならない良い実装が思いつきませんでした。

作成したシェルスクリプトのポイント

移動した後のオブジェクトの取得

aws s3api put-object-taggingコマンドで移動後のオブジェクトにタグを付けたいのですが、対象となる移動後のオブジェクトのオブジェクトキーを取得する必要がありました。

これはaws s3 mvコマンドで--no-progressオプションをつけて結果を出力し、cutコマンドで該当部分を取得しました。

aws s3 mvコマンドでは、例えば以下のような文字列が標準出力されました。半角スペース区切りで4列目を抽出できれば、タグ付けに必要な情報は取得できそうです。

move: s3://バケット名/sample/sample.csv to s3://バケット名/sample/done/sample.csv
move: s3://バケット名/sample/sample2.csv to s3://バケット名/sample/done/sample2.csv

コマンド単体で使っているとあまり気にしていなかったのですが、--no-progressオプションがない場合、これ以外にもアクションの実行状況に関する文字列が出力されます。

Completed 183 Bytes/366 Bytes (1.1 KiB/s) with 2 file(s) remaining
Completed 366 Bytes/366 Bytes (1.9 KiB/s) with 2 file(s) remaining
move: s3://バケット名/sample/sample.csv to s3://バケット名/sample/done/sample.csv
Completed 366 Bytes/366 Bytes (1.9 KiB/s) with 1 file(s) remaining
move: s3://バケット名/sample/sample2.csv to s3://バケット名/sample/done/sample2.csv

これだと不要な文字列を抜き出してしまうため、--no-progressオプションを使用することで解決しました。

移動とタグ付けのコマンドの使い分けについて

今回は高レベル(s3)コマンドでオブジェクトを移動し、移動した後のオブジェクトに対してAPI レベル(s3 api)コマンドでタグ付けしました。

AWS CLIではaws s3 mvコマンドでオブジェクトを移動できますが、このときに合わせてタグを付与するオプションはありませんでした。この仕様については、AWS CLIのGitHubレポジトリでも長い間議論されていることからも分かりました。

ちなみに、Python SDKではcopy_objectメソッドでオブジェクトをコピーする際にTagging引数で指定できそうでした。

そもそもAWS CLIの出力を取得して加工して……というくらい複雑なことをするのであれば、Pythonでスクリプトを実装する方が良いかもしれません。

なお、CopyObjectアクション自体にはリクエストパラメータとしてリクエストに含めたタグに置換するx-amz-taggingがあるので、単にAWS CLIとしてそういう仕様となっているようでした。

最後に

AWS CLIでS3バケット上の指定したパスのオブジェクトを移動し、ついでにタグを付けるというシェルスクリプトの実装例でした。

今回は元々aws s3 mvコマンドを使ったスクリプトを運用していてそれに機能追加したいという前提だったのですが、思ったよりもAWS CLIの仕様やコマンドの使い分けなど前提として知っておくべきことがあり苦戦しました。

ある程度複雑な処理でもあるため、これくらいの複雑さになれば、シェルスクリプトではなくPythonスクリプトに替えてしまうことも選択肢に入れても良いかもしれませんね。

ほかに参考にした資料