タグ: シェルスクリプト Page 1 of 5

ChatGPTによるスクリプト修正。(通貨変換)

海外通販で、ドル円計算が面倒なのでスクリプト化。

以前、ChatGPTで作成した通貨変換スクリプトを、以下のようにChatGPT自身に修正してもらいました。

  1. CSVへの記録ではなく、○ドル◆円のように表示する。
  2. 通貨ペアの変換前の数値をプロンプトで尋ねる。(空白エンターは1)
  3. その数値で計算する。(例えば3ドル○円など)

修正した結果がこちらです。

スクリプト内容

  • currency_check.sh
#!/bin/bash

#### 変数定義ここから #####
# 通貨ペアの変換前の数値を取得する。
read -p "変換したい通貨の数値を入力してください(空白エンターは1): " conversion_factor
if [ -z "$conversion_factor" ]; then
    conversion_factor=1
fi

# 通貨ペアを変数化する。
# USDJPY=ドル円
# EURUSD=ユーロドル
# など、以下のURLに沿ったものを指定
base_currency="USD"
target_currency="JPY"
#### 変数定義ここまで ####

# 1. コマンドを実行してデータを取得し、標準エラー出力を/dev/nullにリダイレクトする。
data=$(wget -O - -U "" http://www.gaitameonline.com/rateaj/getrate 2> /dev/null)

# 2. 現在時刻を取得する。
date=$(date '+%Y/%m/%d')
time=$(date '+%H:%M')

# 3. 指定された通貨ペアの情報を取得する。
currency_data=$(echo "$data" | grep -o '{"high":"[0-9]*\.[0-9]*","open":"[0-9]*\.[0-9]*","bid":"[0-9]*\.[0-9]*","currencyPairCode":"'$base_currency$target_currency'","ask":"[0-9]*\.[0-9]*","low":"[0-9]*\.[0-9]*"}')

# 4. 必要な情報を取り出し、並び替える。
currencyPairCode=$(echo "$currency_data" | grep -o 'currencyPairCode":"'$base_currency$target_currency'"' | cut -d ':' -f 2 | tr -d '"')
open=$(echo "$currency_data" | grep -o 'open":"[0-9]*\.[0-9]*"' | cut -d ':' -f 2 | tr -d '"' | awk -v cf="$conversion_factor" '{printf "%'\''0.2f", $1 * cf}')
high=$(echo "$currency_data" | grep -o 'high":"[0-9]*\.[0-9]*"' | cut -d ':' -f 2 | tr -d '"' | awk -v cf="$conversion_factor" '{printf "%'\''0.2f", $1 * cf}')
bid=$(echo "$currency_data" | grep -o 'bid":"[0-9]*\.[0-9]*"' | cut -d ':' -f 2 | tr -d '"' | awk -v cf="$conversion_factor" '{printf "%'\''0.2f", $1 * cf}')
ask=$(echo "$currency_data" | grep -o 'ask":"[0-9]*\.[0-9]*"' | cut -d ':' -f 2 | tr -d '"' | awk -v cf="$conversion_factor" '{printf "%'\''0.2f", $1 * cf}')
low=$(echo "$currency_data" | grep -o 'low":"[0-9]*\.[0-9]*"' | cut -d ':' -f 2 | tr -d '"' | awk -v cf="$conversion_factor" '{printf "%'\''0.2f", $1 * cf}')

# 5. 結果を表示する。
echo "$date $time ${conversion_factor} $base_currency は $target_currency で以下の通りです。"
echo "始値: $open $target_currency"
echo "高値: $high $target_currency"
echo "売値: $bid $target_currency"
echo "買値: $ask $target_currency"
echo "安値: $low $target_currency"
  • 実行権付与
chmod +x currency_check.sh

動き

./currency_check.sh
変換したい通貨の数値を入力してください(空白エンターは1): 74
2024/03/13 14:25 74 USD は JPY で以下の通りです。
始値: 10,926.10 JPY
高値: 10,926.10 JPY
売値: 10,924.62 JPY
買値: 10,925.36 JPY
安値: 10,895.02 JPY

と、ブラウザを開くことなく結果が出るようになりました。

xmlフィードから最新の見出しを表示するスクリプト。

スクリプト概要

  1. BBCのRSSフィードをcurlする
  2. そこから指定の見出しを持ってくる

スクリプトの内容

  • bbc_headline.sh
#!/bin/bash

# セクションを指定
section="world"  # ここでセクションを変更

# BBC NewsのRSSフィードから見出しを取得
url="https://feeds.bbci.co.uk/news/${section}/rss.xml"
headlines=$(curl -s "$url" | grep -E '<title>' | sed -n 's/.*<title><!\[CDATA\[\(.*\)\]\]><\/title>.*/\1/p' | sed -n '2,4p')

# 見出しを表示
echo "BBC News - ${section} section"
echo "$headlines"
  • 実行権付与
chmod +x bbc_headline.sh

実行例

  • コマンド実行
./bbc_headline.sh
  • 表示結果
BBC News - world section
'We know what's coming': East Ukraine braces for Russian advance
Trump challenges Biden to TV debates 'anytime'
Australian charged over high-profile disappearance

コマンドベースでブラウザを実行するなく見出しだけでも確認できます。

対話式にLinuxサーバの再起動・停止を行うシェルスクリプト。

概要

メンテナンスなどで、指定する時刻にLinuxサーバを停止・再起動したいときは割合にあります。

そこで、そんなスクリプトです。

スクリプト内容

  • server_shutdown.sh
#!/bin/bash

# メッセージを表示して日付を入力
read -p "このサーバを停止・再起動する日付をyyyymmdd形式で入力してください: " input_date

# メッセージを表示して時刻を入力
read -p "このサーバを停止・再起動する時刻をhhmm形式で入力してください: " input_time

# メッセージを表示して再起動か停止かを入力
read -p "再起動は1、停止は2を入力してください(空エンターは1): " restart_option
restart_option=${restart_option:-1}  # デフォルトは再起動(1)

# 日付と時刻を結合
scheduled_datetime="${input_date} ${input_time}"

# オプションに応じてコマンドを設定
case $restart_option in
    1)
        action="再起動"
        ;;
    2)
        action="システム停止"
        ;;
    *)
        echo "無効なオプションが選択されました。スクリプトを終了します。"
        exit 1
        ;;
esac

# 確認メッセージを表示して、実行の可否を確認
read -p "$(date -d "$scheduled_datetime" '+%Y/%m/%d %H:%M') に$action します。よろしいですか?(y/n): " confirmation

if [ "$confirmation" == "y" ]; then
    # 指定した日時まで待機し、処理をバックグラウンドで実行
    (sleep $(( $(date -d "$scheduled_datetime" '+%s') - $(date '+%s') )) && {
        echo "システムに常駐し、指定日時に$action を行います。"
        if [ $restart_option -eq 1 ]; then
            shutdown -r now
        elif [ $restart_option -eq 2 ]; then
            shutdown -h now
        fi
    }) &
    disown  # バックグラウンドプロセスをデタッチ
    echo "処理をバックグラウンドで実行しました。プロンプトに戻ります。"
else
    echo "操作をキャンセルしました。"
fi

作成後、

sudo chown root:root server_shutdown.sh

として所有者をrootに変更。その後、

sudo chmod 744 server_shutdown.sh

で、rootのみに実行権を付与します。

実行時の動き

sudo bash server_shutdown.sh
このサーバを停止・再起動する日付をyyyymmdd形式で入力してください: 20240216
このサーバを停止・再起動する時刻をhhmm形式で入力してください: 0215
再起動は1、停止は2を入力してください(空エンターは1): 1
2024/02/16 02:15 に再起動 します。よろしいですか?(y/n): y
処理をバックグラウンドで実行しました。プロンプトに戻ります。

と、プロンプトが出て指定時刻にサーバーの再起動/停止を行います。

動きを止めたいとき

バックグラウンドで処理が走っているので、プロセスを確認して止めます。

  • プロセス確認
ps aux |grep server_shutdown
root        2202  0.0  0.0  11020  1528 ?        S    16:32   0:00 bash server_shutdown.sh

2202のように、PIDを確認します。

  • プロセス停止
sudo kill 2202

PIDに停止シグナルを送り、処理を止めます。

  • プロセス確認
ps aux |grep server_shutdown

該当するバックグラウンドの処理がなければ大丈夫です。

ChatGPTによるRubyスクリプト。(対話式パスワード生成)

前回作成したパスワード生成スクリプト。変数で指定するのはCron処理などでは便利でしたが、日常には不向き。

そこで、再びChatGPTに相談し、

以下の要件でスクリプトを作成です。

要件

  • スクリプトを実行する
  • 発行強度を1~3で指定する (空エンターは中)
    • 1:弱 8桁/英数字のみ
    • 2:中 10桁/小文字・大文字・英数字と単純な記号
    • 3.強 12桁/小文字・大文字・英数字と複雑な記号
  • 表示するパスワードの数を数字で指定する (空エンターは4個)

スクリプト

できあがったのがこちら。

  • password_generate.rb
def generate_password(length, complexity, password_count)
  case complexity
  when 1
    chars = [('a'..'z'), (0..9)].map(&:to_a).flatten
  when 2
    chars = [('a'..'z'), ('A'..'Z'), (0..9), ['!', '@', '#', '$', '%', '^', '&', '*']].map(&:to_a).flatten
  when 3
    chars = [('a'..'z'), ('A'..'Z'), (0..9), ['!', '@', '#', '$', '%', '^', '&', '*'], ['-', '_', '=', '+', '<', '>', '?']].map(&:to_a).flatten
  else
    puts "無効な発行強度です。"
    return
  end

  password_count.times do |i|
    password = (0...length).map { chars[rand(chars.length)] }.join
    puts "Password #{i + 1}: #{password}"
  end
end

def get_complexity
  print "発行強度を1から3の範囲で入力してください (デフォルトは2):\n"
  complexity_input = gets.chomp

  complexity = complexity_input.empty? ? 2 : complexity_input.to_i

  until (1..3).cover?(complexity)
    print "有効な範囲で入力してください(1から3): "
    complexity = gets.chomp.to_i
  end

  complexity
end

def get_password_count
  print "表示するパスワードの数を入力してください (デフォルトは4): "
  count_input = gets.chomp

  count = count_input.empty? ? 4 : count_input.to_i

  count
end

puts "パスワードを生成します。"
complexity = get_complexity
password_count = get_password_count

case complexity
when 1
  generate_password(8, complexity, password_count)
when 2
  generate_password(10, complexity, password_count)
when 3
  generate_password(12, complexity, password_count)
end

実行例

ruby password_generate.rb
発行強度を1から3の範囲で入力してください (デフォルトは2):
2
表示するパスワードの数を入力してください (デフォルトは4): 9
Password 1: ILAXRn!O47
Password 2: P^qmiduucK
Password 3: 8ytFezU%fU
Password 4: O!6OFeHgYe
Password 5: Ua&!Ryg039
Password 6: &T@AtiNpWy
Password 7: Jz8jyPbp#3
Password 8: HOWE66ki1I
Password 9: 9WRdsHhk*H

シェルスクリプト:秘密鍵を用いたLinuxサーバへのログインスクリプト。

概要

SSH接続しているLinuxクライアントから別のLinuxサーバにSSHログインする際の効率化を図るスクリプトです。

通常のコマンドを用いた秘密鍵を用いたアクセス

ssh -i /path/to/private_key username@server_host

と、-iの後に秘密鍵を指定し、ユーザ名@サーバ名/IPアドレスを入力するのはちょっと面倒なので、コマンド一発で接続できるようにします。

前提

  • Linuxサーバの秘密鍵を有していること。
  • 秘密鍵に適切なパスワードがかけられていること。

スクリプト

  • ssh_server.sh

serverの部分を任意のホスト名にすると便利です

#!/bin/bash

# 変数定義
## 接続先のサーバ名/IPアドレス
server_host="your_server_host"
## 接続先でログインするアカウント名
username="your_username"
## 秘密鍵のファイルパスを絶対パスで表記
private_key_path="/path/to/private_key"
## 接続時間を記録するログのディレクトリパスを絶対パスで表記
log_directory="/path/to/logs"


# ログディレクトリが存在しない場合は作成する
if [ ! -d "$log_directory" ]; then
    mkdir -p "$log_directory"
fi

# 現在の日付と時刻を取得
current_datetime=$(date +"%Y%m%d%H%M")

# ログファイルのパス
log_file="$log_directory/$server_host$current_datetime.log"

# 秘密鍵の存在チェック
if [ ! -f "$private_key_path" ]; then
    echo "$(date): Error - 秘密鍵ファイルが見つかりません: $private_key_path" >> "$log_file"
    exit 1
fi

# 秘密鍵のパーミッションが600であるかのチェック
if [ $(stat -c %a "$private_key_path") -ne 600 ]; then
    echo "$(date): Error - 秘密鍵のパーミッションが正しくありません (600 である必要があります): $private_key_path" >> "$log_file"
    exit 1
fi

# SSH接続
echo "$(date): SSH接続を試行します: ssh -i $private_key_path $username@$server_host" >> "$log_file"
ssh -i "$private_key_path" "$username@$server_host"
echo "$(date): SSH接続が正常に終了しました" >> "$log_file"
  • 実行権付与
chmod +x ssh_server.sh

動き

./ssh_server.sh

を実行後、秘密鍵のパスワード入力画面が出てきます。認証後、接続先のプロンプトが返ってくれば成功です。

Ubuntu : ディレクトリのバックアップスクリプト。

概要

Linux運用時、特定のディレクトリをバックアップするスクリプトです。

  1. バックアップ元とバックアップ先の変数指定
  2. 世代管理
  3. 直接実行するかCronで設定するか

の変数指定が可能です。

スクリプト内容

  • home_backup.sh

※要管理者権限

# 変数の定義
BACKUP_SOURCE="/home"
BACKUP_DEST="/backup"
GENERATION=3
VERBOSE=false  # バックアップ成功メッセージを表示するかどうか(true/false)

# 一時ディレクトリを作成
temp_dir=$(mktemp -d)

# バックアップを作成する関数
create_backup() {
    # yyymmdd形式の日付を取得
    DATE=$(date +"%Y%m%d")

    # 圧縮ファイルの名前を指定
    ARCHIVE_NAME="backup_$DATE.tar.gz"

    # rsyncを用いてディレクトリを一時ディレクトリにコピー
    rsync -a --delete "$BACKUP_SOURCE/" "$temp_dir/"

    # ディレクトリ全体を圧縮
    tar --ignore-failed-read -czf "$BACKUP_DEST/$ARCHIVE_NAME" -C "$temp_dir" .

    # 古いバックアップを削除
    delete_old_backups

    # メッセージの表示
    if [ "$VERBOSE" = true ]; then
        echo "Backup completed successfully."
    fi
}

# 古いバックアップを削除する関数
delete_old_backups() {
    # バックアップディレクトリ内のバックアップファイルを日付順にソート
    sorted_backups=($(ls -t "$BACKUP_DEST" | grep -E "backup_[0-9]{8}\.tar\.gz"))

    # 不要なバックアップファイルを削除
    while [ ${#sorted_backups[@]} -gt $GENERATION ]; do
        file_to_delete="${sorted_backups[${#sorted_backups[@]}-1]}"
        rm "$BACKUP_DEST/$file_to_delete"
        echo "Deleted old backup: $file_to_delete"
        # 削除したファイルをリストから除外
        sorted_backups=("${sorted_backups[@]:0:${#sorted_backups[@]}-1}")
    done
}

# バックアップの作成
create_backup

# 一時ディレクトリを削除
rm -r "$temp_dir"

作成後、実行権を付与します。

sudo chmod +x home_backup.sh

注意点

  • バックアップ先に十分な空き容量があることを想定しています。
  • rsyncとtarの二重処理なので、割と時間がかかります。

この辺をもう少しうまく処理したいです。

ChatGPTによるシェルスクリプト。(Linuxユーザアカウント削除)

概要

先だってはオプションを忘れがちなものを自動化するを元にスクリプト&コマンド化。

今度は、「慎重な操作を要する作業」を一気通貫で行うようにします。

要件

こんな要件のスクリプトを出力です。

  1. 削除するユーザ名を対話式で聞く
     → ない場合はExit
  2. /etc/passwd /etc/shadow /etc/group のバックアップを、 /etc/old/ファイル名.$(date +%Y%m%d)でバックアップ
     → それぞれファイル:○○をバックアップしましたの表示を出す
  3. ユーザのディレクトリも消すかを聞く y/n
  4. 削除前に以下の確認
     ユーザ名:○○を削除
     ユーザのホームディレクトを消す:y or no
    これでよろしいですか?
    → n : Exit
  5. y: ユーザデータを削除する
    ディレクトリを消すでy → userdel -r を実行してメッセージ出力、Exit
    ディレクトリを消すでn → userdel を実行してメッセージ出力、Exit

ここから更に

  • ホームディレクトリのバックアップを取るかどうかを聞く
  • 差分を表示するようにする

などを加えてできたスクリプトがこちらです。

免責

意図通りに動くかは一応、確認しましたが、
本番運用の前に、検証環境で、テストアカウントで実施してください。

準備

以下のディレクトリを作ります。

  • 設定ファイルのバックアップ先
sudo mkdir /etc/old
  • ホームディレクトリのバックアップ先
sudo mkdir /home/deleted_users

スクリプト内容

  • account_delete.sh
#!/bin/bash

# 1. ユーザ名を対話的に聞く
read -p "削除するユーザ名を入力してください: " username

# ユーザが存在しない場合は終了
if ! id "$username" &>/dev/null; then
    echo "ユーザ '$username' は存在しません。終了します。"
    exit 1
fi

# 2. バックアップを作成
backup_dir="/etc/old"
backup_date=$(date +%Y%m%d)
backup_passwd="$backup_dir/passwd.$backup_date"
backup_shadow="$backup_dir/shadow.$backup_date"
backup_group="$backup_dir/group.$backup_date"

cp /etc/passwd "$backup_passwd"
cp /etc/shadow "$backup_shadow"
cp /etc/group "$backup_group"

echo "ファイル:$backup_passwd をバックアップしました。"
echo "ファイル:$backup_shadow をバックアップしました。"
echo "ファイル:$backup_group をバックアップしました。"

# 3. ユーザのディレクトリを消すかどうかを確認
read -p "ユーザのディレクトリも削除しますか? (y/n): " delete_home

# 4. ユーザディレクトリのバックアップを取るかどうかを確認
backup_home=""
if [ "$delete_home" == "y" ]; then
    read -p "ユーザのホームディレクトリのバックアップを取りますか? (y/n): " backup_home_choice
    if [ "$backup_home_choice" == "y" ]; then
        backup_home="/home/deleted_users/$username.$backup_date"
        cp -r /home/$username "$backup_home"
        echo "ユーザ '$username' のホームディレクトリをバックアップしました。"
    fi
fi

# 5. 削除前に確認
echo -e "\nユーザ名: $username を削除"
echo -e "ユーザのホームディレクトリを消す: $delete_home"
echo -e "ユーザのホームディレクトリのバックアップを取る: $backup_home\n"

read -p "これでよろしいですか? (y/n): " confirm

if [ "$confirm" == "n" ]; then
    echo "終了します。"
    exit 0
fi

# 6. ユーザデータを削除
if [ "$delete_home" == "y" ]; then
    userdel -r "$username"
    echo "ユーザ '$username' のデータとディレクトリを削除しました。"
else
    userdel "$username"
    echo "ユーザ '$username' のデータを削除しました。"
fi

# 7. バックアップと現在の差分を表示するかどうかを尋ねる
read -p "アカウント削除後、バックアップと現在の差分を表示しますか? (y/n): " show_diff

if [ "$show_diff" == "y" ]; then
    diff_passwd=$(diff /etc/passwd "$backup_passwd")
    diff_shadow=$(diff /etc/shadow "$backup_shadow")
    diff_group=$(diff /etc/group "$backup_group")

    echo -e "\n--- ファイル:$backup_passwd との差分 ---\n$diff_passwd"
    echo -e "\n--- ファイル:$backup_shadow との差分 ---\n$diff_shadow"
    echo -e "\n--- ファイル:$backup_group との差分 ---\n$diff_group"
fi

echo "終了します。"

実行権付与

  • 所有者変更
sudo chown root:root account_delete.sh

root権限でしか触れないコマンドなので、所有者もrootにします。

  • 権限変更
sudo chmod 700 account_delete.sh

通常アカウントは閲覧もできないようにします。

これで、操作時に確認をしつついざというときの切り戻しもできるようになります。

ChatGPTによるシェルスクリプトとコマンド化。(Zipアーカイブ)

概要

使うときはあるけど、オプションや順番を考えるのが面倒。

そんな悩みをChatGPTの力を借りて解決です。

やったこと

  1. 対話式で圧縮するファイルやディレクトリを指定
  2. 圧縮先のディレクトリを対話で指定(何も指定しない場合はカレントディレクトリ)
  3. アーカイブファイルを対話で指定(何も指定しない場合はファイル/ディレクトリ名.$(date +%Y%m%d).zip)
  4. zipコマンドで圧縮。ディレクトリかを判別し、ディレクトリなら-rオプションをつける

という処理を行います。

スクリプト

  • autozip
#!/bin/bash

# ユーザにファイル/ディレクトリの指定を求める関数
get_input() {
    read -p "$1: " input
    echo "$input"
}

# 対話式でファイル/ディレクトリの指定を取得
source_path=$(get_input "圧縮するファイルやディレクトリのパス")

# 対話式で圧縮先ディレクトリの指定を取得
destination_dir=$(get_input "圧縮先のディレクトリ(何も指定しない場合はカレントディレクトリ)")
destination_dir=${destination_dir:-"."}

# 対話式でアーカイブファイルの指定を取得
archive_name=$(get_input "アーカイブファイルの名前(何も指定しない場合はファイル/ディレクトリ名.\$(date +%Y%m%d).zip)")
archive_name=${archive_name:-"$(basename $source_path).$(date +%Y%m%d).zip"}

# ソースディレクトリに移動
cd "$(dirname "$source_path")" || exit

# zipコマンドで圧縮
if [ -d "$(basename "$source_path")" ]; then
    # ディレクトリの場合は-rオプションを付けて圧縮
    zip -r "$destination_dir/$archive_name" "$(basename "$source_path")"
else
    # ファイルの場合は-rオプションなしで圧縮
    zip "$destination_dir/$archive_name" "$(basename "$source_path")"
fi

echo "圧縮が完了しました。"

# カレントディレクトリに戻る
cd - || exit

作成後の処理

  • 実行権付与
chmod +x autozip
  • コマンド化
sudo chown root:root autozip && sudo mv autozip /usr/local/bin/autozip

実行結果

  • パスが通っていることを確認
which autozip

→ /usr/local/bin/autozip

に表示されます。

  • 実行確認

任意のディレクトリに移動し、

autozip

を実行。

これで、

  1. 対話式で圧縮するファイルやディレクトリを指定
  2. 圧縮先のディレクトリを対話で指定(何も指定しない場合はカレントディレクトリ)
  3. アーカイブファイルを対話で指定(何も指定しない場合はファイル/ディレクトリ名.$(date +%Y%m%d).zip)
  4. zipコマンドで圧縮。ディレクトリかを判別し、ディレクトリなら-rオプションをつける

が実行できました。

Bingによるシェルスクリプト。(UFW遮断)

概要

先だってChatGPTが書いた

  1. IPアドレスを尋ねる
  2. IPアドレスそのままか、/24ごとブロックするか、/16ごとブロックするかを0~2で質問(空白エンターは0)
  3. 実行前に内容を確認する。yなら続行、nなら中止
  4. 1~2に沿ってufw deny処理を行う
  5. 「ルールを付け加えました。他のIPブロックしますか?」の分岐を出す。yなら1に戻る。nなら次の処理
  6. ufw reloadを行う
  7. 現在のブロック状況を出しますか? yならufw status numberedを表示。nなら終了メッセージを出す

このスクリプト、Bingが書くとどうなるかを試してみました。

Bingの出力結果

  • bing_ufw_deny.sh
#!/bin/bash

# ループフラグを設定
loop=true

# ループ開始
while $loop; do
    # IPアドレスを尋ねる
    echo "ブロックしたいIPアドレスを入力してください。"
    read ip

    # IPアドレスの形式をチェック
    if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
        # IPアドレスそのままか、/24ごとブロックするか、/16ごとブロックするかを0~2で質問
        # 空エンターは0
        echo "IPアドレスをそのままブロックする場合は0、/24ごとブロックする場合は1、/16ごとブロックする場合は2を入力してください。"
        echo "何も入力しない場合は0とみなします。"
        read option

        # オプションに応じてIPアドレスを変換
        case $option in
            1)
                # /24ごとブロックする場合
                ip=${ip%.*}.0/24
                ;;
            2)
                # /16ごとブロックする場合
                ip=${ip%.*.*}.0.0/16
                ;;
            *)
                # その他の場合はIPアドレスそのまま
                ;;
        esac

        # 実行前に内容を確認する。yなら続行、nなら中止
        echo "ufw deny from $ip to any を実行します。よろしいですか? (y/n)"
        read confirm

        if [[ $confirm == [yY] ]]; then
            # yならufw deny処理を行う
            sudo ufw deny from $ip to any
            echo "ルールを付け加えました。"
        else
            # nなら中止メッセージを出す
            echo "処理を中止しました。"
        fi

        # 「ルールを付け加えました。他のIPブロックしますか?」の分岐を出す。yなら1に戻る。nなら次の処理
        echo "他のIPをブロックしますか? (y/n)"
        read loop

        if [[ $loop == [yY] ]]; then
            # yならループフラグをtrueにして1に戻る
            loop=true
        else
            # nならループフラグをfalseにしてループを抜ける
            loop=false
        fi

    else
        # IPアドレスの形式が不正な場合はエラーメッセージを出す
        echo "正しいIPアドレスを入力してください。"
    fi

done

# ufw reloadを行う
echo "ufw reload を実行します。"
sudo ufw reload

# 現在のブロック状況を出しますか? yならufw status numberedを表示。nなら終了メッセージを出す
echo "現在のブロック状況を表示しますか? (y/n)"
read status

if [[ $status == [yY] ]]; then
    # yならufw status numberedを表示する
    sudo ufw status numbered
else
    # nなら終了メッセージを出す
    echo "スクリプトが終了しました。"
fi

体感として

Bingはネットワークアドレスの切り出しが素直です。

  • Bingの場合
        # オプションに応じてIPアドレスを変換
        case $option in
            1)
                # /24ごとブロックする場合
                ip=${ip%.*}.0/24
                ;;
            2)
                # /16ごとブロックする場合
                ip=${ip%.*.*}.0.0/16
                ;;
            *)
                # その他の場合はIPアドレスそのまま
                ;;
        esac
  • ChatGPTの場合
    # ネットワークアドレスに変換
    if [[ -n "$block_cidr" ]]; then
        if [[ $block_option == "1" ]]; then
            # /24で区切る場合
            network_address=$(echo $ip_address | cut -d'.' -f1-3)
            # CIDR表記の生成 (/24)
            cidr_notation="$network_address.0$block_cidr"
        elif [[ $block_option == "2" ]]; then
            # /16で区切る場合
            network_address=$(echo $ip_address | cut -d'.' -f1-2)
            # CIDR表記の生成 (/16)
            cidr_notation="$network_address.0.0$block_cidr"
        fi
    else
        cidr_notation="$ip_address"
    fi

