はじめに
YAPC::Kyotoのチケット、2/3(金)までの販売に延長!1 nikkieです。
S3にオブジェクトがあるかどうかをシェルスクリプトで確認する方法を考えてみました。
現時点の暫定解としてアウトプットします。
シェルスクリプトやAWSに関しては伸びしろ豊富と思いますので、フィードバックは大歓迎です。
目次
実現したいこと
S3のオブジェクト(今回はファイルに限定します)のURIが手元にあります(s3://
で始まるURIです)。
このURIが指すオブジェクト(ファイル)があるかどうかをシェルスクリプトで知りたいという状況です。
ここでは詳しく立ち入りませんが、存在有無に応じて後く処理を分岐させることを考えていました。
素振り準備
LocalStackで環境構築
Dockerを使ってローカル環境にS3を用意します。
以下の記事のPythonスクリプトを使って、s3://awesome/foo/bar.txt
を置きました。
docker run
の出力には以下のバージョン記載がありました。
LocalStack version: 1.3.2.dev LocalStack build date: 2023-01-28 LocalStack build git hash: 63e357fb
AWS CLIの設定
コマンドラインからAWSのサービスを操作するのにAWS CLIを使います。
インストールはこちらを参考にしました。
aws configure
でLocalStack用のprofileを用意します2。
高速セットアップ - AWS Command Line Interface
$ aws configure --profile localstack AWS Access Key ID [None]: dummy AWS Secret Access Key [None]: dummy Default region name [None]: us-east-1 Default output format [None]: json
使ったCLIのバージョンは以下です。
$ aws --version aws-cli/2.1.30 Python/3.8.8 Darwin/18.7.0 exe/x86_64 prompt/off
準備できました!
$ aws --profile localstack --endpoint-url http://localhost:4566 s3 ls s3://awesome/foo/ 2023-01-31 19:53:33 11 bar.txt
参考実装とその読み解き
以下を参考にしました。
aws s3 ls --sum
の結果を取得grep
でオブジェクトの数を確認
提案実装(S3にオブジェクトがあるかどうかを確認する方法 暫定解)
exist_or_not.sh
#!/usr/bin/env bash set -euo pipefail file_uri=$1 set +e aws --profile localstack --endpoint-url http://localhost:4566 s3 ls ${file_uri} > /dev/null ls_code=$? set -e if [ ${ls_code} -eq 0 ]; then echo "${file_uri} はあります" else echo "${file_uri} はありません" fi
オブジェクトが存在するURIs3://awesome/foo/bar.txt
と、オブジェクトが存在しないURIs3://awesome/foo/baz.txt
を渡して実行してみます。
$ ./exist_or_not.sh s3://awesome/foo/bar.txt s3://awesome/foo/bar.txt はあります $ ./exist_or_not.sh s3://awesome/foo/baz.txt s3://awesome/foo/baz.txt はありません
aws s3 ls
の終了コード
ドキュメントで見つけられなかったのですが、URIで指定したオブジェクトがない場合、aws s3 ls
の終了コードは1になるようです4。
$ aws --profile localstack --endpoint-url http://localhost:4566 s3 ls s3://awesome/foo/bar.txt 2023-01-31 19:53:33 11 bar.txt $ echo $? 0 $ aws --profile localstack --endpoint-url http://localhost:4566 s3 ls s3://awesome/foo/baz.txt $ echo $? 1
これを利用して、aws s3 ls
の終了コードで処理を分岐しました。
終了コードの扱い(set +e
の理由)
単にaws s3 ls
だけを使って実装したスクリプトは、ファイルが存在するときは期待通り動きますが、ファイルが存在しないときにはecho
されません。
set -e
により、コマンドの終了コードが0以外だと、そこでスクリプトが終了する5ためです。
aws s3 ls
でスクリプトが終了しないよう、set +e
と書いて、set -e
を一時的に無効にしています。
供養したい実装
実はこのエントリを書き始めた時点でのexist_or_not.sh
は以下の実装でした。
diffのシンタックスハイライトを利用して、大きく違うところを強調しています。
#!/usr/bin/env bash set -euo pipefail file_uri=$1 set +e +ls_output=$(aws --profile localstack --endpoint-url http://localhost:4566 s3 ls ${file_uri} --sum) +echo "${ls_output}" | grep -sq 'Total Objects: 1' +grep_code=$? set -e if [ ${grep_code} -eq 0 ]; then echo "${file_uri} はあります" else echo "${file_uri} はありません" fi
エントリを書いているうちにaws s3 ls
の終了コードだけで判定できることに気付き、その方が実装が簡潔になるので差し替えました。
この実装に至るまでに調べたことを以下にアウトプットしておきます。
aws s3 ls --sum
--sum
は--summarize
オプションです。
Displays summary information (number of objects, total size). (
aws s3 ls
のドキュメントより引用)
summary information(要約情報。「Total Objects」「Total Size」のこと)が表示されるようになります。
$ aws --profile localstack --endpoint-url http://localhost:4566 s3 ls s3://awesome/foo/bar.txt --sum 2023-01-31 19:53:33 11 bar.txt Total Objects: 1 Total Size: 11
ドキュメントの「Example 5: Summarizing all prefixes and objects in a bucket」も参考になりました。
grep
aws s3 ls --sum
の結果の文字列をgrep
で検索します。
存在しないオブジェクトのURIを指定したときも--sum
オプションにより要約情報は表示されます。
$ aws --profile localstack --endpoint-url http://localhost:4566 s3 ls s3://awesome/foo/baz.txt --sum Total Objects: 0 Total Size: 0
そこで(ここではオブジェクトはファイルの想定なので)、aws s3 ls --sum
の結果に
と判定することにしました。
set +e
ここまでの要素で実装したスクリプトは、ファイルが存在するときは期待通り動きますが、ファイルが存在しないときにはecho
されませんでした。
これはaws s3 ls
とgrep
の終了コードによります。
URIに対応するオブジェクトが存在しないときaws s3 ls
は終了コード1となります。
set -e
によりエラーが送出されたらスクリプトを終了します。
また、grep
も検索語句が見つからなかったときに終了コードが1となります。
選択される行が見つかったときの終了ステータスは 0 であり、 見つからなかったときは 1 であり、エラーが起きた場合は 2 です。
grep
の終了コードは行が見つかったか否かを表すことを知り、これを判定ロジックで使いたいと考えました。
終了コードをまとめると以下のようになります6。
ファイル有無 | aws s3 ls の終了コード |
grep の終了コード |
---|---|---|
あり | 0 | 0 |
なし | 1 | 1 |
ファイルがない場合、aws s3 ls
もgrep
も終了コードは0でありません。
aws s3 ls
やgrep
でスクリプトが終了しないよう、set +e
を使っています。
終わりに
S3にオブジェクト(ファイル)が存在するか否か確認するスクリプトについてアウトプットしました。
aws s3 ls
の終了コードを使う考えです。
またこの実装に至るまでにaws s3 ls --sum
やgrep
を調べた回り道もアウトプットしました。
- リンク先にありますが、売り切れも近いそうです。お早めに!↩
- LocalStackを使ってローカル環境でS3を立ち上げ、Lambdaでファイルをアップロードしてみた - Qiita を参考にしました↩
-
『マスタリングLinuxシェルスクリプト 第2版』8.1によると
> /dev/null
と同じです↩ -
ドキュメントに記載がない情報を使うのは反対という立場も全然あると思います。私は「
ls
コマンドの終了コードと似ているし、突然実装も変わりづらいだろうから利用しちゃえ」と考えました↩ -
set -e
はエラーが送出されたらそれをハンドルしてから終了するシェルスクリプトの書き方を理解しました(set -e, trap, exit) - nikkie-ftnextの日記で完全に理解しました↩ - この表を書いたのが簡潔な実装に気付くきっかけとなりました↩