最近MacbookNeoをつかいまくってて、
SDカードとか書き込んでて気がついた
そうだった。.ds_storeとか_ファイル名とか、Macを使ってると隠しファイルができてしまうのだ。
しかし!
それらを消すコマンドがある!
ターミナルを開いて、以下のコマンドを入れる
dot_clean -m /Volumes/NO\ NAME
マウントされている名前はNO NAMEなんだけど、スペースが入ってるからバックスラッシュ・スペースでつながっている
これを毎回やるのは嫌なので、
取り出しをするときにフックして、dot_cleanを実行するアプリを作ってみた。
名前はdcleanにした
xcodeを立ち上げて、アカウント入れたりなんだりしてから、contentViewやItemを消して、dcleanAppだけ残して以下のように書き換え
import SwiftUI
import AppKit
import Combine // 🌟 これを追加!
// MARK: - 1. ログのデータ構造と管理クラス
// 重複したログでも区別できるように Identifiable にする
struct LogItem: Identifiable {
let id = UUID()
let text: String
}
// ログを管理し、UI(メニューバー)を更新する専用のクラス
class LogStore: ObservableObject {
static let shared = LogStore()
@Published var logs: [LogItem] = []
// UIの更新は必ずメインスレッドで行う
func addLog(_ message: String) {
DispatchQueue.main.async {
let formatter = DateFormatter()
formatter.dateFormat = "HH:mm:ss" // 実行時間も表示
let timeString = formatter.string(from: Date())
let newLog = LogItem(text: "[\(timeString)] \(message)")
// 最新のログが一番上に来るように追加
self.logs.insert(newLog, at: 0)
// メニューが長くなりすぎないよう最新10件に制限
if self.logs.count > 10 {
self.logs.removeLast()
}
}
}
func clear() {
DispatchQueue.main.async {
self.logs.removeAll()
}
}
}
// MARK: - 2. メインのApp定義
@main
struct dcleanApp: App {
// LogStoreの変更を監視する
@StateObject private var logStore = LogStore.shared
init() {
// アプリ起動時にディスク監視を開始
DiskObserver.shared.start()
}
var body: some Scene {
MenuBarExtra("dclean", systemImage: "wand.and.stars") {
Text("外付けドライブの取り外しを監視中...")
.font(.caption)
.disabled(true)
// ログが存在する場合のみ履歴メニューを表示
if !logStore.logs.isEmpty {
Divider()
Text("最近の動作履歴")
.font(.caption)
.disabled(true)
ForEach(logStore.logs) { log in
Text(log.text)
.font(.system(size: 11)) // ログは少し小さめの文字で表示
}
Divider()
Button("履歴をクリア") {
logStore.clear()
}
}
Divider()
Button("アプリを終了") {
NSApplication.shared.terminate(nil)
}
}
.menuBarExtraStyle(.menu)
}
}
// MARK: - 3. ディスク監視クラス
class DiskObserver: NSObject {
static let shared = DiskObserver()
private override init() {
super.init()
}
func start() {
NSWorkspace.shared.notificationCenter.addObserver(
self,
selector: #selector(handleVolumeWillUnmount(_:)),
name: NSWorkspace.willUnmountNotification,
object: nil
)
}
@objc func handleVolumeWillUnmount(_ notification: Notification) {
guard let volumeURL = notification.userInfo?[NSWorkspace.volumeURLUserInfoKey] as? URL else { return }
let path = volumeURL.path
guard path.hasPrefix("/Volumes/") else { return }
// "/Volumes/USB_Drive" から "USB_Drive" の部分だけを抽出して見やすくする
let driveName = volumeURL.lastPathComponent
print("取り外しを検知: \(path) - dot_cleanを開始します。")
LogStore.shared.addLog("⏳ \(driveName) をクリーン中...")
runDotClean(for: path, driveName: driveName)
}
func runDotClean(for path: String, driveName: String) {
let task = Process()
task.launchPath = "/usr/sbin/dot_clean"
task.arguments = ["-m", path]
do {
try task.run()
task.waitUntilExit()
print("dot_clean完了: \(path) - 安全に取り外します。")
LogStore.shared.addLog("✅ \(driveName) のクリーン完了")
} catch {
print("dot_clean実行エラー: \(error.localizedDescription)")
LogStore.shared.addLog("❌ \(driveName) でエラー発生。\(error.localizedDescription)")
}
}
}
三角を押して動作確認したら、ビルド対象をAny Mac(arm64, x86_64)にして、メニューのプロダクトからアーカイブを選んでディストリビュートし、CopyAppを選んで適当なところに配置、XCODEでを終わらせて、できたAppをダブルクリックしたら動くはず
これで、いちいち、dot_cleanする必要はなくなった
欲しい方はこちらからどうぞ
https://drive.google.com/file/d/1SG18pzazkZzb-8_BR4v4Jonl2tqZUpFc/view?usp=drive_link
起動するとこのようなアイコンが出ます。
終了させるときは「アプリを終了」です。
実はMacだとZIPして他所様に渡すときにもこれらのゴミが入ることがあります。
ZIPするときにすでにフォルダ紛れているのです。
フォルダごとGoogleDriveに置くときにも入ってしまいます。
これについては自分で気をつけるしかなく、
そうは言っても限界がありますので、クイックアクションを作ります。
ショートカットアプリを開き、クイックアクションを選択し、右の+をクリック
以下のように構成します。







