残すシート名を指定し、それ以外のシートをGASで削除する手順です。削除前に対象一覧を下見し、実行時はファイル全体のコピーを作ってから削除します。最初は本番ファイルを複製した練習用ファイルで試してください。
残すシート名と参照関係を確認する
keepSheetsに残すシートの名前をすべて指定します。大文字小文字や空白も含めた完全一致です。この修正版は指定名が1つでも存在しなければ停止し、表示中のシートを最低1枚残すよう確認します。配列に書き忘れたシートは削除対象なので、下見一覧を必ず確認してください。非表示シートも対象です。
残すシートの数式・グラフ・名前付き範囲などが削除対象を参照していると、削除後に壊れる場合があります。コードは参照関係を解析しません。必要性を名前だけで判断せず、共同編集や他の自動処理を止めて確認します。
下見と削除を分けたGASコード
練習用ファイルの「拡張機能」→「Apps Script」でコードを保存します。spreadsheetIdにコピーのURLの/d/と/editの間のIDを設定し、keepSheetsを実際の名前へ変更します。まずpreviewDeleteSheetsだけを実行します。この関数は削除せず、現在のアカウントのユーザープロパティへ計画を保存します。
const DELETE_DEMO = {spreadsheetId:'COPY_SPREADSHEET_ID',keepSheets:['勤怠','共通','名簿']};
const DELETE_PLAN_KEY = 'deleteSheetPreviewV1';
function sheetDeletePlan_() {
const ss=SpreadsheetApp.openById(DELETE_DEMO.spreadsheetId), sheets=ss.getSheets();
const keep=DELETE_DEMO.keepSheets;
if(!keep.length || new Set(keep).size!==keep.length)throw new Error('残す名前を重複なしで指定してください');
if(sheets.length>50)throw new Error('練習は50シート以内です');
const state=sheets.map(s=>({id:s.getSheetId(),name:s.getName(),hidden:s.isSheetHidden()}));
if(keep.some(n=>!state.some(s=>s.name===n)))throw new Error('残すシート名に存在しない名前があります');
if(!state.some(s=>keep.includes(s.name)&&!s.hidden))throw new Error('表示中のシートを最低1枚残してください');
return {spreadsheetId:ss.getId(),keep:keep.slice(),state,remove:state.filter(s=>!keep.includes(s.name))};
}
function previewDeleteSheets() {
const plan=sheetDeletePlan_(), text=JSON.stringify(plan);
if(Utilities.newBlob(text).getBytes().length>8000)throw new Error('計画が大きすぎます');
PropertiesService.getUserProperties().setProperty(DELETE_PLAN_KEY,text);
console.log('削除予定(まだ削除しません):'+JSON.stringify(plan.remove));
console.log('残すシート:'+JSON.stringify(plan.keep));
}
function deleteExcludedSheets() {
const lock=LockService.getScriptLock();
if(!lock.tryLock(1000))throw new Error('別の処理が実行中です');
let completedCount=null;
try {
const props=PropertiesService.getUserProperties(),saved=props.getProperty(DELETE_PLAN_KEY);
if(!saved)throw new Error('同じアカウントで先にpreviewDeleteSheetsを実行してください');
const plan=sheetDeletePlan_();
if(saved!==JSON.stringify(plan))throw new Error('設定やシート構成が変わりました。再度下見してください');
if(!plan.remove.length){console.log('削除対象なし');return;}
const backup=DriveApp.getFileById(plan.spreadsheetId).makeCopy('シート削除前_'+Utilities.getUuid());
console.log('バックアップURL:'+backup.getUrl());
if(saved!==JSON.stringify(sheetDeletePlan_()))throw new Error('コピー中にシート構成が変わりました');
props.deleteProperty(DELETE_PLAN_KEY);
const ss=SpreadsheetApp.openById(plan.spreadsheetId),done=[];
try {
for(const item of plan.remove){
const sheet=ss.getSheetById(item.id);
if(!sheet || sheet.getName()!==item.name)throw new Error('削除対象が変更されました');
ss.deleteSheet(sheet);done.push(item);
}
} catch(error) {
console.log('削除呼び出しが正常終了した対象(全削除結果とは限りません):'+JSON.stringify(done));
throw new Error('途中停止。バックアップと現在の状態を照合してください');
}
completedCount=done.length;
} finally {
try {SpreadsheetApp.flush();}
finally {lock.releaseLock();}
}
console.log('削除処理終了:'+completedCount+'枚。現在のシート構成と参照先を確認してください');
}
削除予定を確認して実行する
ログの削除予定と残す名前を1枚ずつ照合し、問題がない場合だけdeleteExcludedSheetsを手動で実行します。下見後にシートの追加・削除・改名・順序・表示状態・設定が変わった場合は停止します。セル内容の変化は比較対象ではないため、下見中も共同編集を止めてください。
削除実行にはスプレッドシートの編集とDriveのコピー作成の権限が必要です。権限の要求を確認してください。ファイル全体のバックアップを作れなければ削除しません。コピーはマイドライブで確認し、ログに出たバックアップURLを保存します。コピーの共有設定が元と同じとは限りません。
削除処理は一括で取り消せません。途中停止した場合は、下見ログにある削除予定の全シートID・名前を現在の構成と照合し、すぐに再実行しないでください。削除呼び出しの直後に停止すると、削除されたシートがdoneやエラーログに記録されない可能性があります。ログに載った対象だけを復旧確認の範囲にしないでください。保存した計画は削除開始前に消費するため、再実行には新たな下見が必要です。バックアップ作成後に停止した場合は、コピーだけが残ることもあります。ロック解放前のSpreadsheetApp.flushは保留中の変更を反映する処理であり、削除を取り消しません。反映に失敗してもロックは解放し、削除処理終了のログは出しません。
復旧と検証範囲
削除したシートの復旧は自動化していません。バックアップまたは変更履歴と照合し、後から加えられた編集を失わない方法を検討します。バックアップからシートだけを戻しても参照式やシートIDまで元どおりになるとは限らないため、残したシートの計算・グラフも確認してください。トリガーは作成しません。
公式仕様はSpreadsheet.deleteSheetとFile.makeCopy、Lock.releaseLockを参照してください。2026年10月28日の確認はローカル模擬テストで、削除後の応答失敗時にログから対象が漏れる場合と、反映失敗時のロック解放を含め、名前誤り・下見なし・構成変化・コピー失敗時の停止と、バックアップ後の削除順序を検証しました。Google認可・実削除・復旧・画面確認は未実施です。
ScriptLockは同じプロジェクト内の実行だけを制御します。他の人や別プロジェクトによる同時変更を完全に防ぐものではありません。既存画像は旧版参考であり、今回のコードの実行証拠ではありません。


旧版コードはMOTOKI-LLC/cg-method-codeにあります。本文との同期は未確認のため、本文のコードを単独で使用してください。





