祝日データの自動ダウンロードと更新をシェルスクリプトで実現する方法

## はじめに
シェルスクリプトは、UnixやLinux環境でよく利用されるスクリプト言語で、コマンドの自動化やシステムの管理に役立ちます。

このスクリプトでは、祝日データの取得から更新までを自動化し、定期的なメンテナンスを効率化します。

:::note
具体的には、データのバックアップ、ダウンロード、変換、検証、更新の一連の処理を行います。
:::

ログを用いて各処理の結果を記録することで、トラブルシューティングも容易にしています。

## シェルスクリプトのフロー処理

ここでは、祝日データの自動化スクリプトの全体の流れをフローチャート形式で説明します。

```bash
1. 開始
   |
   V
2. 環境変数をセット
   - SYUKUJITSU_FILE
   - BACKUP_FILE
   - NEW_FILE
   - LOG_FILE
   - DOWNLOAD_URL
   |
   V
3. ログファイルをクリア
   |
   V
4. バックアップファイルを作成
   |-- 成功: "SUCCESS: バックアップを作成しました。" をログに書き込む
   |-- 失敗: "ERROR: バックアップの作成に失敗しました。" をログに書き込み、exit 1
   |
   V
5. 新しい祝日CSVファイルを取得
   |-- 成功: "SUCCESS: 新しい祝日CSVファイルを取得しました。" をログに書き込む
   |-- 失敗: "ERROR: 祝日CSVファイルの取得に失敗しました。" をログに書き込み、exit 1
   |
   V
6. 文字コードをSJISからUTF-8に変換
   |-- 成功: "SUCCESS: 文字コードの変換に成功しました。" をログに書き込む
   |-- 失敗: "ERROR: 文字コードの変換に失敗しました。" をログに書き込み、exit 1
   |
   V
7. ファイルサイズの検証
   |-- 成功: "SUCCESS: ファイルサイズの検証に成功しました。" をログに書き込む
   |-- 失敗: "ERROR: ファイルサイズが小さくなっています。" をログに書き込み、exit 1
   |
   V
8. バックアップファイルを削除
   |-- 成功: "SUCCESS: バックアップファイルを削除しました。" をログに書き込む
   |-- 失敗: "ERROR: バックアップファイルの削除に失敗しました。" をログに書き込み、exit 1
   |
   V
9. 新しいファイルをsyukujitsu.csvにリネーム
   |-- 成功: "SUCCESS: ファイルのリネームに成功しました。" をログに書き込む
   |-- 失敗: "ERROR: ファイルのリネームに失敗しました。" をログに書き込み、exit 1
   |
   V
10. "SUCCESS: 処理が完了しました。" をログに書き込み、終了
```

## 完成した全体コード

以下は、シェルスクリプト全体のコードです。祝日データの取得から更新までを自動化するための一連の処理を含んでいます。

コードの各部分については後述する解説で詳しく説明します。

