有人問了我在 Windows 下如何得到某個機器下分享的資料夾總用量?由於單一機器分享的不只是資料夾, 並不允許直接用 DIR /s 指令列出所有資料夾, 因此就必須花點功夫寫個批次檔來處理。
取得 UNC 路徑下所有的分享資料夾清單
首先, 我們得知道 UNC 路徑下有哪些資源是資料夾, 這可以透過 NET VIEW 指令取得, 例如:
>net view \\192.168.0.217 共用資源在 \\192.168.0.217 共用名稱 類型 使用方式 註解 ------------------------------------------------------------------------------- books Disk CPP Disk 命令已經成功完成。
其中, 類型為 'Disk' 的就是分享的資料夾, 可以透過 DIR /s 直接取得總用量, 例如:
>dir /s \\192.168.0.217\cpp 磁碟區 \\192.168.0.217\cpp 中的磁碟是 Data 磁碟區序號: 9446-A1B0 \\192.168.0.217\cpp 的目錄 2022/12/28 下午 02:15 <DIR> . 2023/01/20 下午 06:16 <DIR> .. 2022/04/08 下午 04:15 <DIR> .ipynb_checkpoints ... \\192.168.0.217\cpp\test_ini\output 的目錄 2022/05/03 下午 01:42 <DIR> . 2022/05/03 下午 01:42 <DIR> .. 2022/05/03 下午 01:42 240 foo.obj 2022/05/03 下午 01:42 756 main.obj 2 個檔案 996 位元組 檔案數目總計: 291 個檔案 82,151,599 位元組 149 個目錄 30,557,925,376 位元組可用
DIR /s 最後會列出總計的檔案數量以及總用量, 所以只要針對清單中的個別資料夾一一取得用量, 加總後就可以知道所有分享資料夾的總用量了。
以下就是依據此概念撰寫的批次檔:
@echo off Setlocal EnableDelayedExpansion set /a total=0 @rem net view 可以顯示 UNC 路徑下的所有分享資源 @rem 每一行都是 資源名稱 資源類型 的格式 for /f "tokens=1,2" %%l in ('net view %1') do ( @REM ----------------------------------- @REM 1(l) 2(m) @REM 共用名稱 類型 使用方式 註解 @REM books Disk @REM CPP Disk @REM ----------------------------------- @rem 資源類型為 Disk 的就是資料夾 if "%%m"=="Disk" ( set full_path=%1\%%l set get_total=F @rem dir /s 可以遞迴列出子資料夾與檔案並統計總用量 for /f "tokens=1,3" %%p in ('dir /s !full_path!') do ( @rem ------------------------------------------------- @rem 1(p) @REM 檔案數目總計: @rem 1(p) 2 3(q) @REM 291 個檔案 82,151,599 位元組 @REM 149 個目錄 30,767,378,432 位元組可用 @rem ------------------------------------------------- @rem 已經到了倒數第二行, 也就是總計用量 if "!get_total!"=="T" ( set subtotal=%%q @rem 將顯示用量中的 ',' 去掉 echo %%l 用量:!subtotal:,=! set /a total+=!subtotal:,=! set get_total=F ) @rem dir /s 倒數第三行是『檔案數目總計:』 if "%%p"=="檔案數目總計:" ( set get_total=T ) ) ) ) echo 總用量:%total%
外層 for 迴圈會從 NET VIEW 的輸出結果中逐行取出以空格隔開的第一欄 (資源名稱) 與第二欄 (類型), 如果第二欄 (類型)是 'Disk', 就以內層的 for 迴圈透過 DIR /s 指令取得資料夾內容。由於我想要的是最後倒數第二行的總計, 所以先以 if 判斷前一行是否為 "檔案數目總計:", 即可取出位在以空格隔開的第三欄的總計位元組數了。
由於總用量的數字每三位會以逗號區隔, 但 SET /a 不接受這樣的數值格式, 因此我們也會先用字串取代的方式, 將逗號去除。如果你覺得很麻煩, 也可以在 DIR 時加上 /-c 選項, 就可以用不加逗號的格式顯示數值。
實際執行會像是這樣:
>list_shared_folders2 \\192.168.0.217 books 用量:71280427 CPP 用量:82151599 總用量:153432026
無法自動取得子資料夾的神奇分享資料夾
上述批次檔看起來似乎運作的很好, 不過實際上卻遇到一個奇怪的問題, 有些分享資料夾在使用 DIR /s 的時候, 就只會列出第一層, 不會進入子資料夾, 我不確定這是不是因為 NAS 的關係, 但是同一台機器上的其他分享資料夾又都正常。
在我無法動到遠端不歸我管的 NAS 或是伺服器的情況下, 只好改變策略, 針對分享資料夾內的個別資料夾取得用量後再加總, 這個版本的批次檔如下:
@echo off Setlocal EnableDelayedExpansion set /a total=0 @rem net view 可以顯示 UNC 路徑下的所有分享資源 @rem 每一行都是 資源名稱 資源類型 的格式 for /f "tokens=1,2" %%l in ('net view %1') do ( @rem 資源類型為 Disk 的就是資料夾 if "%%m"=="Disk" ( set full_path=%1\%%l @rem dir /s 預設為包含隱藏檔, 若沒有 /s 則不會 for /f "tokens=2,3,4,5" %%f in ('dir /a/-c !full_path!') do ( @rem -------------------------------------------------- @rem 1 2(f) 3(g) 4(h) @rem 43 個檔案 4938370 位元組 @rem -------------------------------------------------- if "%%f"=="個檔案" ( echo !full_path! 用量:%%g set /a total+=%%g ) @rem ------------------------------------------------- @rem 1 2(f) 3(g) 4(h) 5(i) @rem 2022/05/03 下午 01:42 <DIR> test_ini @rem -------------------------------------------------- if "%%h"=="<DIR>" ( if not "%%i"=="." ( if not "%%i"==".." ( set sub_path=!full_path!\%%i @rem dir /s 可以遞迴列出子資料夾與檔案並統計總用量 set get_total=F for /f "tokens=1,3" %%p in ('dir /s /-c !sub_path!') do ( @rem ------------------------------------------------- @rem 1(p) @REM 檔案數目總計: @rem 1(p) 2 3(q) @REM 3781 個檔案 71280427 位元組 @REM 1501 個目錄 30558121984 位元組可用 @rem ------------------------------------------------- @rem 已經到了倒數第二行, 也就是總計用量 if "!get_total!"=="T" ( echo !sub_path! 子資料夾用量:%%q set /a total+=%%q set get_total=F ) @rem dir /s 倒數第三行是『檔案數目總計:』 if "%%p"=="檔案數目總計:" ( set get_total=T ) ) ) ) ) ) ) ) echo 總用量:%total%
在第二層的 for 迴圈要特別注意, 必須以 DIR /a 才能顯示隱藏的資料夾, 否則會漏計用量。另外, 也要記得把最上層的分享資料夾自己的用量加進來, 不然就只會計算到分享的資料夾底下子資料夾的用量。
第三層的 for 迴圈就跟前一版的批次檔一樣, 使用 DIR /s 計算連同子資料夾的總用量, 不再贅述。
這一版也在 DIR 時加上 /-c 選項, 不用逗號區隔數字, 執行結果如下:
>list_shared_folders \\192.168.0.217 \\192.168.0.217\books\.git 子資料夾用量:30185953 \\192.168.0.217\books\.vscode 子資料夾用量:0 \\192.168.0.217\books\old 子資料夾用量:13204 \\192.168.0.217\books\python 子資料夾用量:41051833 \\192.168.0.217\books 用量:29437 \\192.168.0.217\CPP\.ipynb_checkpoints 子資料夾用量:144 \\192.168.0.217\CPP\.vscode 子資料夾用量:46791 \\192.168.0.217\CPP\blink 子資料夾用量:21016766 \\192.168.0.217\CPP\esp32_times 子資料夾用量:350 \\192.168.0.217\CPP\fb_test 子資料夾用量:104207 \\192.168.0.217\CPP\intro 子資料夾用量:55832596 \\192.168.0.217\CPP\intro_define.cpp 子資料夾用量:802 \\192.168.0.217\CPP\mdp 子資料夾用量:19863 \\192.168.0.217\CPP\test 子資料夾用量:59625 \\192.168.0.217\CPP\test_ext 子資料夾用量:3889 \\192.168.0.217\CPP\test_extern 子資料夾用量:61501 \\192.168.0.217\CPP\test_ini 子資料夾用量:66695 \\192.168.0.217\CPP 用量:4938370 總用量:153432026
你可以看到它會先取得分享資料夾內個別資料夾的用量, 再加上分享資料夾本身的用量, 運作正常。
SET /a 的限制:32 位元整數
剛剛的批次檔測試後可以正常運作, 但是有一個大問題, 就是 SET /a 只能處理 32 位元的整數, 設定時只能接受最大 2147483647, 單一資料夾的用量一大, 就爆掉了, 類似這樣:
>set /a a=2147483647 2147483647 >set /a a=2147483648 不正確的數字。數字限制為 32 位元精確度。
這個問題有兩個解法, 一是改用 Powershell, 即可使用 .net 的 long 來計算;另一種是自己將 DIR 取得的數字分段, 個別計算並處理進位即可, 以下就是我新修改的程式:
@echo off Setlocal EnableDelayedExpansion @rem net view 可以顯示 UNC 路徑下的所有分享資源 @rem 每一行都是 資源名稱 資源類型 的格式 for /f "tokens=1,2" %%l in ('net view %1') do ( @rem 資源類型為 Disk 的就是資料夾 if "%%m"=="Disk" ( set full_path=%1\%%l @rem dir /s 預設為包含隱藏檔, 若沒有 /s 則不會 for /f "tokens=2,3,4,5" %%f in ('dir /a/-c !full_path!') do ( @rem -------------------------------------------------- @rem 1 2(f) 3(g) 4(h) @rem 43 個檔案 4938370 位元組 @rem -------------------------------------------------- if "%%f"=="個檔案" ( echo !full_path! 用量:%%g call :uni_add %%g ) @rem ------------------------------------------------- @rem 1 2(f) 3(g) 4(h) 5(i) @rem 2022/05/03 下午 01:42 <DIR> test_ini @rem -------------------------------------------------- if "%%h"=="<DIR>" ( if not "%%i"=="." ( if not "%%i"==".." ( set sub_path=!full_path!\%%i @rem dir /s 可以遞迴列出子資料夾與檔案並統計總用量 set get_total=F for /f "tokens=1,3" %%p in ('dir /s /-c !sub_path!') do ( @rem ------------------------------------------------- @rem 1(p) @REM 檔案數目總計: @rem 1(p) 2 3(q) @REM 3781 個檔案 71280427 位元組 @REM 1501 個目錄 30558121984 位元組可用 @rem ------------------------------------------------- @rem 已經到了倒數第二行, 也就是總計用量 if "!get_total!"=="T" ( echo !sub_path! 子資料夾用量:%%q call :uni_add %%q set get_total=F ) @rem dir /s 倒數第三行是『檔案數目總計:』 if "%%p"=="檔案數目總計:" ( set get_total=T ) ) ) ) ) ) ) ) echo 總用量:%tot_s% goto:eof :uni_add set num=%~1 @rem 取得位數 for /l %%d in (0, 1, 98) do ( if not "!num:~%%d,1!" == "" ( set /a digits=%%d + 1 ) ) @rem 每三位數一個單位會剩下幾位數 set /a remain_digits=digits%%3 @rem 三位數一個單位共佔幾位數 set /a r_digit=digits - remain_digits @rem 進位數值 set /a carry=0 @rem 依序取得每三位的數字 for /l %%d in (-3, -3, -!r_digit!) do ( set sub_sum[%%d]=!num:~%%d,3! ) set /a r_digit+=3 set /a r_digit=-r_digit @rem 取得不足三位的最左邊剩餘數字 for %%d in (!remain_digits!) do ( @REM echo !num:~0,%%d! set sub_sum[!r_digit!]=!num:~0,%%d! ) @rem 循序三位一組將小計與目前總計相加 for /l %%d in (-3, -3, !r_digit!) do ( if "!totals[%%d]!"=="" ( set totals[%%d]=!sub_sum[%%d]! set /a carry=0 ) else ( set /a temp=1000!sub_sum[%%d]!%%1000+1000!totals[%%d]!%%1000+carry set /a carry=temp/1000 set /a temp=temp%%1000 set totals[%%d]=!temp! ) ) @rem 處理進位 if !carry! gtr 0 ( set /a r_digit-=3 for /l %%d in (!r_digit!, -3, !max_digit!) do ( @rem 由於 0 開頭的數字會被當成 8 進位 @rem 這裡採取加 1000000 再取除以 1000 的餘數來避免 set /a temp=1000!totals[%%d]!%%1000+carry set /a carry=temp/1000 set /a temp=temp%%1000 set totals[%%d]=!temp! ) ) @rem 更新目前最高位數 if !max_digit! gtr !r_digit! set /a max_digit=r_digit @rem 進位到新的一組三位數 if !carry! gtr 0 ( set /a max_digit-=3 for %%d in (!max_digit!) do ( set totals[%%d]=carry ) ) @rem 用三位一組的格式顯示總計 set tot_s= for /l %%d in (!max_digit!, 3, -3) do ( set temp_s=1000!totals[%%d]! @rem 由於計算結果可能不到三位數 @rem 這裡用特別的技巧補上開頭的 0 set tot_s=!tot_s!,!temp_s:~-3,3! ) set tot_s=!tot_s:~1! exit /b
在程式最後新增了 uni_add 副程式, 它會把數字每三個一位分段, 個別當成數字進行加法, 並且處理進位。要特別留意的是, 因為我們是直接把文字每三位切開, 以 120051 為例, 會切開成 120 和 051, 但是因為批次檔的解譯器會把 0 開頭的數字以 8 進位來解釋, 如果不特別處理, 就會得到錯誤的結果, 例如:
>set a=051 >set /a a=a+10 51
由於 051 被解釋成 8 進位, 所以變成 41 + 10, 結果就是奇怪的 41。為了避免這個問題, 我們採用簡單的方式, 先在數字前面串接上 "1000", 然後將串接後的結果當成數值運算取除以 1000 的餘數, 就可以確保得到正確的 10 進位數值, 像是這樣:
>set a=051 >set /a a=1000%a%%1000+10 61
這裡要特別留意, 前面串接的字串在 1 之後至少要有 3 個 0, 因為我們每個分段是三位數。另外, 也要記得在互動環境下取餘數只要寫一個 % 符號, 但是在批次檔中則是要寫兩個 % 符號。
當我們要把個別分段重組起來顯示的時候, 會遇到數字不足三位數的問題, 例如 26, 除非是在整個數字最左側, 否則就應該要顯示成 026, 不然會少掉一位數。這裡我們也採取類似的方式, 在前面先串接上 "1000", 然後從倒數第三個數字開始取三位數, 就可以自動補 0, 像是這樣:
>set a=26 >set a=1000%a% >echo %a:~-3% 026
結語
實際上如果遠端是 NAS, 我想應該都是有使用者介面可以查看, 但是在無法進入 NAS 管理系統的前提下, 還是得靠本文的苦力方式取得資訊。
Top comments (0)