HapInS Developers Blog

HapInSが提供するエンジニアリングの情報サイト

【Bash】サブディレクトリの中身だけ消してディレクトリは残したい!

HapInSアドベントカレンダー2024、22日目、今年もNoRADのサンタクロース追跡を楽しみにしているponです!

本日は「サブディレクトリたちの中身だけ削除するものの、ディレクトリは残しておきたい!」というときのスクリプトについて説明したいと思います!

経緯:不要なデータとの遭遇

先日、YouTubeでたまたま見かけた海外製の超小型ゲーム機を購入してみたのですが、ゲーム機に内蔵されていたmicroSDカードに不要なゲームデータが内蔵されていたのでそれを削除するために今回のスクリプトを作成することにしました。

イメージ画像

スクリプト以外での削除方法

一応、スクリプトで削除する以外の方法も(かなり面倒ではありますが)、大きく二通りありますので、そちらもざっくり記載しておきます(今回は対象外です)

StockOSイメージをmicroSDに書き込む

内蔵されているmicroSDカードごと初期化して、メーカー公式のStockOS(Linuxベース)をダウンロードして、WindowsであればRufusMacであればBalene EtcherなどでSDカードに書き込むパターンです。
必要なディレクトリはOSイメージ書き込み後、自動で作成できるようです。初めての場合、地味に手順が面倒です。

手作業で削除

不要なデータを一個一個削除しようと考えると、これもかなり面倒です。
18個ほどディレクトリがあり、合計ファイル数ははっきり覚えていませんが、合計ファイルサイズは20GBぐらいと、昔実家にあったWindowsXPの総RAM容量ぐらいあります。
時間の有り余っている人は良いかも知れませんが、私は遠慮しておきます。

スクリプト

それでは、さっそくスクリプトを見ていきましょう。
今回はbashスクリプトですが、一部Mac向けのコマンドを含んだbashとして作成しています。

#!/bin/bash

# 引数で指定されたディレクトリを取得
TARGET_DIR="$1"

# 引数が指定されていない場合はエラーメッセージを表示して終了
if [ -z "$TARGET_DIR" ]; then
  echo "使用方法: $0 <ターゲットディレクトリ>"
  exit 1
fi

# 指定されたディレクトリが存在するか確認
if [ ! -d "$TARGET_DIR" ]; then
  echo "エラー: 指定されたディレクトリ '$TARGET_DIR' は存在しません。"
  exit 1
fi

# 削除対象ディレクトリのリストを設定
TARGET_DIRS=("Arcade" "Atari lynx" "Game Boy" "Game Boy Advance" "Game Boy Color" "Game Gear" "Neo Geo Pocket"
             "NES" "PCE-TurboGrafx" "Pico8" "Pokemini" "PS1" "Sega Genesis" "Sega Master System" "SNES" "Virtualboy" "WonderSwan")

