AWS CLIでAmazon S3のバケットにあるファイルから最新のファイル名を1つ取得する

便利だったのでメモ。


## やりたいこと

以下のように特定のバケットにあるファイルから最新のファイル名を1つ取得したい。

```console
> aws s3 ls s3://<バケット名>/

2019-11-26 09:01:32          0 hoge1.txt
2019-11-26 09:01:54          0 hoge2.txt
2019-11-26 09:01:25          0 hoge3.txt
2019-11-26 09:01:46          0 hoge4.txt
2019-11-26 09:01:39          0 hoge5.txt

# 「hoge2.txt」がほしい

> <いい感じのコマンド>

hoge2.txt
```

`aws s3 ls` に最終更新日時が含まれているのでできなくはなかろうと思って調べて試してみました。


## いい感じのコマンド

```console
> aws s3 ls s3://<バケット名>/ | sort -nr | head -n 1 | awk '{print $NF}'

hoge2.txt
```
いい感じです。

## ポイント

### `ls` コマンドで出力される最終更新日時はPUT開始時間

下記によると最終更新日時はPUT開始時間だそうなので、ファイル作成・更新日時でも、PUT後に参照可能になった時間でもないことに注意しましょう。

S3の最終更新日時(Last Modified)の罠 - Qiita
https://qiita.com/katsuyan/items/bf6f1a06d8148212c057


### `sort` コマンドで並び替え

[aws]s3 lsで取得したファイルを更新日時順でソートする
https://blog.hello-world.jp.net/aws/4254/

>nオプションで先頭を数値とみなしてソートすることで更新日時順にソートされます。
>また、rオプションで降順にソートしています。

`aws s3 ls` コマンドの結果を`sort` コマンドに渡してやればよいとのことでした。

```console
# 昇順
> aws s3 ls s3://<バケット名>/ | sort -n

2019-11-26 09:01:25          0 hoge3.txt
2019-11-26 09:01:32          0 hoge1.txt
2019-11-26 09:01:39          0 hoge5.txt
2019-11-26 09:01:46          0 hoge4.txt
2019-11-26 09:01:54          0 hoge2.txt

# 降順
> aws s3 ls s3://<バケット名>/ | sort -nr

2019-11-26 09:01:54          0 hoge2.txt
2019-11-26 09:01:46          0 hoge4.txt
2019-11-26 09:01:39          0 hoge5.txt
2019-11-26 09:01:32          0 hoge1.txt
2019-11-26 09:01:25          0 hoge3.txt
```


### `head` コマンドで1行目を取得する

ファイルの先頭から表示するheadコマンドの詳細まとめ【Linuxコマンド集】
https://eng-entrance.com/linux-command-head

`head` コマンドの`-n` オプションで出力する行数が指定できるので、それを利用します。

```console
> aws s3 ls s3://<バケット名>/ | sort -nr | head -n 1

2019-11-26 09:01:54          0 hoge2.txt
```


### `awk` コマンドでファイル名を取得する

最終更新日、日時、ファイルサイズ、ファイル名と並んでいる中からファイル名のみ出力させたい場合、`awk` コマンドが利用できました。
4番目にあるので`awk '{print $4}'` とも指定できますし、最後にあるので`awk '{print $NF}'` ともできます。

AWS(Amazon Web Services) - aws S3 lsコマンドでファイル名だけを表示させたい｜teratail
https://teratail.com/questions/168264

awk とシェルとの値の受け渡しについて - Qiita
https://qiita.com/shotets/items/b9ebf936b4152ea12aa1


```console
> aws s3 ls s3://<バケット名>/ | sort -nr | head -n 1 | awk '{print $NF}'

hoge2.txt
```


### 下層ディレクトリ(キー)にあるファイルもまとめてソートしたい

`aws s3` コマンドの`--recursive` オプションを利用すれば実現できました。

