個人的な備忘録まとめ：シェルスクリプトによる祝日CSVファイルの管理

## はじめに
このシェルスクリプトは、祝日のCSVファイルを管理するための一連の処理を自動化することを目的としています。

具体的には、既存のCSVファイルのバックアップを作成し、新しい祝日データをダウンロード、文字コードを変換し、ファイルサイズを検証した後、古いファイルを削除して新しいファイルにリネームする流れを構築しています。

このスクリプトを使用することで、手動での作業を減らし、効率的かつエラーの少ないデータ管理が実現できます。

## フロー処理の流れについて

シェルスクリプトの処理フローを詳細に表現したテキストベースのフローチャートです。図形を用いて視覚的に整理しています。

```txt
【開始】
     ↓
【環境変数をセット】
     ↓
【祝日のCSVファイルをバックアップする】
     ↓
  ┌─────────────────────┐
  │  バックアップ成功   │
  └─────────────────────┘
     ↓                       ↓
「バックアップを作成しました。」   ────>【バックアップ失敗】
                                     ↓
                             「バックアップの作成に失敗しました。」
                                     ↓
                             ログファイルにエラーを書き込み
                                     ↓
                                   【終了】

     ↓
【CSVファイルを取得する】
     ↓
  ┌─────────────────────┐
  │   取得成功         │
  └─────────────────────┘
     ↓                       ↓
「新しい祝日CSVファイルを取得しました。」 ──>【取得失敗】
                                     ↓
                             「祝日CSVファイルの取得に失敗しました。」
                                     ↓
                             ログファイルにエラーを書き込み
                                     ↓
                                   【終了】

     ↓
【文字コードをSJISからUTF-8に変換する】
     ↓
  ┌─────────────────────┐
  │   変換成功         │
  └─────────────────────┘
     ↓                       ↓
「文字コードの変換に成功しました。」 ──>【変換失敗】
                                     ↓
                             「文字コードの変換に失敗しました。」
                                     ↓
                             ログファイルにエラーを書き込み
                                     ↓
                                   【終了】

     ↓
【新しいCSVファイルとバックアップファイルのサイズを比較】
     ↓
  ┌─────────────────────┐
  │  サイズ検証成功    │
  └─────────────────────┘
     ↓                       ↓
「ファイルサイズの検証に成功しました。」 ──>【サイズ検証失敗】
                                     ↓
                             「ファイルサイズが小さくなっています。」
                                     ↓
                             ログファイルにエラーを書き込み
                                     ↓
                                   【終了】

     ↓
【バックアップファイルを削除】
     ↓
  ┌─────────────────────┐
  │  削除成功         │
  └─────────────────────┘
     ↓                       ↓
「バックアップファイルを削除しました。」 ──>【削除失敗】
                                     ↓
                             「バックアップファイルの削除に失敗しました。」
                                     ↓
                             ログファイルにエラーを書き込み
                                     ↓
                                   【終了】

     ↓
【新しいファイルをsyukujitsu.csvにリネーム】
     ↓
  ┌─────────────────────┐
  │  リネーム成功      │
  └─────────────────────┘
     ↓                       ↓
「ファイルのリネームに成功しました。」 ──>【リネーム失敗】
                                     ↓
                             「ファイルのリネームに失敗しました。」
                                     ↓
                             ログファイルにエラーを書き込み
                                     ↓
                                   【終了】

     ↓
「処理が完了しました。」
     ↓
【終了】
```


## コードの部品紹介

以下はコードを1行ずつ説明したものです。

```sh
#!/bin/bash
```

シェルスクリプトのインタープリタを指定（Bashシェルを使用）。
```sh
# 環境変数をセット
SYUKUJITSU_FILE="syukujitsu.csv"
BACKUP_FILE="backup_syukujitsu.csv"
NEW_FILE="new_syukujitsu.csv"
LOG_FILE="syukujitsu.log"
DOWNLOAD_URL="https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv"
```

