# [メタ情報] # 識別子: ファイルパス変更処理_exe # システム名: ファイルパスの変更を記録するappとGAS # 技術種別: Misc # 機能名: Misc # 使用言語: ShellScript GAS # 状態: 実行用 # [/メタ情報] ファイルパスの変更を記録する.app アプリケーション 以下をAutomatorで登録 アプリにする ログイン項目にする /Users/xxxxxxxxm1/Library/Scripts/watch_rename_find.sh ShellScript #!/bin/bash # 監視するフォルダ(mmedia と pmedia の両方を監視) WATCH_DIRS=( "/Volumes/NO3_SSD/Dropbox/dropbox_1/mmedia" "/Volumes/NO3_SSD/Dropbox/dropbox_1/pmedia" ) LOG_FILE="/Volumes/NO3_SSD/Dropbox/dropbox_1/media_filepath/folder_watch.log" BEFORE_STATE="/Volumes/NO3_SSD/Dropbox/dropbox_1/media_filepath/folder_before.txt" AFTER_STATE="/Volumes/NO3_SSD/Dropbox/dropbox_1/media_filepath/folder_after.txt" # Google Apps Script Webhook URL WEBHOOK_URL = "" # 常設ファイルを作成(ファイルがなければ作成) if [ ! -f "$BEFORE_STATE" ]; then touch "$BEFORE_STATE" fi if [ ! -f "$AFTER_STATE" ]; then touch "$AFTER_STATE" fi # 初回のフォルダ状態を記録 find "${WATCH_DIRS[@]}" \( \( -type f -o -type d \) -and -not -name ".DS_Store" \) | sort > "$BEFORE_STATE" while true; do sleep 2 # 2秒ごとにチェック # 変更後のフォルダ状態を取得 find "${WATCH_DIRS[@]}" \( \( -type f -o -type d \) -and -not -name ".DS_Store" \) | sort > "$AFTER_STATE" # 差分を取得(削除されたファイル・フォルダ) REMOVED=$(comm -23 "$BEFORE_STATE" "$AFTER_STATE") # 差分を取得(追加されたファイル・フォルダ) ADDED=$(comm -13 "$BEFORE_STATE" "$AFTER_STATE") # リネーム(削除+追加のペア)を検出し、Webhook へ送信 if [[ -n "$REMOVED" && -n "$ADDED" ]]; then TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S") LOG_ENTRY="$TIMESTAMP - ファイルまたはフォルダが移動またはリネームされました: $REMOVED → $ADDED" echo "$LOG_ENTRY" >> "$LOG_FILE" # Google スプレッドシートへ送信(リダイレクトを追跡&エラーフィルタリング) RESPONSE=$(curl -s -L -X POST \ --data-urlencode "before=$REMOVED" \ --data-urlencode "after=$ADDED" \ --data-urlencode "time=$TIMESTAMP" \ "$WEBHOOK_URL") # HTMLレスポンスかどうかを判定し、エラーのみログに記録 if [[ -n "$RESPONSE" && "$RESPONSE" != "Success" ]]; then if echo "$RESPONSE" | grep -q "> "$LOG_FILE" else echo "$TIMESTAMP - Webhook Error: $RESPONSE" >> "$LOG_FILE" fi fi fi # 更新 mv "$AFTER_STATE" "$BEFORE_STATE" done F3_ファイルパス変更記録 <あなたのID> sheet1 (A)変更前パス (B)変更後パス (C)更新日時 (D)更新フラグ RenameHistoryLogger.gs function doPost(e) { const sheet = SpreadsheetApp.openById("<あなたのID>") .getSheetByName("sheet1"); if (!sheet) { return ContentService.createTextOutput("Error: Sheet not found") .setMimeType(ContentService.MimeType.TEXT); } // パラメータ取得 const before = e.parameter.before ? decodeURIComponent(e.parameter.before) : "N/A"; const after = e.parameter.after ? decodeURIComponent(e.parameter.after) : "N/A"; const time = e.parameter.time || Utilities.formatDate(new Date(), "Asia/Tokyo", "yyyy-MM-dd HH:mm:ss"); // ✅ 新規データを (D)=TRUE 付きで追加 sheet.appendRow([before, after, time, "TRUE"]); // データ取得・整形 const data = sheet.getDataRange().getValues(); // ✅ 1. 空白行を除外 const cleanedData = data.filter(row => row.some(cell => cell.toString().trim() !== "")); // ✅ 2. before + after が同じ重複行のうち、更新日時が古い方だけを残す const recordMap = {}; const indicesToKeep = new Set(); cleanedData.forEach((row, index) => { const key = row[0] + "|" + row[1]; // A+B const rowTime = new Date(row[2]); if (!recordMap[key]) { recordMap[key] = { index: index, time: rowTime }; indicesToKeep.add(index); } else { if (rowTime < recordMap[key].time) { indicesToKeep.delete(recordMap[key].index); recordMap[key] = { index: index, time: rowTime }; indicesToKeep.add(index); } } }); // ✅ 3. 重複を除いたデータを抽出 const dedupedData = cleanedData.filter((_, index) => indicesToKeep.has(index)); // ✅ 4. 更新日時の降順でソート(新しいものが上に) dedupedData.sort((a, b) => new Date(b[2]) - new Date(a[2])); // ✅ 5. 書き戻し sheet.clearContents(); if (dedupedData.length > 0) { sheet.getRange(1, 1, dedupedData.length, dedupedData[0].length).setValues(dedupedData); } return ContentService.createTextOutput("Success").setMimeType(ContentService.MimeType.TEXT); }