ls — AWS CLI 1.16.291 Command Reference
https://docs.aws.amazon.com/cli/latest/reference/s3/ls.html


下層にあるファイルの場合、ディレクトリ(キー)名が含まれるので、`basename` コマンドでファイル名のみ取得することもできます。

【 basename 】コマンド――パス名からファイル名を取得する：Linux基本コマンドTips（177） - ＠IT
https://www.atmarkit.co.jp/ait/articles/1801/25/news025.html


```console
# ファイルをfooディレクトリ(キー)下に追加
> aws s3 cp hoge6.txt s3://<バケット名>/foo/

> aws s3 ls s3://<バケット名>/ \
  | sort -nr

2019-11-26 09:01:54          0 hoge2.txt
2019-11-26 09:01:46          0 hoge4.txt
2019-11-26 09:01:39          0 hoge5.txt
2019-11-26 09:01:32          0 hoge1.txt
2019-11-26 09:01:25          0 hoge3.txt
                           PRE foo/

> aws s3 ls --recursive s3://<バケット名>/ \
  | sort -nr | head -n 1

2019-11-26 09:23:39          0 hoge/foo/hoge6.txt
2019-11-26 09:01:54          0 hoge/hoge2.txt
2019-11-26 09:01:46          0 hoge/hoge4.txt
2019-11-26 09:01:39          0 hoge/hoge5.txt
2019-11-26 09:01:32          0 hoge/hoge1.txt
2019-11-26 09:01:25          0 hoge/hoge3.txt


> aws s3 ls --recursive s3://<バケット名>/ \
  | sort -nr | head -n 1 | awk '{print $NF}'

foo/hoge6.txt


> basename "$(aws s3 ls --recursive s3://<バケット名>/ \
  | sort -nr | head -n 1 | awk '{print $NF}')"

hoge6.txt
```

fish shellな方はこちら

```console
> basename (aws s3 ls --recursive s3://<バケット名>/ \
  | sort -nr | head -n 1 | awk '{print $NF}')

hoge6.txt
```

Windowsな方は、、、ゴメンナサイ。WSL使ってください。

## 別解

本記事をまとめ終わったあとに~~orz~~、Stack Overflowでみかけた別解です。

amazon web services - get last modified object from S3 CLI - Stack Overflow
https://stackoverflow.com/questions/31062365/get-last-modified-object-from-s3-cli

`aws s3api list-objects-v2` コマンドを利用して`--query` オプションで絞り込みます。なるへそ。
こちらは指定せずとも再帰的にファイルを取得してくれました。

```console
> aws s3api list-objects-v2 \
  --bucket "<バケット名>" \
  --query 'reverse(sort_by(Contents, &LastModified))[:1].Key' \
  --output=text

hoge6.txt
```

## 参考

S3の最終更新日時(Last Modified)の罠 - Qiita
https://qiita.com/katsuyan/items/bf6f1a06d8148212c057

[aws]s3 lsで取得したファイルを更新日時順でソートする
https://blog.hello-world.jp.net/aws/4254/

ファイルの先頭から表示するheadコマンドの詳細まとめ【Linuxコマンド集】
https://eng-entrance.com/linux-command-head

AWS(Amazon Web Services) - aws S3 lsコマンドでファイル名だけを表示させたい｜teratail
https://teratail.com/questions/168264

awk とシェルとの値の受け渡しについて - Qiita
https://qiita.com/shotets/items/b9ebf936b4152ea12aa1

ls — AWS CLI 1.16.291 Command Reference
https://docs.aws.amazon.com/cli/latest/reference/s3/ls.html

【 basename 】コマンド――パス名からファイル名を取得する：Linux基本コマンドTips（177） - ＠IT
https://www.atmarkit.co.jp/ait/articles/1801/25/news025.html

amazon web services - get last modified object from S3 CLI - Stack Overflow
https://stackoverflow.com/questions/31062365/get-last-modified-object-from-s3-cli