必要なファイル名やURLなどの変数を設定。
```sh
# 今ある祝日のCSVファイルをコピーする（バックアップファイルを作成する）
if cp "${SYUKUJITSU_FILE}" "${BACKUP_FILE}"; then
    echo "バックアップを作成しました。"
else
    echo "バックアップの作成に失敗しました。" >> "${LOG_FILE}"
    exit 1
fi
```

SYUKUJITSU_FILE を BACKUP_FILE にコピーし、成功したらメッセージを表示。失敗したらログファイルにエラーメッセージを書き込み、スクリプトを終了。

```sh
# CSVファイルを取得する
if curl -o "${NEW_FILE}" "${DOWNLOAD_URL}"; then
    echo "新しい祝日CSVファイルを取得しました。"
else
    echo "祝日CSVファイルの取得に失敗しました。" >> "${LOG_FILE}"
    exit 1
fi
```

指定されたURLから新しいCSVファイルをダウンロードし、成功したらメッセージを表示。失敗したらログファイルにエラーメッセージを書き込み、スクリプトを終了。

```sh
# 文字コードをSJISからUTF-8に変換する
if iconv -f SHIFT_JIS -t UTF-8 "${NEW_FILE}" -o "${NEW_FILE}.utf8"; then
    mv "${NEW_FILE}.utf8" "${NEW_FILE}"
    echo "文字コードの変換に成功しました。"
else
    echo "文字コードの変換に失敗しました。" >> "${LOG_FILE}"
    exit 1
fi
```

ダウンロードしたCSVファイルの文字コードを変換し、成功したらメッセージを表示。失敗したらログファイルにエラーメッセージを書き込み、スクリプトを終了。

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

新しいCSVファイルとバックアップファイルのサイズを比較し、サイズが小さくなっていなければ成功とする。サイズが小さくなっていたらログファイルにエラーメッセージを書き込み、スクリプトを終了。

```sh
# バックアップファイルを削除
if rm "${BACKUP_FILE}"; then
    echo "バックアップファイルを削除しました。"
else
    echo "バックアップファイルの削除に失敗しました。" >> "${LOG_FILE}"
    exit 1
fi
```

バックアップファイルを削除し、成功したらメッセージを表示。失敗したらログファイルにエラーメッセージを書き込み、スクリプトを終了。

```sh
# 新しいファイルをsyukujitsu.csvにリネーム
if mv "${NEW_FILE}" "${SYUKUJITSU_FILE}"; then
    echo "ファイルのリネームに成功しました。"
else
    echo "ファイルのリネームに失敗しました。" >> "${LOG_FILE}"
    exit 1
fi
```

新しいCSVファイルを最終的に SYUKUJITSU_FILE にリネームし、成功したらメッセージを表示。失敗したらログファイルにエラーメッセージを書き込み、スクリプトを終了。

```sh
echo "処理が完了しました。"
exit 0
全ての処理が正常に終了したことを表示し、スクリプトを終了。
```

## 完成したコード

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

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

# 今ある祝日のCSVファイルをコピーする（バックアップファイルを作成する）
if cp "${SYUKUJITSU_FILE}" "${BACKUP_FILE}"; then
    echo "バックアップを作成しました。"
else
    echo "バックアップの作成に失敗しました。" >> "${LOG_FILE}"
    exit 1
fi

# CSVファイルを取得する
if curl -o "${NEW_FILE}" "${DOWNLOAD_URL}"; then
    echo "新しい祝日CSVファイルを取得しました。"
else
    echo "祝日CSVファイルの取得に失敗しました。" >> "${LOG_FILE}"
    exit 1
fi

# 文字コードをSJISからUTF-8に変換する
if iconv -f SHIFT_JIS -t UTF-8 "${NEW_FILE}" -o "${NEW_FILE}.utf8"; then
    mv "${NEW_FILE}.utf8" "${NEW_FILE}"
    echo "文字コードの変換に成功しました。"