```syukujitsu.sh
#!/bin/bash

# 環境変数をセット
SYUKUJITSU_FILE="/home/honda/syukujitsu/syukujitsu.csv"
BACKUP_FILE="/home/honda/syukujitsu/backup_syukujitsu.csv"
NEW_FILE="/home/honda/syukujitsu/new_syukujitsu.csv"
LOG_FILE="/home/honda/syukujitsu/syukujitsu.log"
DOWNLOAD_URL="https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv"

# ログファイルをクリア
> "${LOG_FILE}"

# 今ある祝日のCSVファイルをコピーする（バックアップファイルを作成する）
if cp "${SYUKUJITSU_FILE}" "${BACKUP_FILE}"; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') - SUCCESS: バックアップを作成しました。" >> "${LOG_FILE}"
else
    echo "$(date '+%Y-%m-%d %H:%M:%S') - ERROR: バックアップの作成に失敗しました。" >> "${LOG_FILE}"
    exit 1
fi

# CSVファイルを取得する
if curl -o "${NEW_FILE}" "${DOWNLOAD_URL}"; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') - SUCCESS: 新しい祝日CSVファイルを取得しました。" >> "${LOG_FILE}"
else
    echo "$(date '+%Y-%m-%d %H:%M:%S') - ERROR: 祝日CSVファイルの取得に失敗しました。" >> "${LOG_FILE}"
    exit 1
fi

# 文字コードをSJISからUTF-8に変換する
if iconv -f SHIFT_JIS -t UTF-8 "${NEW_FILE}" > "${NEW_FILE}.utf8"; then
    mv "${NEW_FILE}.utf8" "${NEW_FILE}"
    echo "$(date '+%Y-%m-%d %H:%M:%S') - SUCCESS: 文字コードの変換に成功しました。" >> "${LOG_FILE}"
else
    echo "$(date '+%Y-%m-%d %H:%M:%S') - ERROR: 文字コードの変換に失敗しました。" >> "${LOG_FILE}"
    exit 1
fi

# 新しいCSVファイルとバックアップファイルのサイズを比較
if [ "$(stat -c%s "${NEW_FILE}")" -ge "$(stat -c%s "${BACKUP_FILE}")" ]; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') - SUCCESS: ファイルサイズの検証に成功しました。" >> "${LOG_FILE}"
else
    echo "$(date '+%Y-%m-%d %H:%M:%S') - ERROR: ファイルサイズが小さくなっています。" >> "${LOG_FILE}"
    exit 1
fi

# バックアップファイルを削除
if rm "${BACKUP_FILE}"; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') - SUCCESS: バックアップファイルを削除しました。" >> "${LOG_FILE}"
else
    echo "$(date '+%Y-%m-%d %H:%M:%S') - ERROR: バックアップファイルの削除に失敗しました。" >> "${LOG_FILE}"
    exit 1
fi

# 新しいファイルをsyukujitsu.csvにリネーム
if mv "${NEW_FILE}" "${SYUKUJITSU_FILE}"; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') - SUCCESS: ファイルのリネームに成功しました。" >> "${LOG_FILE}"
else
    echo "$(date '+%Y-%m-%d %H:%M:%S') - ERROR: ファイルのリネームに失敗しました。" >> "${LOG_FILE}"
    exit 1
fi

echo "$(date '+%Y-%m-%d %H:%M:%S') - SUCCESS: 処理が完了しました。" >> "${LOG_FILE}"
exit 0
```

:::note alert
事前に文字コードを変換した(UTF-8版)国民の祝日ファイルを同じディレクトリに配置しておく必要があります。

https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv


以下のようなディレクトリ構成になっています。

```bash
[honda@HONDA-TEST ~]$ tree syukujitsu
syukujitsu
├── syukujitsu.csv
└── syukujitsu.sh
```

:::


### ログファイルの出力結果

```bash
[honda@HONDA-TEST syukujitsu]$ ./syukujitsu.sh 
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 20831  100 20831    0     0   5849      0  0:00:03  0:00:03 --:--:--  5871
[honda@HONDA-TEST syukujitsu]$ cat syukujitsu.log 
2024-10-14 21:39:53 - SUCCESS: バックアップを作成しました。
2024-10-14 21:39:57 - SUCCESS: 新しい祝日CSVファイルを取得しました。
2024-10-14 21:39:57 - SUCCESS: 文字コードの変換に成功しました。
2024-10-14 21:39:57 - SUCCESS: ファイルサイズの検証に成功しました。
2024-10-14 21:39:57 - SUCCESS: バックアップファイルを削除しました。
2024-10-14 21:39:57 - SUCCESS: ファイルのリネームに成功しました。
2024-10-14 21:39:57 - SUCCESS: 処理が完了しました。
[honda@HONDA-TEST syukujitsu]$ 
```

想定通りの出力結果になっていることが確認できました！


## コードの部品解説

### 1. 環境変数をセット
```bash
SYUKUJITSU_FILE="/home/honda/syukujitsu/syukujitsu.csv"
BACKUP_FILE="/home/honda/syukujitsu/backup_syukujitsu.csv"
NEW_FILE="/home/honda/syukujitsu/new_syukujitsu.csv"
LOG_FILE="/home/honda/syukujitsu/syukujitsu.log"
DOWNLOAD_URL="https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv"
```

各種ファイルやURLのパスを環境変数としてセットします。

:::note
SYUKUJITSU_FILE: 現在使用中の祝日CSVファイルのパス。
BACKUP_FILE: バックアップ用のファイルパス。
NEW_FILE: ダウンロードした新しいCSVファイルのパス。
LOG_FILE: ログを記録するファイルのパス。
DOWNLOAD_URL: 祝日データのダウンロード先URL。
:::

### 2. ログファイルをクリア
```bash
> "${LOG_FILE}"
```

LOG_FILEの内容をクリアし、新たにログを記録できるようにします。

### 3. バックアップファイルを作成
```bash
if cp "${SYUKUJITSU_FILE}" "${BACKUP_FILE}"; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') - SUCCESS: バックアップを作成しました。" >> "${LOG_FILE}"
else
    echo "$(date '+%Y-%m-%d %H:%M:%S') - ERROR: バックアップの作成に失敗しました。" >> "${LOG_FILE}"
    exit 1
fi
```