と、シンプルになっています。

蛇足ですが、ChatGPTはこの処理にたどり着くまで5回ぐらいの錯誤を繰り返しました。

なので、このケースで言えばBingはよりユーザーの意図をくみ取ったと考えられます。

こちらの選択肢もあると分かったという収穫。

ChatGPTによるシェルスクリプト。(対話式のUFWブロック)

概要

不正アクセス対策として役立つUFW。これによる不審なIPアドレスの遮断をもっと効率的に行うようにしてみました。

このスクリプトを書い(てもらっ)た背景

攻撃者は複数のIPアドレスから同時に不正アクセスをしてくるパターンが多々あり、

ufw deny from xxx.yyy.1.1
ufw deny from aaa.bbb.0.2

とやるより、

ufw deny from xxx.yyy.1.0/24
ufw deny from aaa.bbb.0.0/16

と、ネットワークアドレスごと遮断した方が効率的だからです。

前提

  • 既にUFWが稼働していること。
  • サービス指定ではなく、あらゆるアクセスを拒絶するという合意が当事者で取れていること。

ChatGPTへの質問と回答

提示した要件

  1. IPアドレスを尋ねる
  2. IPアドレスそのままか、/24ごとブロックするか、/16ごとブロックするかを0~2で質問(空エンターは0)
  3. 実行前に内容を確認する。yなら続行、nなら中止
  4. 1~2に沿ってufw deny処理を行う
  5. 「ルールを付け加えました。他のIPブロックしますか?」の分岐を出す。yなら1に戻る。nなら次の処理
  6. ufw reloadを行う
  7. 現在のブロック状況を出しますか? yならufw status numberedを表示。nなら終了メッセージを出す

