Google 雲端硬碟:取得資料夾或檔案 next()

 


很多人第一次用 Google Apps Script 的 DriveApp 都會以為回傳的是陣列,結果一呼叫 map() 或直接取值就報錯。其實像 getFiles()、getFolders() 拿到的是「迭代器」,要先 hasNext() 再 next() 一個一個取。差在這裡,寫法就完全不一樣。這篇會用生活化的方式把 next() 講清楚:它到底做什麼、什麼情境最好用、常見踩雷(像沒判斷就硬 next()、查詢語法寫錯、效能被時間限制卡住)、以及怎麼安全地把結果轉成陣列方便後續處理。文中也會補上實戰片段:指定資料夾篩檔、批次改名、遞迴走訪子資料夾,還有長任務怎麼用續跑 token 接力。希望本篇文章的內容可以幫助到您。


目錄

{tocify} $title={目錄} 


next() 是什麼?為什麼不是回傳陣列?

        在 DriveApp 中,像 DriveApp.getFiles()、getFilesByName(name)、getFilesByType(mimeType)、Folder.getFiles() 與 getFolders() 等方法,回傳的都是 迭代器,不是陣列。你不能直接用 forEach、map 去跑;正確寫法是:

const it = DriveApp.getFiles();     // FileIterator
while (it.hasNext()) {
  const file = it.next();           // 逐一取出 File 物件
  Logger.log(file.getName());
}


        hasNext():回傳布林值,表示還有沒有下一個。

        next():取出下一個元素;如果已經沒有,會丟出例外(Exception)。官方對 FileIterator 與 FolderIterator 的說明一致:沒有剩餘項目時呼叫 next() 就會拋錯,因此務必先判斷。


        這種設計是為了能處理「可能很大」的集合,不需要一次載入所有項目到記憶體。DriveApp 本身也提供多種取回迭代器的入口,例如 DriveApp.getFiles()、DriveApp.getFilesByName(name)、DriveApp.getFilesByType(mimeType),與從 Folder 物件上取得的 getFiles() / getFolders() 等。


你會在哪些情境用到 next()?

1.    列出整個雲端硬碟的所有檔案

        DriveApp.getFiles() → FileIterator → while (hasNext()) next()

2.    在特定資料夾中逐一處理檔案

        someFolder.getFiles() → FileIterator → next() 取出 File

3.    用條件搜尋再處理(DriveApp 的簡易查詢)

        DriveApp.searchFiles(query)(回 FileIterator),搭配 hasNext()/next() 走訪。想更彈性排序或分頁,需改用「進階服務 Drive」或 REST API。

4.    層層往下走資料夾樹

        Folder.getFolders() 會回 FolderIterator,用 next() 取得 Folder,再各自 getFiles()。


標準使用步驟(安全、穩定、不會拋錯)

模式 A:處理檔案迭代器

function listFilesBasic() {
  const files = DriveApp.getFiles(); // FileIterator
  while (files.hasNext()) {
    const f = files.next();          // File
    Logger.log([f.getId(), f.getName(), f.getMimeType()].join(' | '));
  }
}


模式 B:處理資料夾內檔案

function listFilesInFolder(folderId) {
  const folder = DriveApp.getFolderById(folderId);
  const it = folder.getFiles();      // FileIterator
  while (it.hasNext()) {
    const file = it.next();
    // 在這裡執行你的邏輯,例如搬移、改名、加共用…
  }
}


模式 C:條件搜尋(名稱、MIME 類型、時間)