# 総ディレクトリ数を取得
TOTAL_DIRS=${#TARGET_DIRS[@]}
COMPLETED=0

# 削除対象ディレクトリを再帰的に処理
for dir in "${TARGET_DIRS[@]}"; do
  # 対象サブディレクトリが存在する場合
  if [ -d "$TARGET_DIR/$dir" ]; then
    # ロックを解除し、すべてのファイルとサブディレクトリを削除
    find "$TARGET_DIR/$dir" -mindepth 1 -exec chflags nouchg {} \; -exec rm -rf {} +

    # 進捗状況を計算
    COMPLETED=$((COMPLETED + 1))
    PERCENTAGE=$((COMPLETED * 100 / TOTAL_DIRS))

    # 完了したディレクトリを表示
    echo -e "完了:$TARGET_DIR/$dir - 100% 完了"

    # 次のディレクトリの「処理中」を表示し、進捗を上書き
    if [ "$COMPLETED" -lt "$TOTAL_DIRS" ]; then
      echo -ne "処理中: $TARGET_DIR/${TARGET_DIRS[COMPLETED]} - $PERCENTAGE% 完了\r"
    fi
  else
    echo "警告: $TARGET_DIR/$dir は存在しません。"
  fi
done

echo -e "\n指定されたディレクトリ内の削除対象フォルダの中身をすべて削除しました。"

各機能の説明

引数の取得と検証

機能:
スクリプトを実行する際に、対象のディレクトリをコマンドライン引数として渡す必要があります。

TARGET_DIR="$1"
if [ -z "$TARGET_DIR" ]; then
  echo "使用方法: $0 <ターゲットディレクトリ>"
  exit 1
fi

引数を渡すとこんな感じですね。

bash clear_dirs.sh targetdir

動作:

  • $1:スクリプトに渡された最初の引数を取得。
  • 引数が指定されていない場合、使用方法を表示して終了します。

指定ディレクトリの存在確認

機能:
引数で渡されたディレクトリが実際に存在するか確認します。

if [ ! -d "$TARGET_DIR" ]; then
  echo "エラー: 指定されたディレクトリ '$TARGET_DIR' は存在しません。"
  exit 1
fi

動作:

  • [ ! -d "$TARGET_DIR" ]:指定されたパスが存在しないか、ディレクトリでない場合にエラーメッセージを表示して終了。

削除対象ディレクトリのリスト定義

機能:
削除対象のサブディレクトリ名をリストとして定義します。

今回はそもそものデータが入っているディレクトリを対象とし、他にも大事そうな設定が入っているディレクトリもチラホラあったため、対象のディレクトリをリスト定義しています。

TARGET_DIRS=("Arcade" "Atari lynx" "Game Boy" "Game Boy Advance" "Game Boy Color" "Game Gear" "Neo Geo Pocket"
             "NES" "PCE-TurboGrafx" "Pico8" "Pokemini" "PS1" "Sega Genesis" "Sega Master System" "SNES" "Virtualboy" "WonderSwan")

動作:

みんな大好き配列!わたしも大好きです。

削除処理の進捗管理

機能:
削除対象ディレクトリの総数を計算し、進捗率を管理します。

TOTAL_DIRS=${#TARGET_DIRS[@]}
COMPLETED=0

動作:

メイン処理(削除ループ)

機能:

  • 定義された削除対象ディレクトリを順番に確認し、中身を削除します。
for dir in "${TARGET_DIRS[@]}"; do
  if [ -d "$TARGET_DIR/$dir" ]; then
    find "$TARGET_DIR/$dir" -mindepth 1 -exec chflags nouchg {} \; -exec rm -rf {} +

動作:

  • 配列TARGET_DIRSをループ処理で1つずつ取り出し、$TARGET_DIRのサブディレクトリとして存在するかを確認。 <存在する場合>
  • find "$TARGET_DIR/$dir" -mindepth 1:指定されたディレクトリの中身(サブディレクトリやファイル)を対象に検索。
  • chflags nouchg {}:削除不可属性を解除(macOS用のコマンド)。
  • rm -rf {}:再帰的に削除。

進捗状況の計算と表示

機能:
完了したディレクトリ数を計算し、進捗率を表示します。

COMPLETED=$((COMPLETED + 1))
PERCENTAGE=$((COMPLETED * 100 / TOTAL_DIRS))
echo -e "完了:$TARGET_DIR/$dir - 100% 完了"

動作:

この機能は、スクリプト実行時に大きなファイルを削除しているとフリーズしたみたいになってしまうので、何が起こっているのかを可視化するために実装しました。

未存在ディレクトリの警告

機能:
削除対象リストに含まれるが、実際には存在しないディレクトリについて警告を出力。

else
  echo "警告: $TARGET_DIR/$dir は存在しません。"

動作:

  • 存在しない場合に警告を表示。

処理完了メッセージ

機能:
全ての対象ディレクトリの処理が終わったら終了メッセージを出力。

echo -e "\n指定されたディレクトリ内の削除対象フォルダの中身をすべて削除しました。"

動作:

  • 処理がすべて終了したことをユーザーに知らせます。

おわりに

いかがでしたでしょうか?
最近はPico-8やPort masterなど、自作ゲームコミュニティーも増え、海外製のゲーム機で手軽に遊べる機会も増えてきました。
チャッピー(ChatGPI)やジミー(Gemini)が増えたことで、初心者でも手軽にスクリプトを作成するハードルが下がりましたので、不要なデータを削除したゲーム機でそれらのインディーズゲームを遊んでみてはいかがでしょうか?