codemee
Posted on January 22, 2023
有人問了我在 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 管理系統的前提下, 還是得靠本文的苦力方式取得資訊。
Posted on January 22, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.