出てきたスクリプト

  • ufw_deny.sh
#!/bin/bash

# ユーザーからIPアドレスとブロック範囲を入力してもらい、
# UFWを使用して指定した範囲のIPアドレスをブロックするスクリプトです。

while true; do
    # IPアドレスを尋ねる
    read -p "ブロックしたいIPアドレスを入力してください: " ip_address

    # ブロック範囲を尋ねる
    read -p "IPアドレスをそのままブロックする場合は0、/24ごとにブロックする場合は1、/16ごとにブロックする場合は2を入力してください(空エンターは0): " block_option

    # 入力が空の場合はデフォルトで0(IPアドレスそのまま)とする
    block_option=${block_option:-0}

    # ブロック範囲を計算
    case $block_option in
        1) block_cidr="/24" ;;
        2) block_cidr="/16" ;;
        *) block_cidr="" ;;
    esac

    # ネットワークアドレスに変換
    if [[ -n "$block_cidr" ]]; then
        if [[ $block_option == "1" ]]; then
            # /24で区切る場合
            network_address=$(echo $ip_address | cut -d'.' -f1-3)
            cidr_notation="$network_address.0$block_cidr"
        elif [[ $block_option == "2" ]]; then
            # /16で区切る場合
            network_address=$(echo $ip_address | cut -d'.' -f1-2)
            cidr_notation="$network_address.0.0$block_cidr"
        fi
    else
        cidr_notation="$ip_address"
    fi

    # 設定内容を確認する
    echo "以下の内容でブロックを行います:"
    echo "IPアドレス: $cidr_notation"

    read -p "よろしいですか?(y/n): " confirm

    # ユーザーが処理を続行しない場合、スクリプトを終了する
    if [[ $confirm != "y" && $confirm != "Y" ]]; then
        echo "処理を中止しました。"
        exit 0
    fi

    # ブロックルールをufwに追加
    sudo ufw deny from "$cidr_notation"

    # エラーがあった場合はエラーメッセージを表示して終了
    if [[ $? -ne 0 ]]; then
        echo "ERROR: 不正なソースアドレス"
        exit 1
    fi

    echo "ルールを付け加えました。"

    # 他のIPをブロックするか尋ねる
    read -p "他のIPをブロックしますか?(y/n): " continue_blocking
    if [[ $continue_blocking != "y" && $continue_blocking != "Y" ]]; then
        break
    fi