else
    echo "文字コードの変換に失敗しました。" >> "${LOG_FILE}"
    exit 1
fi

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

# バックアップファイルを削除
if rm "${BACKUP_FILE}"; then
    echo "バックアップファイルを削除しました。"
else
    echo "バックアップファイルの削除に失敗しました。" >> "${LOG_FILE}"
    exit 1
fi

# 新しいファイルをsyukujitsu.csvにリネーム
if mv "${NEW_FILE}" "${SYUKUJITSU_FILE}"; then
    echo "ファイルのリネームに成功しました。"
else
    echo "ファイルのリネームに失敗しました。" >> "${LOG_FILE}"
    exit 1
fi

echo "処理が完了しました。"
exit 0
```

## 各処理のテスト検証

各処理を個別に検証できるように、簡易的なシェルスクリプトに切り出していきます。各スクリプトを個別に実行することで、動作を検証できます。

### 1. バックアップファイルの作成

```sh
#!/bin/bash

# Backup creation
SYUKUJITSU_FILE="syukujitsu.csv"
BACKUP_FILE="backup_syukujitsu.csv"
LOG_FILE="syukujitsu.log"

if ! cp "${SYUKUJITSU_FILE}" "${BACKUP_FILE}"; then
    echo "Failed to create backup." >> "${LOG_FILE}"
    exit 1
fi

exit 0
```

### 2. CSVファイルのダウンロード
```sh
#!/bin/bash

# Download new CSV
NEW_FILE="new_syukujitsu.csv"
LOG_FILE="syukujitsu.log"
DOWNLOAD_URL="https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv"

if ! curl -o "${NEW_FILE}" "${DOWNLOAD_URL}"; then
    echo "Failed to download the new holiday CSV file." >> "${LOG_FILE}"
    exit 1
fi

exit 0
```
### 3. 文字コードの変換
```sh
#!/bin/bash

# Convert encoding from SJIS to UTF-8
NEW_FILE="new_syukujitsu.csv"
LOG_FILE="syukujitsu.log"

if ! iconv -f SHIFT_JIS -t UTF-8 "${NEW_FILE}" -o "${NEW_FILE}.utf8"; then
    echo "Failed to convert character encoding." >> "${LOG_FILE}"
    exit 1
fi

# Replace the original file with the converted one
mv "${NEW_FILE}.utf8" "${NEW_FILE}"

exit 0
```

### 4. ファイルサイズの比較
```sh
#!/bin/bash

# Compare file sizes
NEW_FILE="new_syukujitsu.csv"
BACKUP_FILE="backup_syukujitsu.csv"
LOG_FILE="syukujitsu.log"

if [ "$(stat -c%s "${NEW_FILE}")" -lt "$(stat -c%s "${BACKUP_FILE}")" ]; then
    echo "New file size is smaller than the backup." >> "${LOG_FILE}"
    exit 1
fi

exit 0
```

### 5. バックアップファイルの削除
```sh
#!/bin/bash

# Remove backup file
BACKUP_FILE="backup_syukujitsu.csv"
LOG_FILE="syukujitsu.log"

if ! rm "${BACKUP_FILE}"; then
    echo "Failed to delete the backup file." >> "${LOG_FILE}"
    exit 1
fi

exit 0
```

### 6. 新しいファイルのリネーム
```sh
#!/bin/bash

# Rename new file to syukujitsu.csv
NEW_FILE="new_syukujitsu.csv"
SYUKUJITSU_FILE="syukujitsu.csv"
LOG_FILE="syukujitsu.log"

if ! mv "${NEW_FILE}" "${SYUKUJITSU_FILE}"; then
    echo "Failed to rename the new file." >> "${LOG_FILE}"
    exit 1
fi

exit 0
```

これらのスクリプトをそれぞれ保存して実行することで、各処理を個別に検証できます。検証後、問題がないことを確認したら、元の統合スクリプトに組み込むと良いでしょう。


## おまけ

処理が成功した場合に何も出力しないようにするには、成功時のechoコマンドを削除すれば大丈夫です。失敗時にはログファイルにエラーメッセージを記録するようにしているので、必要な出力だけが記録されます。

```sh
#!/bin/bash

