【業務向け】シェルスクリプトでファイルを削除する

業務上データやログをファイルに出力することって日常茶飯事だと思います。
サーバの容量を確保するためにも、作成から一定期間以上経過して不要になったファイルは定期的に削除していきたいですよね。
今回はシェルスクリプトを使って、業務レベルで使えるファイル削除プログラムを考えてみました。

## 環境
* Amazon Linux2

## 前提条件
各ファイルは、それぞれ以下のディレクトリに格納されてるものとします。

* 削除対象ファイル：/home/test配下のファイル（サブディレクトリ含む）
* プログラムファイル：/home/ec2-user/delete_files.sh
* ログファイル：/home/ec2-user/log/delete_files_[年月日].log

## 手順

### 1. mailxのインストール
処理が失敗した場合にエラーメールを送信するため、mailxをインストールしておきましょう。

```
> yum install -y mailx
```

今回、Gmailを使いましたが、別途ライブラリのインストールや設定が必要でした。
[CentOS7でmailコマンドでgmailにメール送信できるようにする | Qiita](https://qiita.com/Esfahan/items/6fa613069b13eb7daa17)
に詳しい手順が載っていますので、そちらをご参考に。
セキュリティ的な観点から、普段使いのメールアドレスではなく、専用のメールアドレスを作成したほうがよいでしょう。

他のメールサービスを使用する場合は、必要な設定等ないか調べてみてください。

### 2. シェルスクリプトの作成
#### (1) プログラムファイルの作成
作成したシェルスクリプトは以下の通り。
今回は、bashを使用しました。
ざっくり説明すると、最終更新から、100日を超過したファイルを削除し、ファイル名と件数をログファイルに出力、失敗した場合は、エラーメールを送信する、という内容になっています。

詳細は以下プログラムのコメントをご覧ください。
（パイプ「|」で区切りの箇所は、①, ②, ③・・と記述を分けてます。）

```bash:
#!/bin/bash

function delete(){
		
		# 削除対象ディレクトリ（今回は/home/test）
        DIR="/home/test";
        # 対象ディレクトリに移動
        cd $DIR;
        # メッセージ出力
        echo "最終更新日から100日を超過したファイルを削除します"
		
		# ① 最終更新から100日を超過したファイルを再帰的に検索
		# ② ①の検索結果を強制的に削除し、詳細を出力
		# ③ 結果を/home/ec2-user/log配下のdelete_files_[年月日].logファイルに出力（ファイルが存在しない場合は新規作成される）
		# ④ 削除件数を取得
        COUNT=$(find . -type f -mtime +100 | xargs rm -frv | tee -a /home/ec2-user/log/delete_files_`date +%Y%m%d`.log | wc -l)
        # 削除件数を出力
        echo "${COUNT} ファイル削除しました"

}

function fail(){
		# 処理を実行したサーバのホスト名を取得
        HOSTNAME=$(hostname)
        # ① メール本文を作成
        # ② 件名「削除処理失敗@[処理を実行したサーバのホスト名]」、宛先（To）hoge@gmail.comにメール送信
        echo "処理に失敗しました" | mail -s "削除処理失敗@${HOSTNAME}" hoge@gmail.com
}

# deleteに失敗した場合は、failを実行
echo "======= 処理開始=======" && (delete || fail) && echo "======= 処理終了======="
```

※厳密には、101日以前になりますが、単に不要なファイルを削除するだけなので、細かいところは気にしないこととします。

#### (2) 実行権限の付与

後述のcron登録で、うまく動作させられるようプログラムファイルに実行権限を付与しておきましょう。

```
> chmod +x /home/ec2-user/delete_files.sh
```

### 3. cron登録
cron登録して、一定期間毎に処理を実行できるようにしましょう。
今回は、毎月15日の15:30に処理を実行することとします。

#### (1) bashコマンドの検索
まず、whichコマンドを使って、bashコマンドのありかを探しましょう。

```
> which bash
/usr/bin/bash
```

自分の場合は上述の通り、/usr/bin/bashでした。

#### (2) クーロン登録
crontabに以下の記述を追加しましょう。

```
# 最終更新から100日を超過したファイルの削除
30 15 15 * * /usr/bin/bash /home/ec2-user/delete_files.sh >> /home/ec2-user/log/delete_files_`date +\%Y\%m\%d`.log 2>&1
```

「/usr/bin/bash」の部分は、(1)のwhichコマンドでヒットした内容に変更してください。

ポイントは、ログ出力ファイルを
/home/ec2-user/log/delete_files_`date +\%Y\%m\%d`.log
と、シェルスクリプトで削除ファイルを出力するファイルと同じにしていること。
こうすることで、echoで標準出力する内容も同じファイルにまとめて出力することができます。

※crontabで % 使う場合は必ず \% とエスケープが必要になりますので、その点はご注意を！

crontab -eで直接書き込んでもよいのですが、既存の記述を変更してしまう可能性もあって危険なので、個人的には、一度ファイルに記述してから登録するようにしています。
[crontabを安全に編集する手順 | Qiita](https://qiita.com/nishina555/items/10fd1f1188b43ed8d972)
が、とても参考になってよいです。

#### (3) 動作確認
cron設定日時に、以下のコマンドを実行し、設定した処理が実行されることを確認します。

```
> tail -f -20 /var/log/cron
```

※手動実行だとうまくいくのに、クーロンだとうまくいかない場合もあると思います。

そんな時は、
[cronがどうやっても動かない時に考えられる原因とその対処法 | Qiita](https://qiita.com/nagimaruxxx/items/b7308644b5d2c9efbeea)
辺りをご参考に！

### 4. 実行結果
成功/失敗の場合に、それぞれどのような結果になったか見ておきましょう。

#### (1) 正常終了の場合

今回は、以下のコマンドでタイムスタンプが101日以前のファイルを作成しました。

```
> touch -t yyyyMMddHHmm /home/ec2-user/test/test_{1..125}.log
> touch -t yyyyMMddHHmm /home/ec2-user/test/test2/test_{126..205}.log
```
「yyyyMMddHHmm」の部分は適宜変更してください。

① ログファイル
/home/ec2-user/log/delete_files_[年月日].log
の中身は以下の通り。

```
======= 処理開始=======
最終更新日から100日を超過したファイルを削除します
removed ‘./test_117.log’
removed ‘./test_119.log’
(省略）
removed ‘./test_125.log’
removed ‘./test2/test_126.log’
(省略）
removed ‘./test2/test_204.log’
removed ‘./test2/test_205.log’
205 ファイル削除しました
======= 処理終了=======
```

シェルスクリプトでechoしている（標準出力している）内容と、rm -frvしている内容が同じファイルに書き込めていますね。

② メール
送信されてきませんでした。
正常終了の場合はメールはとばさない仕様なので、OKです。
余談ですが、自分のプロジェクトでは、致命的な内容のメールを見逃さないようにするため、正常終了の場合は基本メール送信しないようにしています。

#### (2) 異常終了の場合

今回は、プログラムファイルのdeleteファンクション内の一番上に、

```
echo $1 1>&2
return 1
```
の記述を入れることで意図的に発生させてみました。

① ログファイル
/home/ec2-user/log/delete_files_[年月日].log
の中身は以下の通り。

```
======= 処理開始=======
更新日が100日より前のファイルを削除します

処理に失敗しました
======= 処理終了=======
```
failファンクションの内容「処理に失敗しました」が表示されています。

② メール
以下のような内容のメールが来ました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356958/17992132-f6f4-b3a0-674c-afeca6bfaf45.png)
ぼかしを入れていますが、件名の@のうしろには、実行サーバのホスト名が入っています。
件名だけで一目瞭然なので、実行環境によってメールの送信元を変えるよりも楽でよいです。

## 終わりに

シェルスクリプトはあまり使ったことがなかったのですが、Linuxに標準装備されているので、事前準備が少なくてすむのが大きなメリットですね。
複雑な処理には不向きそうですが、シンプルな処理の実装にガンガン使っていきたいと思います。

## 参考
### 1. mailxのインストール
* [[mailコマンド]Linuxからメールを送る | Qiita](https://qiita.com/shuntaro_tamura/items/40a7d9b4400f31ec0923)
* [postfixでGmailに送信できるようにする | パパパイソニスタ](http://wpress.biz/blog/2016/09/18/postfix%E3%81%A7gmail%E3%81%AB%E9%80%81%E4%BF%A1%E3%81%A7%E3%81%8D%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%99%E3%82%8B/)

### 2. シェルスクリプトの作成
* [シェルスクリプトBash入門 | Qiita](https://qiita.com/ebisennet/items/573618ab827ce1660b0e)
* [Linuxコマンドを連続して使うには | Qiita](https://qiita.com/egawa_kun/items/714394609eef6be8e0bf)
* [Bashの便利な構文だがよく忘れてしまうものの備忘録 | Qiita](https://qiita.com/Ping/items/57fd75465dfada76e633)
* [findコマンドのmtimeオプションまとめ | Qiita](https://qiita.com/narumi_/items/9ea27362a1eb502e2dbc)
* [シェルスクリプトで関数を利用する | Qiita](https://qiita.com/kaw/items/034bc4221c4526fe8866)
* [検索してファイルを消す時に高速に行う方法 | Qiita](https://qiita.com/iron-breaker/items/74a2ed1753587476157f)
* [teeコマンドの使い方 | Qiita](https://qiita.com/wnoguchi/items/2fc3ec11043d139dc6bb)
* [日時をファイル名に付けるには | atmarkIT](https://www.atmarkit.co.jp/flinux/rensai/linuxtips/240datefilename.html)
* [【linux】ディレクトリの中のファイル数を数える（ls と wc） | SOFTELメモ](https://www.softel.co.jp/blogs/tech/archives/804)
* [シェルスクリプトで変数と文字列を結合する | beyondDays](http://shimadays.com/2018/05/15/connectstring/)

### 3. cron登録
* [コマンドの場所を調べるには | atmarkIT](https://www.atmarkit.co.jp/flinux/rensai/linuxtips/163wherecomd.html)
* [crontabの書き方 | server-memo.net](https://www.server-memo.net/tips/crontab.html)
* [crontabのガイドライン | Qiita](https://qiita.com/onomame/items/71646c5517a39bcd01cc)
* [Bashの入出力リダイレクトまとめ | Qiita](https://qiita.com/progrhyme/items/e99be732c2e62d4a7641)

### 4. 実行結果
* [bashスクリプトのエラー処理のベストプラクティス | marketechlabo](https://www.marketechlabo.com/bash-batch-best-practice/)
* [ファイルやディレクトリのタイムスタンプの変更 | Linux入門](https://linux.keicode.com/commands/touch.php)
* [連番ファイルをコマンド一発で作成する | kazmax Linuxで自宅サーバー](https://kazmax.zpp.jp/linux/bash_tips.html#ah_2)