現在の祝日CSVファイルをバックアップファイルとしてコピーします。
成功時にはログにSUCCESSとして記録し、失敗時にはERRORを記録してスクリプトを終了します。

### 4. CSVファイルを取得
```bash
if curl -o "${NEW_FILE}" "${DOWNLOAD_URL}"; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') - SUCCESS: 新しい祝日CSVファイルを取得しました。" >> "${LOG_FILE}"
else
    echo "$(date '+%Y-%m-%d %H:%M:%S') - ERROR: 祝日CSVファイルの取得に失敗しました。" >> "${LOG_FILE}"
    exit 1
fi
```

curlコマンドを使って、新しい祝日データをNEW_FILEとしてダウンロードします。

成功または失敗に応じてログに記録し、失敗時はスクリプトを終了します。

### 5. 文字コード変換
```bash
if iconv -f SHIFT_JIS -t UTF-8 "${NEW_FILE}" > "${NEW_FILE}.utf8"; then
    mv "${NEW_FILE}.utf8" "${NEW_FILE}"
    echo "$(date '+%Y-%m-%d %H:%M:%S') - SUCCESS: 文字コードの変換に成功しました。" >> "${LOG_FILE}"
else
    echo "$(date '+%Y-%m-%d %H:%M:%S') - ERROR: 文字コードの変換に失敗しました。" >> "${LOG_FILE}"
    exit 1
fi
```

iconvコマンドを使って、ダウンロードしたCSVファイルの文字コードをSHIFT_JISからUTF-8に変換します。

変換後、一時ファイルを元のNEW_FILEにリネームして上書きします。

成功または失敗に応じてログに記録し、失敗時はスクリプトを終了します。

### 6. ファイルサイズの検証
```bash
if [ "$(stat -c%s "${NEW_FILE}")" -ge "$(stat -c%s "${BACKUP_FILE}")" ]; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') - SUCCESS: ファイルサイズの検証に成功しました。" >> "${LOG_FILE}"
else
    echo "$(date '+%Y-%m-%d %H:%M:%S') - ERROR: ファイルサイズが小さくなっています。" >> "${LOG_FILE}"
    exit 1
fi
```

新しいCSVファイルのサイズが、バックアップファイルのサイズ以上であることを確認します。

サイズが小さい場合はエラーとしてログに記録し、スクリプトを終了します。

### 7. バックアップファイルの削除
```bash
if rm "${BACKUP_FILE}"; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') - SUCCESS: バックアップファイルを削除しました。" >> "${LOG_FILE}"
else
    echo "$(date '+%Y-%m-%d %H:%M:%S') - ERROR: バックアップファイルの削除に失敗しました。" >> "${LOG_FILE}"
    exit 1
fi
```

バックアップファイルを削除します。

成功または失敗に応じてログに記録し、失敗時はスクリプトを終了します。

### 8. 新しいファイルのリネーム
```bash
if mv "${NEW_FILE}" "${SYUKUJITSU_FILE}"; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') - SUCCESS: ファイルのリネームに成功しました。" >> "${LOG_FILE}"
else
    echo "$(date '+%Y-%m-%d %H:%M:%S') - ERROR: ファイルのリネームに失敗しました。" >> "${LOG_FILE}"
    exit 1
fi
```

新しいCSVファイルを、現在のSYUKUJITSU_FILEにリネームします。

成功または失敗に応じてログに記録し、失敗時はスクリプトを終了します。

### 9. 処理完了メッセージ
```bash
echo "$(date '+%Y-%m-%d %H:%M:%S') - SUCCESS: 処理が完了しました。" >> "${LOG_FILE}"
exit 0
```

最後に、処理が成功したことをログに記録し、スクリプトを正常終了します。

## まとめ
このシェルスクリプトは、祝日データの取得、バックアップ、変換、更新を自動で行います。

古いデータのバックアップ、新しいデータのダウンロード、文字コード変換、データの検証、更新の順に処理が進行します。

各ステップで成功・失敗をログに記録し、エラーが発生すると即座に終了します。

これにより、問題発生時に迅速に原因を把握でき、安全な自動更新を実現しています。

## 関連記事

https://qiita.com/free-honda/items/efaa963bcd7a79e09c8b

https://qiita.com/free-honda/items/d87cb5fedce714a87e34

https://qiita.com/free-honda/items/af6e0a055a7950739ce3

https://qiita.com/free-honda/items/2f07cda9fa77e63db248