你可能早就會用 Drive
搜尋、分享連結,但真正要把檔案「管理好」,關鍵在從根開始。DriveApp.getRootFolder()
提供一個穩定起點:我們能從這裡列目錄、過濾出需要處理的檔案、一次建立資料夾骨架,甚至定期輸出盤點報表。本文把流程拆成小模組:先授權與基本迭代,再上查詢語法、批次移動、分類歸檔,最後補上效能與配額的避坑法。你也會看見常見誤解的拆解——例如把
My Drive 與 Shared drives
混為一談,或忽略單一父層的設定,導致移動後的路徑判讀混亂。希望本篇文章內容能夠幫助到需要的您。
目錄
{tocify} $title={目錄}
getRootFolder() 是什麼?為什麼要用它?
DriveApp.getRootFolder() 會回傳目前執行身分的「My Drive」根資料夾(回傳型別為 Folder 物件)。直白講,就是你一打開 Google Drive 網頁看到的那個最上層位置。拿到這個 Folder 物件後,你就能:
列出根層的檔案/資料夾(例如找出「漂浮在根目錄」的零散檔案)
建立標準化的專案資料夾結構(在根目錄下一次生成多層資料夾)
以根目錄為起點做搜尋(searchFiles() / searchFolders())
透過 moveTo() 把檔案從根目錄搬到指定資料夾,維持單一父層結構(亦可配合 DriveApp.enforceSingleParent(true))
官方定義:「Gets the folder at the root of the user's Drive.」且 Folder 提供查詢、移動、建立子資料夾等方法。
重要界線
getRootFolder() 只對「使用者的 My Drive」成立;若你的工作重心在 共用雲端硬碟(Shared drives),官方建議改用「進階 Drive 服務(Advanced Drive Service)」或 Drive API 直接處理,因為內建 DriveApp 在某些新功能與共用雲端硬碟存取上有侷限。
快速上手:從 0 到列出根目錄
1. 建專案與權限授權
在 Google Drive 任何位置建立一個 Apps Script 專案(或在 Sheets/Docs 裡的擴充功能 → Apps Script)。
貼上以下程式碼,首次執行會要求授權,因為 DriveApp 需要 Drive 權限(通常會自動偵測到 https://www.googleapis.com/auth/drive 範圍):
function listRoot() {
const root = DriveApp.getRootFolder();
const files = root.getFiles();
const folders = root.getFolders();
Logger.log('=== Files in My Drive root ===');
while (files.hasNext()) {
const f = files.next();
Logger.log(`${f.getName()} | ${f.getUrl()}`);
}
Logger.log('=== Folders in My Drive root ===');
while (folders.hasNext()) {
const d = folders.next();
Logger.log(`${d.getName()} | ${d.getUrl()}`);
}
}
getFiles() / getFolders() 都是從 Folder 物件出發的標準迭代器用法。
2. 用搜尋語法直接篩出根層項目
你可以在根資料夾上用 Drive v2 的查詢語法(modifiedDate, title contains, starred 等)做更精準的篩選:
function searchAtRoot() {
const root = DriveApp.getRootFolder();
const q = 'modifiedDate > "2024-01-01" and title contains "報告"';
const iter = root.searchFiles(q); // 或 searchFolders(q)
while (iter.hasNext()) {
const item = iter.next();
Logger.log(`${item.getName()} | ${item.getUrl()}`);
}
}
searchFiles() / searchFolders() 在 Folder 物件上可用,且官方文件特別提醒:內建 Drive 服務採用 Drive API v2 的查詢欄位名稱。
常見實務:清根目錄、搬動與初始化
A. 清理根目錄的零散檔案
很多人會不小心把檔案建立在根目錄,久了就很亂。以下範例會把根層所有 Google 文件(mimeType 比對)搬到你指定的「_Inbox」資料夾;如果資料夾不存在會自動建立。
function tidyRootDocs() {
DriveApp.enforceSingleParent(true); // 強制單一父層,有助避免舊多重父層殘留
const root = DriveApp.getRootFolder();
const targetName = '_Inbox';
// 找或建目標資料夾
let target;
const found = root.getFoldersByName(targetName);
if (found.hasNext()) {
target = found.next();
} else {
target = root.createFolder(targetName);
}
// 只搬 Google Docs
const q = 'mimeType = "application/vnd.google-apps.document"';
const files = root.searchFiles(q);
let moved = 0;
while (files.hasNext()) {
const file = files.next();
file.moveTo(target); // 直接移動,維持單一父層
moved++;
}
Logger.log(`Moved ${moved} Docs to ${target.getName()}`);
}
moveTo(destination) 可把檔案/資料夾移到新父層;DriveApp.enforceSingleParent(true) 讓父層語意保持單一,對舊多父層模型有保險作用。
B. 初始化專案資料夾骨架
一次在根目錄建立多層資料夾(像 Project_X/{01_Input, 02_Work, 03_Output}):
function initProjectSkeleton(projectName) {
const root = DriveApp.getRootFolder();
const project = root.createFolder(projectName);
['01_Input', '02_Work', '03_Output'].forEach(n => project.createFolder(n));
Logger.log(`Created ${projectName} skeleton at root.`);
}
C. 從根目錄拉一份「日誌」報表
把根層所有資料夾的名稱、建立時間、是否加星號,輸出到 Logger(你也可以寫進試算表):
function rootFolderDigest() {
const root = DriveApp.getRootFolder();
const folders = root.getFolders();
while (folders.hasNext()) {
const d = folders.next();
Logger.log([
d.getName(),
d.getDateCreated(),
d.isStarred(),
d.getUrl()
].join(' | '));
}
}
Folder 支援 getDateCreated()、isStarred()、getUrl() 等方法,足夠做目錄稽核。
Shared drives 注意事項
getRootFolder() 不會回傳共用雲端硬碟(Shared drives)的根節點;它只代表使用者個人的 My Drive。若要操作 Shared drives:
走 進階 Drive 服務(Advanced Drive Service) 或原生 Drive API,這樣可以指定 supportsAllDrives=true、includeItemsFromAllDrives=true 等參數處理共用雲端硬碟的存取。
官方文件也明確寫到:要存取 Shared drives,請用進階 Drive 服務以獲得最新功能與支援。
權限與授權(Scopes)
大多數 DriveApp 操作需要 https://www.googleapis.com/auth/drive 或唯讀範圍,Apps Script 會依你使用的方法自動偵測所需範圍。也可在 appsscript.json 明確設定 oauthScopes。
若你把腳本發佈成 Web App 或 附加元件(Add-on),要特別留意執行身分與授權流程。
效能與配額(Quotas)
Apps Script 各服務都有每日配額與限制;超過就會丟例外並停止。Drive 相關操作也在此列。
常見限制包含單次執行時間上限、每日總執行時間、以及各服務呼叫次數等。實務上請把批次工作拆段、加上 sleep / 分頁迭代、以及錯誤重試。
常見錯誤與雷點
以下整理在實務上最容易踩到的坑,搭配對應的思路或繞道方式:
1.「為什麼我拿不到 Shared drives 的根?」
症狀:你以為 getRootFolder() 能代表「整個雲端硬碟世界」,但它只對應 My Drive。
對策:處理 Shared drives 時,改用進階 Drive 服務 / Drive API,並帶上 Shared drives 相關參數。
2. Web App 執行 getRootFolder() 回傳異常或空值
症狀:在 Web App 內呼叫,結果拿到 null 或沒有預期的內容。
原因思路:可能是 授權範圍、執行身分(「以我執行」vs「以存取者執行」)、或網頁端 google.script.run 呼叫流程所致。
對策:
確認 Web App 的 Deploy 設定(執行身分與存取權)
首次執行一定要經過完整授權流程(可先在 IDE 內跑一次以觸發授權)
若以「存取者」執行,該使用者至少要有 Drive 基本權限
分離伺服端與用戶端邏輯:伺服端函式取得資料後再回傳純資料給前端渲染
3. 權限被網域管理員關閉
症狀:在企業網域中,明明寫法正確,卻怎麼都取不到結果。
原因:管理員若關閉 Drive SDK,會影響所有使用 Drive 服務或進階 Drive 服務的腳本、Web App、附加元件。
對策:請網域管理員檢查 Drive SDK 與允許清單(allowlist)設定。
4. 多層父目錄歷史遺留 & 移動語意不清
症狀:舊檔案在歷史上可能曾有「多重父層」;你用程式碼搬移後,仍看到怪異路徑。
對策:先呼叫 DriveApp.enforceSingleParent(true),再用 moveTo() 做移動,避免遺留多父層情況。
5. 查詢條件不生效
症狀:searchFiles()/searchFolders() 沒回傳想要的結果。
原因:DriveApp 使用 Drive v2 查詢語法,欄位名稱與 v3 有差異。
對策:對照 v2 欄位文件撰寫條件;確保是在 根資料夾物件 上呼叫。
6. 配額打滿或逾時
症狀:大量搬檔、遍歷根層時,腳本中途停止。
對策:
拆批次(例如每 200 筆一批)
搭配時間驅動觸發器排程
寫入中繼點(PropertiesService)以便續跑
加入退避(exponential backoff)與再試機制
實務範例包:從根目錄打造一條龍維運
以下整合幾個常見任務,讓你直接拿去改:
範例 1:根層「孤兒檔」搬家(依副檔名分流)
function sweepRootByExtension() {
DriveApp.enforceSingleParent(true);
const root = DriveApp.getRootFolder();
// 規則表:副檔名 -> 目標資料夾
const rules = {
'pdf': '_PDFs',
'xlsx': '_Spreadsheets',
'csv': '_Spreadsheets',
'pptx': '_Slides',
'zip': '_Archives'
};
// 先建好目標資料夾
const ensure = name => {
const it = root.getFoldersByName(name);
return it.hasNext() ? it.next() : root.createFolder(name);
};
const targets = {};
Object.keys(rules).forEach(ext => targets[rules[ext]] ||= ensure(rules[ext]));
// 只看非 Google 內建類型(以檔名後綴判斷)
const files = root.getFiles();
let moved = 0;
while (files.hasNext()) {
const f = files.next();
const name = f.getName();
const m = name.match(/\.([A-Za-z0-9]+)$/);
if (!m) continue;
const ext = m[1].toLowerCase();
const bucket = rules[ext];
if (!bucket) continue;
f.moveTo(targets[bucket]);
moved++;
}
Logger.log(`Moved ${moved} files to categorized folders.`);
}
範例 2:根層快速稽核報表(寫回試算表)
function rootInventoryToSheet() {
const root = DriveApp.getRootFolder();
const ss = SpreadsheetApp.create('Root Inventory Report');
const sh = ss.getActiveSheet();
sh.appendRow(['Type', 'Name', 'Created', 'Starred', 'URL']);
const pushFolder = folder => {
sh.appendRow(['Folder', folder.getName(), folder.getDateCreated(), folder.isStarred(), folder.getUrl()]);
};
const pushFile = file => {
sh.appendRow(['File', file.getName(), file.getDateCreated(), file.isStarred(), file.getUrl()]);
};
let it = root.getFolders();
while (it.hasNext()) pushFolder(it.next());
it = root.getFiles();
while (it.hasNext()) pushFile(it.next());
Logger.log(`Report ready: ${ss.getUrl()}`);
}
範例 3:以根為起點的條件搜尋(最近 30 天修改、名稱含關鍵字)
function searchRecentWorkAtRoot(keyword) {
const root = DriveApp.getRootFolder();
const since = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
const y = since.getFullYear();
const m = ('0' + (since.getMonth() + 1)).slice(-2);
const d = ('0' + since.getDate()).slice(-2);
const q = `modifiedDate > "${y}-${m}-${d}" and title contains "${keyword}"`;
const files = root.searchFiles(q);
const results = [];
while (files.hasNext()) {
const f = files.next();
results.push([f.getName(), f.getUrl()]);
}
Logger.log(JSON.stringify(results, null, 2));
}
測試與除錯建議
最小步驟先通:先跑 getRootFolder() → getFiles()/getFolders(),確認授權與回傳正常,再疊加搜尋與搬移邏輯。
逐批處理:根層可能很多項目,請以迭代器逐步處理,並以 Logger 紀錄進度。
邏輯與權限分離:Web App 前端只負責顯示;資料一律由伺服端(Apps Script)取得後回傳,避免授權環節卡在前端。
管理員環境:企業網域若關了 Drive SDK,與 Drive 相關的腳本、附加元件都會受影響,要先確認這個系統性條件。
問題集
Q1. 我可不可以把 getRootFolder() 當成 Shared drives 的根?
不行。它只代表 My Drive。Shared drives 請用進階 Drive 服務或 Drive API。
Q2. 我搬檔案後,為什麼還看到舊路徑?
先啟用 DriveApp.enforceSingleParent(true) 再 moveTo(),降低多父層遺留造成的視覺錯亂。
Q3. searchFiles() 的欄位跟我印象中的不一樣?
內建 Drive 服務採 Drive v2 語法;請依 v2 欄位名稱撰寫條件。
Q4. 執行到一半停掉?
可能是配額或時間上限:把工作拆批次、加排程、做好錯誤重試,並參照官方 Quotas 文件。