function searchSpreadsheetsWithKeyword(keyword) {
  const query = "title contains '" + keyword.replace(/'/g, "\\'") + "'" +
                " and mimeType = 'application/vnd.google-apps.spreadsheet'";
  const it = DriveApp.searchFiles(query); // FileIterator
  while (it.hasNext()) {
    const file = it.next();
    Logger.log(file.getName());
  }
}


注意 title contains 在某些情況下僅做前綴詞匹配的行為觀察(「HelloWorld」可配到「Hello」,但不一定配得到「World」),撰寫查詢時要有心理準備。要更精準與可排序,建議改用 Drive API 的 files.list。


常見錯誤與雷點

1.    在沒有 hasNext() 的情況下直接呼叫 next()

症狀:拋出 Exception(沒有剩餘項目)。

修正:一律寫成 while (it.hasNext()) { const x = it.next(); ... }。這不是可選步驟,是必要保護。官方文件明確指出:沒有剩餘項目時呼叫 next() 會拋錯。

2.    以為 FileIterator/FolderIterator 能 map / forEach

症狀:TypeError 或邏輯跑不起來。

原因:它不是陣列。

修正:用 while + next();要陣列,就自己 push:

const arr = [];
while (it.hasNext()) arr.push(it.next());


(也有人在討論中提醒:只能一個一個 next() 取,不能直接用 map/filter,這是迭代器限制。)

3.    取得空迭代器、hasNext() 永遠是 false

症狀:你確定有檔案,但 getFilesByName / searchFiles 卻取不到。

排查清單:

        名稱是否完全符合(getFilesByName 是完全比對,不是包含比對)。

        查詢語法是否正確(title contains 'X' and mimeType = 'Y';需要 and not title contains 'Archived' 這種語法時,and 放的位置要對)。

        檔案是不是其實在別的資料夾或你沒有權限。

        遇到零星案例時,改以 Drive API files.list 驗證結果。

        社群上偶有個案貼文反映 getFilesByName() 對極少數檔案取不到,這類非系統性問題可用 API 交叉驗證並改以 files.list(q=...) 取得。


4.    誤以為能要求排序或自訂分頁

現況:DriveApp 的迭代器不提供排序參數與顯式分頁控制;只管「下一個」。

對策:

        需要排序(依 modifiedTime、name 等)或精確分頁,請改用「進階服務 Drive」或直接呼叫 Drive REST 的 files.list(可加 orderBy、q、pageSize、pageToken)。


5.    忘了權限範圍(Scopes)

症狀:第一次執行就跳要求授權或拿不到內容。

說明:

        像 DriveApp.getFiles()、getFilesByName() 等都屬於需要 drive.readonly 或 drive 授權範圍。第一次跑腳本會要求同意。


6.    大量資料的效能與時間限制

狀況:你的雲端硬碟很大;腳本執行時間(6 分鐘區間)吃緊。

對策:

        迭代時儘量早判斷早跳出(例如先快速比對 MIME 或名稱就跳過)。

        長任務切批次,搭配持續化狀態:FolderIterator 提供 getContinuationToken(),可保存進度,下次接續。

        需要可控分頁時,改用 Drive API files.list 的 pageToken 機制。


實務範例:你可以直接複製改名就上線


範例 1:列出整個雲端硬碟中前 200 個 PDF 的名稱與連結(遇到很多就提早停)

function listSomePDFs(limit = 200) {
  const it = DriveApp.getFilesByType('application/pdf');
  let count = 0;
  while (it.hasNext() && count < limit) {
    const f = it.next();
    Logger.log(`${f.getName()} | https://drive.google.com/open?id=${f.getId()}`);
    count++;
  }
}


為何可行:getFilesByType(mimeType) 回 FileIterator,next() 逐一取出 File。


範例 2:在指定資料夾中搜尋標題含關鍵字的 Google 試算表,並加上星號

function starSheetsInFolder(folderId, keyword) {
  const folder = DriveApp.getFolderById(folderId);
  const q = "title contains '" + keyword.replace(/'/g, "\\'") + "'" +
            " and mimeType = 'application/vnd.google-apps.spreadsheet'";
  const it = folder.searchFiles(q);        // FileIterator
  while (it.hasNext()) {
    const file = it.next();                // File
    file.setStarred(true);
  }
}


小叮嚀:title contains 的比對在某些情況下類似「前綴」行為;若要更精準/能排序,請考慮 Drive API。


範例 3:安全取得「唯一名稱」的資料夾,若無就建立

function getOrCreateFolderByExactName(name) {
  const it = DriveApp.getFoldersByName(name); // FolderIterator
  if (it.hasNext()) return it.next();         // 已存在 → 直接用
  return DriveApp.createFolder(name);         // 不存在 → 建立
}


說明:getFoldersByName() 回 FolderIterator,先 hasNext() 再 next() 取第一個。


範例 4:深度走訪:遞迴列出某資料夾下所有試算表

function listAllSheetsUnderFolder(rootId) {
  const root = DriveApp.getFolderById(rootId);
  walk(root);
  function walk(folder) {
    const files = folder.getFiles();
    while (files.hasNext()) {
      const f = files.next();
      if (f.getMimeType() === 'application/vnd.google-apps.spreadsheet') {
        Logger.log(`[SHEET] ${f.getName()} | ${f.getId()}`);
      }
    }
    const subs = folder.getFolders();
    while (subs.hasNext()) {
      walk(subs.next());
    }
  }
}


兩種迭代器交替使用:FileIterator 與 FolderIterator。每次都由 hasNext() 把關,next() 只在保證有下一個時呼叫。


範例 5:長任務續跑(使用 getContinuationToken())

function processInBatches() {
  const props = PropertiesService.getScriptProperties();
  const token = props.getProperty('FOLDER_TOKEN');
  let it = token
    ? DriveApp.continueFolderIterator(token)
    : DriveApp.getRootFolder().getFolders();

  let processed = 0;
  while (it.hasNext() && processed < 50) { // 每次跑 50 個
    const folder = it.next();
    // ...對 folder 做事...
    processed++;
  }
  // 存續標記,下一輪接續
  if (it.hasNext()) {
    props.setProperty('FOLDER_TOKEN', it.getContinuationToken());
  } else {
    props.deleteProperty('FOLDER_TOKEN');
  }
}


FolderIterator 支援取得延續用的 token,以利批次化處理。


與 Drive API 的分工:什麼時候該放下 DriveApp?

DriveApp + next() 的優勢是簡單好上手;但它的迭代器 無排序、無 pageSize、無複雜查詢。當你需要:

        依 modifiedTime desc 排序;

        指定每頁 100 筆並拿 pageToken 分頁;

        用更完整的 q 語法(擁有者、共用狀態、parents in …);

就該改用 Drive API files.list(可透過「進階服務 Drive」或直接呼叫 REST),這時一樣是 while 迴圈處理結果,但基礎能力更強、效率更可控。


Debug 與排錯清單

1.    永遠先 hasNext():沒有就別 next()。

2.    確認來源:DriveApp 取到的是整庫;Folder 取到的是該資料夾;別混淆。

3.    查詢字串:title contains、mimeType、modifiedDate、and not 等語法位置要對(常見是少了 and)。

4.    權限:首次執行需要 drive.readonly / drive 授權。

5.    需求超過 DriveApp 能力:要排序或嚴格分頁,就上 Drive API。

6.    極端個案:getFilesByName 找不到少數已存在檔案時,用 files.list 交叉驗證。


問題集

Q1:能不能把迭代器「一次展開」成陣列?

A:可以,但要自己 while 取完 push 到陣列;迭代器本身沒有 toArray()。

Q2:為什麼我覺得 title contains 有時不中?

A:社群觀察到它在部分情境像前綴匹配;要更一致用法與排序,請改 Drive API。

Q3:能不能指定排序(例如名稱或修改時間)?

A:DriveApp 不行;Drive API 可用 orderBy。


可直接套用的程式片段(拷貝就能用)

片段 A:把某資料夾下所有 PDF 搬到另一個資料夾

function movePdfs(srcFolderId, dstFolderId) {
  const src = DriveApp.getFolderById(srcFolderId);
  const dst = DriveApp.getFolderById(dstFolderId);
  const it = src.getFilesByType('application/pdf');
  while (it.hasNext()) {
    const f = it.next();
    dst.addFile(f);
    src.removeFile(f);
  }
}


片段 B:找名稱完全相同的一批檔案並重新命名(避免重名)

function renameDuplicateNames(name) {
  const it = DriveApp.getFilesByName(name);
  let i = 1;
  while (it.hasNext()) {
    const f = it.next();
    f.setName(`${name} (${i++})`);
  }
}


片段 C:用查詢字串篩出近 7 天更新的試算表

function recentSheets(days = 7) {
  const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000)
                  .toISOString().slice(0, 19) + 'Z';
  const q = "mimeType = 'application/vnd.google-apps.spreadsheet' " +
            "and modifiedDate >= '" + since + "'";
  const it = DriveApp.searchFiles(q);
  while (it.hasNext()) {
    const f = it.next();
    Logger.log(`${f.getName()} | ${f.getLastUpdated()}`);
  }
}


小結

1.    next() 是 DriveApp 迭代器的核心用法,一定先 hasNext() 再 next(),不然就會拋錯。

2.    DriveApp 的迭代器主打「簡單走訪」,不負責排序與分頁;想要更強查詢、排序與分頁,改用 Drive API files.list。

3.    針對大量資料與長任務,善用提早跳出與 getContinuationToken() 的續跑設計;必要時換 API。

4.    查詢語法細節(特別是 title contains 行為)要小心,碰到不確定的匹配與排序,用 API 交叉驗證。

張貼留言 (0)
較新的 較舊