# Set environment variables
SYUKUJITSU_FILE="syukujitsu.csv"
BACKUP_FILE="backup_syukujitsu.csv"
NEW_FILE="new_syukujitsu.csv"
LOG_FILE="syukujitsu.log"
DOWNLOAD_URL="https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv"

# Create a backup of the current holiday CSV file
if ! cp "${SYUKUJITSU_FILE}" "${BACKUP_FILE}"; then
    echo "Failed to create backup." >> "${LOG_FILE}"
    exit 1
fi

# Download the new holiday CSV file
if ! curl -o "${NEW_FILE}" "${DOWNLOAD_URL}"; then
    echo "Failed to download the new holiday CSV file." >> "${LOG_FILE}"
    exit 1
fi

# Convert the character encoding from SJIS to UTF-8
if ! iconv -f SHIFT_JIS -t UTF-8 "${NEW_FILE}" -o "${NEW_FILE}.utf8"; then
    echo "Failed to convert character encoding." >> "${LOG_FILE}"
    exit 1
fi

# Replace the old file with the converted one
mv "${NEW_FILE}.utf8" "${NEW_FILE}"

# Compare the file sizes of the new and backup files
if [ "$(stat -c%s "${NEW_FILE}")" -lt "$(stat -c%s "${BACKUP_FILE}")" ]; then
    echo "New file size is smaller than the backup." >> "${LOG_FILE}"
    exit 1
fi

# Remove the backup file
if ! rm "${BACKUP_FILE}"; then
    echo "Failed to delete the backup file." >> "${LOG_FILE}"
    exit 1
fi

# Rename the new file to syukujitsu.csv
if ! mv "${NEW_FILE}" "${SYUKUJITSU_FILE}"; then
    echo "Failed to rename the new file." >> "${LOG_FILE}"
    exit 1
fi

# Exit with success
exit 0
```
これで、処理が成功した場合は何もechoせず、失敗した場合にのみLOG_FILEに英語でメッセージが記録されるようになります。

## 備忘録メモ

```sh
#!/bin/bash

# 環境変数をセット
SYUKUJITSU_FILE="syukujitsu.csv"
BACKUP_FILE="backup_syukujitsu.csv"
NEW_FILE="new_syukujitsu.csv"
LOG_FILE="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') - バックアップを作成しました。" >> "${LOG_FILE}"
else
    echo "$(date '+%Y-%m-%d %H:%M:%S') - バックアップの作成に失敗しました。" >> "${LOG_FILE}"
    exit 1
fi

# CSVファイルを取得する
if curl -o "${NEW_FILE}" "${DOWNLOAD_URL}"; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') - 新しい祝日CSVファイルを取得しました。" >> "${LOG_FILE}"
else
    echo "$(date '+%Y-%m-%d %H:%M:%S') - 祝日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') - 文字コードの変換に成功しました。" >> "${LOG_FILE}"
else
    echo "$(date '+%Y-%m-%d %H:%M:%S') - 文字コードの変換に失敗しました。" >> "${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') - ファイルサイズの検証に成功しました。" >> "${LOG_FILE}"
else
    echo "$(date '+%Y-%m-%d %H:%M:%S') - ファイルサイズが小さくなっています。" >> "${LOG_FILE}"
    exit 1
fi

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

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

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


## まとめ
このシェルスクリプトを用いることで、祝日のCSVファイルを効果的に管理し、データの整合性を保つことができます。

各処理の成功と失敗をログに記録することで、問題が発生した際のトラブルシューティングが容易になり、運用の信頼性を向上させます。

このような自動化により、時間を節約し、エラーを削減することが可能になります。スクリプトの各ステップを適切に理解し、必要に応じてカスタマイズすることで、より一層の効率化が図れるでしょう。