リストファイル記載FQDNからcurl実行、レスポンス情報を記録するbashスクリプト（ChatGPTによるリファクタリング）

さっそくChatGPTによるリファクタリングを試してみた。AIの能力は素晴らしい。プログラミング、スクリプティング分野ではもはやかなう相手はいないだろう。人間というフィルタを通して翻訳するより、機械が書いてくれたほうがそりゃ立派なものができる。
AIにソースコードを書いてもらうことは、英語ネイティブの人に英会話してもらうようなものなんだろう。餅は餅屋。

ということで、[前回の記事](https://qiita.com/If_it_bleeds-we_can_kill_it/items/5aaa98d3d75b529ddab3)をさっそくChatGPTにリファクタリングしてもらった。

# スクリプトの処理概要
- ログファイル名に日付時刻を入れるため変数宣言
- 引数を変数に代入
- 引数の個数チェック
- 引数に記載するリストファイルの存在確認
- 引数で指定するグループ名ディレクトリの存在確認
  - ない場合はディレクトリを作成する
- ログディレクトリ名、ログファイル名の確定
- ログファイルの空ファイル作成
- ログファイル見出し行追記
- リストファイル読み込み（while）
  - 1列目、2列目をそれぞれ変数に代入
  - URL作成
  - HTTPレスポンスコード取得
  - HTTPレスポンスヘッダVia取得
  - ログファイルに書き込み
- 終了（exit0）

# リファクタリング1回目（ChatGPT）
まずは単純に、[前回の記事](https://qiita.com/If_it_bleeds-we_can_kill_it/items/5aaa98d3d75b529ddab3)に書いたソースコードを載せて、リファクタリングを依頼した。

> 変数名をわかりやすくした上でリファクタリングしたスクリプトです。また、標準エラー出力を利用するように改善し、エラーの内容をより具体的に出力するようにしました。

```bash:curl-sequence.sh(UTF-8/LF)
#!/bin/bash

# 変数宣言
EXECDATE=$(date "+%Y%m%d-%H%M-%S")
HOMEDIR=/home/cloudshell-user
GRPNAME="$1"
LISTNAME="$2"
LOGFILE="${HOMEDIR}/${GRPNAME}/curl-sequence-result-${EXECDATE}.csv"

# 引数チェック
if [ $# -ne 2 ]; then
  echo "実行するには1個目の引数(任意ディレクトリ名)と、2個目の引数(リストファイル名)が必要です。" >&2
  exit 10
fi

# リストファイル存在確認
if [ ! -f "${HOMEDIR}/${LISTNAME}" ]; then
  echo "指定されたリストファイルがありません。" >&2
  exit 11
fi

# グループ名ディレクトリが存在しない場合は作成する
if [ ! -d "${HOMEDIR}/${GRPNAME}" ]; then
  mkdir -p "${HOMEDIR}/${GRPNAME}"
fi

# ログファイルに見出しを出力する
echo "statuscode,url,via" > "${LOGFILE}"

# リストファイルのURLに対して、curlコマンドを実行しレスポンスコードとViaヘッダを取得する
while read -r LINE; do
  URL="$(echo "${LINE}" | cut -d',' -f2,3)"
  RESP=$(curl -LI "${URL}" -o /dev/null -w '%{http_code}\n' -s)
  VIA=$(curl -LI "${URL}" -s | awk -F': ' '/Via/{print $2}')
  echo "${RESP},${URL},${VIA}" >> "${LOGFILE}"
done < "${HOMEDIR}/${LISTNAME}"

# スクリプト終了
exit 0
```
<br>

***いいね！***

まさかと思ったが、実行も試せるか聞いてみた。

> 以下は、リファクタリング後のスクリプトを bash -uvx オプションで実行した際に、ログファイルに出力される内容の一例です。
ログファイル（例: curl-sequence-result-20220404-131500.csv）の中身:
```csv:curl-sequence-list.csv(UTF-8/LF)
statuscode,url,via
200,https://aws.amazon.com/jp/console/,"cloudfront"
200,https://qiita.com/,"cloudfront"
```
> リストファイルの4行分のURLに対して、それぞれHTTPリクエストを送信し、レスポンスコード、URL、Viaヘッダーの内容をカンマ区切りのCSV形式でログファイルに書き込んでいます。例えば、1行目は https://aws.amazon.com/jp/console/ にHTTPリクエストを送信し、レスポンスコードが 200 であること、URLが https://aws.amazon.com/jp/console/ であること、Viaヘッダーが cloudfront であることをログファイルに書き込んでいます。

Dry-Runもできるのは驚きだった。すごい。

# リファクタリング2回目（わたし）
前回のオリジナルと1回目リファクタリングはcurlに-Lオプションをつけていたため、オリジナルのリクエストがリダイレクトされた場合にも、追跡するように指示していた。このため、リダイレクト先のレスポンスコード200が返ってきて、3xxのリダイレクトを示すレスポンスコードがログに記録されなかった。これを改善する。

```bash:curl-sequence.sh(UTF-8/LF)
#!/bin/bash

# 現在の日時を取得してログファイル名に使う
EXEC_DATE=$(date "+%Y%m%d-%H%M-%S")
HOME_DIR=/home/cloudshell-user
GROUP_NAME=$1
LIST_FILE=$2

# 引数が2個でなければエラーを出力して終了
if [ $# -ne 2 ]; then
  echo "引数が正しく指定されていません。"
  echo "第1引数にグループ名、第2引数にリストファイル名を指定してください。"
  exit 1
fi

# リストファイルが存在しなければエラーを出力して終了
if [ ! -e "${HOME_DIR}/${LIST_FILE}" ]; then
  echo "指定されたリストファイルが存在しません。"
  exit 2
fi

# グループ名のディレクトリがなければ作成する
if [ ! -d "${HOME_DIR}/${GROUP_NAME}" ]; then
  mkdir -p "${HOME_DIR}/${GROUP_NAME}"
fi

# 処理結果を出力するログファイル名を決定し、空ファイルを作成する
LOG_DIR="${HOME_DIR}/${GROUP_NAME}"
LOG_FILE="${LOG_DIR}/curl-sequence-result-${EXEC_DATE}.csv"
echo "status_code,url,via" > "${LOG_FILE}"

# リストファイルを1行ずつ読み込みながら、URLにアクセスしてHTTPレスポンスコードを取得し、ログファイルに追記していく
while read -r LINE; do
  # 1行目をカンマで分割し、それぞれ変数に代入
  COLUMN1=$(echo "${LINE}" | cut -d "," -f 1)
  COLUMN2=$(echo "${LINE}" | cut -d "," -f 2)

  # URLの構文を作成
  URL="${COLUMN1}://${COLUMN2}"

  # curlコマンドでHTTPレスポンスコードを取得
  HTTP_RESPONSE_CODE=$(curl -I "${URL}" -o /dev/null -w '%{http_code}\n' -s)

  # curlコマンドでViaヘッダーを取得
  VIA=$(curl -I "${URL}" -s | grep -i via | cut -f 2 -d ":")

  # ログファイルに結果を書き込む
  echo "${HTTP_RESPONSE_CODE},${URL},${VIA}" >> "${LOG_FILE}"
done < "${HOME_DIR}/${LIST_FILE}"

exit 0
```
<br>

念のためChatGPTにもチェックしてもらう。
> 新しいスクリプトは、サーバーからのリダイレクトを自動的に追跡しないようになっていますね。確認いただきありがとうございました。何か他に質問があれば、いつでもお聞きください。

OKっぽい。

# リファクタリング3回目（ChatGPT）
curlの-Lオプションでリダイレクトを追跡してステータスコードを返す場合と、-Lオプションをつけないで結果を返したい場合で分岐できるか相談してみた。案の定、回答が来た。
<br>

> はい、可能です。例えば以下のように、第3引数でリダイレクトを追跡するかどうかを指定するように変更することができます。

```bash:curl-sequence.sh(UTF-8/LF)
#!/bin/bash

# 現在の日時を取得してログファイル名に使う
EXEC_DATE=$(date "+%Y%m%d-%H%M-%S")
HOME_DIR=/home/cloudshell-user
GROUP_NAME=$1
LIST_FILE=$2
FOLLOW_REDIRECTS=$3

# 引数が3個でなければエラーを出力して終了
if [ $# -ne 3 ]; then
  echo "引数が正しく指定されていません。"
  echo "第1引数にグループ名、第2引数にリストファイル名、第3引数にリダイレクトを追跡するかどうかを指定してください。"
  exit 1
fi

# リストファイルが存在しなければエラーを出力して終了
if [ ! -e "${HOME_DIR}/${LIST_FILE}" ]; then
  echo "指定されたリストファイルが存在しません。"
  exit 2
fi

# グループ名のディレクトリがなければ作成する
if [ ! -d "${HOME_DIR}/${GROUP_NAME}" ]; then
  mkdir -p "${HOME_DIR}/${GROUP_NAME}"
fi

# 処理結果を出力するログファイル名を決定し、空ファイルを作成する
LOG_DIR="${HOME_DIR}/${GROUP_NAME}"
LOG_FILE="${LOG_DIR}/curl-sequence-result-${EXEC_DATE}.csv"
echo "status_code,url,via" > "${LOG_FILE}"

# リストファイルを1行ずつ読み込みながら、URLにアクセスしてHTTPレスポンスコードを取得し、ログファイルに追記していく
while read -r LINE; do
  # 1行目をカンマで分割し、それぞれ変数に代入
  COLUMN1=$(echo "${LINE}" | cut -d "," -f 1)
  COLUMN2=$(echo "${LINE}" | cut -d "," -f 2)

  # URLの構文を作成
  URL="${COLUMN1}://${COLUMN2}"

  # curlコマンドでHTTPレスポンスコードを取得
  if [ "${FOLLOW_REDIRECTS}" = "1" ]; then
    HTTP_RESPONSE_CODE=$(curl -I -L "${URL}" -o /dev/null -w '%{http_code}\n' -s)
  else
    HTTP_RESPONSE_CODE=$(curl -I "${URL}" -o /dev/null -w '%{http_code}\n' -s)
  fi

  # curlコマンドでViaヘッダーを取得
  VIA=$(curl -I "${URL}" -s | grep -i via | cut -f 2 -d ":")

  # ログファイルに結果を書き込む
  echo "${HTTP_RESPONSE_CODE},${URL},${VIA}" >> "${LOG_FILE}"
done < "${HOME_DIR}/${LIST_FILE}"

exit 0
```
<br>

実行方法も聞いてみた。
> 実行方法は以下の通りです。
> リダイレクトを追跡する場合：
> ```bash
> ./curl-sequence.sh group1 list.txt 1
> ```
> リダイレクトを追跡しない場合：
> ```bash
> ./curl-sequence.sh group1 list.txt 0
> ```
<br>

リストファイルは前回と同じものを使う。
```csv:curl-sequence-list.csv(UTF-8/LF)
https,aws.amazon.com/jp/console/
https,qiita.com/
http,www.ipa.go.jp/
https,www.ipa.go.jp/
```
<br>

これを実際のCloudShellで試してみる。
```shell-session:cloudshell
[cloudshell-user@ ~]$ pwd
/home/cloudshell-user
[cloudshell-user@ ~]$ bash curl-sequence.sh test curl-sequence-list.csv 1
[cloudshell-user@ ~]$ 
[cloudshell-user@ ~]$ bash curl-sequence.sh test curl-sequence-list.csv 0
[cloudshell-user@ ~]$ 
```
特にエラーはでないので成功した様子。
<br>

lsでログがあるか確認。
```shell-session:cloudshell
[cloudshell-user@ ~]$ ls -l test
-rw-rw-r-- 1 cloudshell-user cloudshell-user 337 Apr  4 06:41 curl-sequence-result-20230404-0641-01.csv
-rw-rw-r-- 1 cloudshell-user cloudshell-user 337 Apr  4 06:41 curl-sequence-result-20230404-0641-05.csv
[cloudshell-user@ ~]$ 
```
リダイレクトを追跡する場合、しない場合のログが出ている。
<br>

リダイレクトを追跡する場合のログを見てみる。
```shell-session:cloudshell
[cloudshell-user@ ~]$ tail test/curl-sequence-result-20230404-0641-01.csv 
status_code,url,via
200,https://aws.amazon.com/jp/console/, 1.1 4b93c15bdfb93d7749719f6a48f1f7de.cloudfront.net (CloudFront)
200,https://qiita.com/,
200,http://www.ipa.go.jp/, 1.1 de7faf172f0834adc90263d79dc3e864.cloudfront.net (CloudFront)
200,https://www.ipa.go.jp/, 1.1 9bc02c0e66fba3c7d773b9367b192e40.cloudfront.net (CloudFront)
```
この場合、リダイレクトを追跡するオプションのログであるため「`200,http://www.ipa.go.jp/`」という結果になっている。
<br>

リダイレクトを追跡しない場合のログを見てみる。
```shell-session:cloudshell
[cloudshell-user@ ~]$ tail test/curl-sequence-result-20230404-0641-05.csv 
status_code,url,via
200,https://aws.amazon.com/jp/console/, 1.1 abc87053038222ee9532eebb6497bd1c.cloudfront.net (CloudFront)
200,https://qiita.com/,
301,http://www.ipa.go.jp/, 1.1 9a57190cf6aa7b1142210e5f804431fc.cloudfront.net (CloudFront)
200,https://www.ipa.go.jp/, 1.1 9bc02c0e66fba3c7d773b9367b192e40.cloudfront.net (CloudFront)
[cloudshell-user@ ~]$ 
```
リダイレクトを**追跡しない**オプションのログであるため「`301,http://www.ipa.go.jp/`」となり、[The HyperText Transfer Protocol (HTTP) の 301 Moved Permanently](https://developer.mozilla.org/ja/docs/Web/HTTP/Status/301)が結果に表示されるようになった。

# 感想
変数名の設計やミスの修正、機能追加まで、すべてに対応してくれる素晴らしいChatGPTくん。もしかしたら、英語か日本語で仕様書だけ書けばソースは作ってくれるんじゃないか？と思った。今回の場合、前回記事のソースコードと、処理概要日本語を書いたら、良い感じにリファクタリングしてくれた。人間が書いてそれを修正させると、さらにパワフルな動きをするのかもしれない。
こんな形で過去に書いたスクリプトやプログラムのソースコードも、GPTリファクタリング日記を作ってみるかも、みないかも（暇があれば）。

AIとの対話は面白いです。好きな映画がターミネーター2なんですが、あの映画のジョンコナーになったような気持ち、ドラえもんでいうのび太の気分を味わえます。