done

# ufwを再読み込みする
sudo ufw reload

# ブロック状況を確認するか尋ねる
read -p "現在のブロック状況を確認しますか?(y/n): " check_status
if [[ $check_status == "y" || $check_status == "Y" ]]; then
    sudo ufw status numbered
fi

# 終了メッセージを出す
echo "IPブロックを終了しました。"

このスクリプトを

chmod +x ufw_deny.sh

とすれば作成は完了です。

スクリプトの動き

./ufw_deny.sh
ブロックしたいIPアドレスを入力してください:
# IPアドレスを入力します

IPアドレスをそのままブロックする場合は0、/24ごとにブロックする場合は1、/16ごとにブロックする場合は2を入力してください(空エンター
は0): 
# IPアドレスのレンジを
# IPアドレスそのまま:0
# /24(255.255.255.0)で区切る:1
# /16(255.255.0.0)で区切る:2

IPアドレス:
よろしいですか? (y/n):
# 先ほどのIPを表示します。ネットワークアドレスの場合は、/24 /16で区切って表示します。
# 内容を確認します。
# y を入力後、ufwの処理が走ります。一般ユーザーの場合はsudoパスワードが訊かれます。

ルールを追加しました
ルールを付け加えました。
# ufw deny from (入力したIP/NWアドレス)を実行します。

他のIPをブロックしますか?(y/n): 
# 続行するかを訊きます。yの場合はIPアドレスから始まります。

ファイアウォールを再読込しました
現在のブロック状況を確認しますか?(y/n):
# yの場合は sudo ufw status numberedを実行します。

と、

  • IPアドレス→ネットワークアドレスへの変換
  • UFW DENYに追加
  • UFWルールの読込
  • 設定後のルール表示

まで一元管理してくれます。

Page 1 of 5

Powered by WordPress & Theme by Anders Norén