整理雲端硬碟,最怕資料夾一大串,名字重複、層級又深,人工看一圈就天黑。其實用 Google Apps Script 的 DriveApp.getFolders(),就能把你有權限的資料夾一口氣掃出來,還能加條件篩選、寫進試算表、做權限盤點。
這篇從最基本的語法開始,帶你一步步把清單匯出、找出空資料夾、比對擁有者與協作者,最後再補上常見坑洞與效能避雷。寫好的範例可以直接貼上跑,不用先懂太多程式,照步驟做就能把硬碟變清爽。希望本文章能幫助到需要的您。
目錄
{tocify} $title={目錄}
為什麼要認真學 getFolders()?
如果你在公司或個人日常要處理大量檔案,像是:
例行整理 Drive 資料夾、盤點舊案、抓出「空資料夾」;
針對所有資料夾做權限稽核、產出清單;
把每個資料夾底下檔案搬運、重新命名、或歸檔;
那你八成會用到 DriveApp 這個服務,而 getFolders() 就是打開「所有資料夾入口」的那把鑰匙。
快速認識 DriveApp.getFolders()
語法:DriveApp.getFolders()
回傳:FolderIterator(資料夾迭代器)
用途:遍歷你「有權限看到」的所有 Google 雲端硬碟資料夾(不含資源回收筒)。
如何取出資料夾:透過 while (it.hasNext()) { const folder = it.next(); } 一個個拿
小提醒:FolderIterator 不是陣列,不能用 forEach 或索引值 [i] 直接取。這是新手最常踩到的第一個坑。
建立專案與基本測試
1. 開啟 Apps Script 編輯器
在 Google 雲端硬碟中:新增 → 更多 → Google Apps Script。
2. 設定專案名稱
例如:Drive getFolders 全掃描工具。
3. 建立測試函式
function listAllFolders_basic() {
const it = DriveApp.getFolders(); // 取得所有資料夾的迭代器
let count = 0;
while (it.hasNext()) {
const f = it.next();
Logger.log('[%s] %s', f.getId(), f.getName());
count++;
}
Logger.log('Total folders: %s', count);
}
4. 授權並執行
第一次跑會跳出授權視窗,依指示允許即可。
5. 查看結果
在「執行」→「檢視記錄」看到輸出。
如果清單很多,建議不要單純用 Logger,改寫入試算表,效能與可讀性更好(下面有完整範例)。
getFolders() 周邊常用方法速查
在迭代每個 Folder 時,你通常會立刻用到:
getName():資料夾名稱
getId():資料夾 ID
getUrl():資料夾連結
getDateCreated() / getLastUpdated():建立與最後更新時間
getOwner() / getEditors() / getViewers():擁有者與協作者
getFolders():取得子資料夾(注意:這是 Folder 物件 的 getFolders(),可往下鑽)
實務範例
範例 1:把所有資料夾清單寫入 Google 試算表
需求:做一次盤點,產出可排序、可篩選的表格。
function exportAllFoldersToSheet() {
const ss = SpreadsheetApp.create('Drive 資料夾清單(全域掃描)');
const sh = ss.getActiveSheet();
sh.clear();
sh.appendRow(['Folder Name', 'Folder ID', 'URL', 'Owner Email', 'Created', 'Updated']);
const it = DriveApp.getFolders();
const rows = [];
while (it.hasNext()) {
const f = it.next();
const owner = f.getOwner ? f.getOwner() : null; // 有些情境下可能沒有 owner(像共用雲端硬碟)
rows.push([
f.getName(),
f.getId(),
f.getUrl(),
owner ? owner.getEmail() : '(N/A)',
f.getDateCreated(),
f.getLastUpdated()
]);
}
if (rows.length) sh.getRange(2, 1, rows.length, rows[0].length).setValues(rows);
Logger.log('Done: ' + ss.getUrl());
}
為什麼不邊迭代邊 appendRow()?
因為一次性 setValues() 才是效能王道;逐列寫入很容易觸發執行時間上限。
範例 2:只列出「空資料夾」
需求:清出殭屍資料夾,為整理或刪除做準備。
function listEmptyFolders() {
const it = DriveApp.getFolders();
const empty = [];
while (it.hasNext()) {
const f = it.next();
const hasSub = f.getFolders().hasNext();
const hasFiles = f.getFiles().hasNext();
if (!hasSub && !hasFiles) {
empty.push([f.getName(), f.getId(), f.getUrl()]);
}
}
const ss = SpreadsheetApp.create('空資料夾清單');
const sh = ss.getActiveSheet();
sh.appendRow(['Folder Name', 'Folder ID', 'URL']);
if (empty.length) sh.getRange(2, 1, empty.length, 3).setValues(empty);
Logger.log('Empty folders: ' + empty.length + ' | ' + ss.getUrl());
}
強烈建議:刪除前先輸出清單並人工確認,避免誤刪。
範例 3:權限盤點(擁有者、編輯者、檢視者)
需求:做資安稽核,找出外部網域的協作者。
function auditFolderPermissions() {
const it = DriveApp.getFolders();
const rows = [['Folder Name', 'Folder ID', 'Role', 'Email']];
while (it.hasNext()) {
const f = it.next();
// Owner(某些共用雲端硬碟資料夾可能沒有 owner)
try {
const owner = f.getOwner();
if (owner) rows.push([f.getName(), f.getId(), 'OWNER', owner.getEmail()]);
} catch (e) {
rows.push([f.getName(), f.getId(), 'OWNER', '(N/A)']);
}
// Editors
f.getEditors().forEach(u => rows.push([f.getName(), f.getId(), 'EDITOR', u.getEmail()]));
// Viewers
f.getViewers().forEach(u => rows.push([f.getName(), f.getId(), 'VIEWER', u.getEmail()]));
}
const ss = SpreadsheetApp.create('資料夾權限盤點');
ss.getActiveSheet().getRange(1, 1, rows.length, rows[0].length).setValues(rows);
Logger.log('Done: ' + ss.getUrl());
}
網域檢查:拉出清單後,用試算表公式或 Apps Script 再判斷 @yourcompany.com 以外的帳號。
範例 4:依條件篩選再處理(搭配 searchFolders)
getFolders() 是「全部」,若你只想找「名稱含關鍵字且未丟入資源回收筒」的資料夾,用 DriveApp.searchFolders(query) 更快:
function findFoldersByTitleContains(keyword) {
const q = `title contains "${keyword}" and trashed = false`;
const it = DriveApp.searchFolders(q);
const list = [];
while (it.hasNext()) {
const f = it.next();
list.push([f.getName(), f.getId(), f.getUrl()]);
}
Logger.log('Matched: ' + list.length);
return list;
}
searchFolders 支援類似 Drive 搜尋語法(title、modifiedDate、trashed…),做精準篩選更有效率。
範例 5:階層向下鑽(從指定資料夾開始)
有時不想全域掃描,只想從某個根資料夾往下抓到最末層。
function traverseFromFolderId(rootId) {
const root = DriveApp.getFolderById(rootId);
const acc = [];
function dfs(folder, path) {
const curPath = path + '/' + folder.getName();
acc.push([curPath, folder.getId(), folder.getUrl()]);
const it = folder.getFolders();
while (it.hasNext()) {
dfs(it.next(), curPath);
}
}
dfs(root, '');
const ss = SpreadsheetApp.create('階層清單');
ss.getActiveSheet().getRange(1, 1, acc.length, 3).setValues(acc);
Logger.log('Depth result: ' + acc.length + ' | ' + ss.getUrl());
}
這種 DFS(深度優先)要留意遞迴深度與執行時間上限(Apps Script 一次約 6 分鐘);路徑很深就考慮分批執行。
範例 6:批次重新命名資料夾(安全加護版)
需求:把名稱中包含指定字串的資料夾做規則化命名(例如去掉前後空白、替換非法字元)。
function normalizeFolderNames() {
const it = DriveApp.getFolders();
let changed = 0;
while (it.hasNext()) {
const f = it.next();
const oldName = f.getName();
let newName = oldName.trim();
newName = newName.replace(/[\\/:*?"<>|]/g, '_'); // 避免跨系統同步問題
if (newName !== oldName) {
try {
f.setName(newName);
changed++;
Logger.log('Rename: "%s" → "%s"', oldName, newName);
} catch (e) {
Logger.log('Skip (no permission?): %s | %s', oldName, e.message);
}
}
}
Logger.log('Done. Changed: ' + changed);
}
命名衝突:若同層已有同名資料夾,可能會丟錯或被拒;真要規模化命名,建議在新名稱後加序號或時間戳。
效能最佳化與穩定性策略
大批量掃描最常遇到的是「跑不完」。以下是穩定密技:
1. 控制輸出節奏
集中資料到陣列,最後一次 setValues();切勿在迴圈中頻繁寫入。
2. 分段執行 / 續跑機制
透過 PropertiesService 記錄「上次處理到哪個資料夾 ID」,下次從該點續跑。
或把 ID 清單先寫到試算表,分批讀取處理。
3. 避免無謂的深度遍歷
若只要頂層資訊,就不要對每個資料夾再 getFolders();層層往下很耗時。
4. 優先用 searchFolders(query)
有條件就先篩再掃,減少母體規模。
5. 錯誤容忍
try/catch 包覆敏感操作(改名、移動、改權限),記錄錯誤、不因個案中斷。
6. 時限意識
Apps Script 單次執行約 6 分鐘;超過就拆段或搭配觸發器(time-driven trigger)分批跑。
7. 記錄與回報
每 N 筆打一次 Logger.log() 或寫進暫存工作表,便於診斷。
權限、共用雲端硬碟與安全考量
1. 你只能看到你「有權限」的資料夾
getFolders() 只會遍歷你能存取的範圍。若覺得數量少,通常是權限不足。
2. 共用雲端硬碟(Shared drives)注意
某些操作(像擁有者概念)與個人雲端不同,getOwner() 可能回傳不到人。請以錯誤容忍方式處理,或改用 Advanced Drive Service(Drive API v3) 做更細緻的權限查詢。
3. 回收筒不在其中
getFolders() 不含資源回收筒內容;要查回收筒或更複雜條件,用 searchFolders() 或 Drive API 查詢。
4. 敏感動作要二次驗證
刪除、搬運、改權限等,都先「產生清單 → 人工審核 → 再執行」;別直接在同一支程式裡一把做完。
常見錯誤與雷點
1. 把 FolderIterator 當陣列用
錯誤:DriveApp.getFolders().forEach(...) 或 it[i]。
對策:用 while (it.hasNext()) { const f = it.next(); }。
2. 逐列 appendRow() 十萬火急寫表格
症狀:執行時間暴增,超時失敗。
對策:先把資料 push 到陣列,最後 setValues() 一次寫入。
3. 忽略共用雲端硬碟差異
症狀:getOwner() 拋錯或回傳 null。
對策:加 try/catch,或在資料欄用 (N/A) 區別;更進階用 Drive API v3。
4. 期望 getFolders() 有排序或分頁
事實:沒有直接排序;你要自己收集成陣列再排序。
對策:例如 rows.sort((a,b) => a[0].localeCompare(b[0])) 依名稱排序。
5. 想用關鍵字模糊搜卻用 getFoldersByName()
事實:getFoldersByName(name) 是精準比對。
對策:使用 searchFolders('title contains "xxx" and trashed = false')。
6. 大量改名/搬運未做衝突與例外處理
症狀:個別資料夾失敗導致整批中斷。
對策:封裝 try/catch、記錄失敗名單、最後統整報表。
7. 直接刪除空資料夾
風險:有時看起來「空」,但其實有隱藏檔或即將同步的內容。
對策:先輸出清單,人工檢查後再刪;或等 1–2 天確認無人使用再執行。
8. 忽略配額與速率限制
症狀:一段時間後開始丟 429/Rate Limit 或時間上限。
對策:分段執行、加上 Utilities.sleep(100~300) 做輕量節流、縮小掃描範圍。
問題集
Q1:getFolders() 會包含共用雲端硬碟嗎?
A:它會遍歷你能看見的資料夾(包含某些共用雲端硬碟場景),但權限模型有差異,像 getOwner() 可能取不到。對共用雲端硬碟的進階查詢或管理,建議改用 Advanced Drive Service (Drive API v3)。
Q2:能不能只掃描我雲端硬碟某個母資料夾?
A:可以,用「範例 5」從 getFolderById() 取得根資料夾後,往下 DFS 或 BFS 即可。
Q3:要怎麼找「名稱包含 XXX」的資料夾?
A:searchFolders('title contains "XXX" and trashed = false'),效率遠勝全域掃描再過濾。
Q4:怎麼避免超時?
A:分批、先篩後掃、集中寫表、必要時加觸發器定時跑,並加入續跑機制。
可直接複用的輔助工具片段
(1) 陣列批量寫入工具
function writeRows_(sheet, startRow, startCol, rows) {
if (!rows.length) return;
sheet.getRange(startRow, startCol, rows.length, rows[0].length).setValues(rows);
}
(2) 續跑機制(記錄最後處理的資料夾 ID)
function resumeToken_(val) {
const props = PropertiesService.getScriptProperties();
if (typeof val === 'string') { props.setProperty('LAST_FOLDER_ID', val); }
return props.getProperty('LAST_FOLDER_ID');
}
(3) 輕量節流
function throttle_(ms) {
Utilities.sleep(ms || 150);
}
參考指令小抄
取得所有資料夾迭代器:DriveApp.getFolders()
名稱精準比對:DriveApp.getFoldersByName('Name')
條件搜尋(推薦):DriveApp.searchFolders('title contains "X" and trashed = false')
取得子資料夾:folder.getFolders()
其他常用:getName()/getId()/getUrl()/getDateCreated()/getLastUpdated()/getEditors()/getViewers()
